SpringBoot统计接口请求耗时
💬 写在前面
接口请求时间的快慢就代表着获取到对应的数据的快慢,也代表着用户请求页面数据的快慢,常常可以借助接口请求快慢进行相应的优化!
以往我们的做法可能是在每一个接口的方法中的开始添加当前时间,结尾用当前时间减去开始时间就表示该接口的访问时间。
具体代码如下:
@RequestMapping("/test")
public String test(){
long startTime = System.currentTimeMillis();
//此处的调用业务代码省略
System.out.println("访问时间为:"+(System.currentTimeMillis()-startTime));
return "访问接口成功";
}
那如果有几百个接口的话,每一个接口都需要统计对应的访问时间的话,那就要写几百遍,这很不符合我们的常理,所以有没有一种办法是可以不修改对应的接口方法,并且只需要写一遍就能够应用到所有的接口上面或者指定的接口上面。
我们第一时间就可以想到AOP技术,AOP是在Spring当中比较常见的技术, AOP就是在不修改原来的代码就可以对接口方法进行增强的作用,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
🌸 解决方案
根据上述,我们需要到AOP,第一个不能少的则是对应的依赖。
🌲 引入对应依赖
<!--aspectj-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.4</version>
</dependency>
🌿 自定义注解
统计接口的耗时和访问次数也不需要每一个接口都使用,比如说一些不经常访问的接口就没有统计他的访问次数,所以我们可以自定义一个注解,只要对应的接口方法上应用了这个注解,Spring会进行扫描,并执行对应的统计耗时操作即可。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 统计 方法/接口耗时 注解
*
* @author devcheng
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CostTime {
}
🌴 定义AOP切面
如果接口方法上应用了自定义的注解,那么就会被Spring扫描到,这里我用的是 @Pointcut 和 @Around 配合使用。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 统计 方法/接口耗时 注解
*
* @author devcheng
*/
@Aspect
@Component
@Slf4j
public class CostTimeAspect {
@Pointcut(value = "@annotation(net.devcheng.www.data.annotation.CostTime)")
public void costTime() {
}
@Around("costTime()")
public Object costTimeAround(ProceedingJoinPoint joinPoint) {
Object obj = null;
try {
long beginTime = System.currentTimeMillis();
obj = joinPoint.proceed();
//获取方法名称
String method = joinPoint.getSignature().getName();
//获取类名称
String className=joinPoint.getSignature().getDeclaringTypeName();
//计算耗时
long cost = System.currentTimeMillis() - beginTime;
log.error("类:[{}],方法:[{}] 接口耗时:[{}]", className,method, cost + "毫秒");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return obj;
}
}
🌋 用在统计接口上
@GetMapping("/V4/getSignsPredictDetail")
@ResponseBody
@CostTime
public String getSignsPredictDetail(String name) {
if (StringUtils.isEmpty(name)) {
return "[]";
}
return cityBrain4Service.getSignsPredictDetailByName(name);
}
🌏 用在统计定时任务上
@Scheduled(cron = "55 */5 * * * ?")
@CostTime
public void scenesSignTask() {
// 业务逻辑
}
🎉 运行输出
2022-11-18 10:31:51.523 [http-nio-8886-exec-8] ERROR net.devcheng.www.data.config.CostTimeAspect Line:32 - 类:[net.devcheng.www.data.controller.SpecialInterfaceController],方法:[getWeather] 接口耗时:[0毫秒]
2022-11-18 10:31:52.122 [http-nio-8886-exec-9] ERROR net.devcheng.www.data.config.CostTimeAspect Line:32 - 类:[net.devcheng.www.data.controller.SpecialInterfaceController],方法:[getWeather] 接口耗时:[1毫秒]
2022-11-18 10:31:55.073 [http-nio-8886-exec-15] ERROR net.devcheng.www.data.config.CostTimeAspect Line:32 - 类:[net.devcheng.www.data.controller.CityBrain4Controller],方法:[getScrollingMessages] 接口耗时:[2毫秒]
2022-11-18 10:31:55.076 [http-nio-8886-exec-3] ERROR net.devcheng.www.data.config.CostTimeAspect Line:32 - 类:[net.devcheng.www.data.controller.SpecialInterfaceController],方法:[getWeather] 接口耗时:[1毫秒]
The end