接口请求耗时和接口的访问次数是我们比较关注点之一,接口请求时间的快慢就代表着获取到对应的数据的快慢,也代表着用户请求页面数据的快慢

以往我们的做法可能是在每一个接口的方法中的开始添加当前时间,结尾用当前时间减去开始时间就表示该接口的访问时间

@RequestMapping("/test")
public String test02(){
    long startTime = System.currentTimeMillis();
    //此处的调用业务代码省略
    System.out.println("访问时间为:"+(System.currentTimeMillis()-startTime));
    return "访问接口成功";
}

那如果有几百个接口的话,每一个接口都需要统计对应的访问时间的话,那就要写几百遍,这很不符合我们的常理,所以有没有一种办法是可以不修改对应的接口方法,并且只需要写一遍就能够应用到所有的接口上面或者指定的接口上面

我们第一时间就可以想到AOP技术,AOP是在Spring当中比较常见的技术, AOP就是在不修改原来的代码就可以对接口方法进行增强的作用,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

https://pic.imgdb.cn/item/62ea80a616f2c2beb1826938.png

有了解决方案我们就可以进行代码的构建,首先我们直接新建一个SpringBoot项目即可,然后进行下面的操作

1、引入依赖

所以要用到AOP技术,我们首先需要导入对应的依赖,Redis依赖也导入一下,后面统计接口访问的次数时也需要用到

<!--引入AOP依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--引入Redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、自定义注解

统计接口的耗时和访问次数也不需要每一个接口都使用,比如说一些不经常访问的接口就没有统计他的访问次数,所以我们可以自定义一个注解,只要对应的接口方法上应用了这个注解,Spring会进行扫描,并执行对应的操作

//统计接口的耗时以及再规定事件内的访问次数,作用域为方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TakeCount {

    //表示统计接口次数的时间,默认为60秒
    int time() default 60;

}

3、配置Redis过期键监听以及对应配置类

统计接口在指定时间内的访问次数,我们可以监听Redis对应过期的key来获取对应的访问次数,过期的时间是自己在注解的参数中设置的时间,如果没有设置,默认就是60秒

//对Redis过期的key进行监听
@Slf4j
public class KeyExpiredListener extends KeyExpirationEventMessageListener {

    @Autowired
    @Qualifier("myRedisTemplate")
    private RedisTemplate redisTemplate;

    public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        //获取对应的接口的访问次数
        log.info("{}接口在规定时间内被访问{}次",message,redisTemplate.opsForValue().get(message.toString()+":count"));
        //将对应接口的访问数据的key进行删除
        redisTemplate.delete(message.toString()+":count");
    }
}

接着还需要写一个Redis的配置类,将刚刚的监听类注入到Spring的容器当中,并且自定义Redis的字符串序列化,默认的为jdk本身的序列化,存储时会乱码

//Redis配置类
@Configuration
public class RedisConfiguration {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer() {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
        return redisMessageListenerContainer;
    }

    @Bean
    public KeyExpiredListener keyExpiredListener() {
        return new KeyExpiredListener(this.redisMessageListenerContainer());
    }

    //配置Redis的字符串序列化,默认的为jdk本身的序列化,存储时会乱码
    @Bean("myRedisTemplate")
    @SuppressWarnings("all")
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        //key序列化
        template.setKeySerializer(new StringRedisSerializer());
        //value序列化
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //hash的key序列化
        template.setHashKeySerializer(new StringRedisSerializer());
        //hash的value序列化
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        template.setConnectionFactory(factory);
        return template;
    }

}

4、定义AOP切面

如果接口方法上应用了自定义的注解,那么就会被Spring扫描到,当接口方法被执行的时候,就会先执行doBefore方法,记录开始的时间并且将该接口方法对应的访问次数加一,在接口方法执行完毕之后就会执行doAfter,将接口调用的时间输出到控制台上

@Aspect
@Component
@Slf4j
public class TakeCountAspect {

    @Autowired
    @Qualifier("myRedisTemplate")
    private RedisTemplate redisTemplate;

    //用ThreadLocal记录当前线程访问接口的开始时间
    private ThreadLocal<Long> startTime = new ThreadLocal<>();

    //扫描所有添加了@TakeCount注解的方法
    @Before("@annotation(takeCount)")
    public void doBefore(TakeCount takeCount){
        //记录接口的开始时间
        startTime.set(System.currentTimeMillis());
        //接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求的内容
        String url = request.getRequestURL().toString();
        //如果缓存当中没有当前接口的key就进行存储,如果有的话就对应接口的访问数据自增加一
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(url, "num",takeCount.time(), TimeUnit.SECONDS);
        if(ifAbsent){
            redisTemplate.opsForValue().set(url+":count",1);
        }else{
            redisTemplate.opsForValue().increment(url+":count");
        }
    }

    //接口方法执行完成之后
    @After("@annotation(TakeCount)")
    public void doAfter(JoinPoint joinPoint){
        //将当前的事件减去之前的事件
        log.info("{}访问时间为:{}ms",joinPoint.getSignature().getName(),(System.currentTimeMillis()-startTime.get()));
    }

}

5、定义接口方法

在Controller层中自定义一个接口,然后添加上 @TakeCount注解,time根据自己可设置可不设置 (默认单位为秒)

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/02")
    @TakeCount(time = 15)
    public String test02(){
        //此处的逻辑代码省略
        return "访问接口成功";
    }
    
}

6、运行测试

最后在浏览器中访问localhost:8080/test/02就能看到对应的效果啦

image-20220803222336174

已上SpringBoot统计接口请求耗时以及在指定时间内的访问次数的方法,希望文章内容对大家有所帮助,也纯属是个人的做法,如果有好的方法也可以自己试一试!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐