# (一)前言 {#一-前言}
最近到年底了,上线三个月的系统也已经稳定了。不过项目经理最近感觉系统变慢了,想知道整个系统前端和后端所有方法的执行时间。是的你没有听错,是每个方法。这个需求一提出来就被整蒙了,给每个方法计算执行时间,这不是平白给系统压力吗?
# (二)思路 {#二-思路}
既然提了,先给思路吧。其实很简单,每个方法执行前记录一个时间戳,执行结束后记录一个时间戳,相减就是执行时间。但是去每个方法里改是不现实的,那么多代码,如果去侵入那工作量也太大了,而且如果有一天项目经理又不要这个功能了呢?
一听到这个需求,就应该想到AOP,用了那么久的Spring,IOC、AOP背的滚瓜烂熟,现在就是应用的时候了。
# (三)使用AOP记录时间 {#三-使用aop记录时间}
AOP的概念就不在这里提了,面向切面,无侵入开发,正适合这个需求。而且20行代码可以解决问题。
# 3.1 引入依赖 {#_3-1-引入依赖}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1
2
3
4
# 3.2 配置切面 {#_3-2-配置切面}
首先配置一个切入点,我这里要让切面在TestController这个类中的所有方法上生效
@Slf4j
@Component
@Aspect
@Order(10)
public class LogAspect {
//配置切入点,即执行的位置
@Pointcut("execution(* com.javayz.codinguser.controller.TestController.*(..))")
public void pointCut(){
}
//使用环绕通知,在方法的执行前后分别记录一个时间相减
@Around("pointCut()")
public Object doLog(ProceedingJoinPoint proceedingJoinPoint){
long startTime = System.currentTimeMillis();
Object object=null;
try {
object=proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
long endTime = System.currentTimeMillis();
log.info(proceedingJoinPoint.getSignature().toShortString()+"方法执行了:"+(endTime-startTime)+"ms");
}
return object;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
TestController方法是这样的:
@RestController
@RequestMapping("/testApi")
public class TestController {
@GetMapping("/test1")
public String testAop(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "testAop";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.3 测试一下 {#_3-3-测试一下}
直接调用/testApi/test1,效果出来了
# 3.4 自定义一个注解 {#_3-4-自定义一个注解}
现在已经可以满足项目经理的需求了,如果以后新的代码没有在切入点的范围内,就又不生效了,因此定义一个注解吧,要记录时直接写个注解
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
}
1
2
3
4
5
配置切入点的时候增加注解
@Pointcut("execution(* com.javayz.codinguser.controller.TestController.*(..)) || @annotation(com.javayz.codinguser.annotation.LogAnnotation)")
public void pointCut(){
}
1
2
3
好了,在需要执行的时候加上这个注解就OK了。
# (四)关于记录数据的问题 {#四-关于记录数据的问题}
项目经理还希望可以随时查某个时间的方法执行时间,那就必须存下来这些数据了。
现在这个系统共有4万人使用,如果存入数据库,我在表里定义三个字段:时间、调用人员、调用时间,调用方法,一条数据大约在40字节大小。按四万个人的使用,一天最少也要产生4000万条数据,大约1.5G。数据量倒是没关系,对数据库写入的压力就更大了。
如果存在日志里也一样,日志会变得特别大,而且排查起来不方便。
# (五)结果 {#五-结果}
最终,在和项目经理一顿讲理之后,终于改变了他的想法,只记录执行时间超过200ms的方法,并且数据就记录下日志里,这个需求也算是通过沟通和小部分代码就解决了。好了,我们下期再见!