大纲
前言
本文将介绍在 SpringBoot 项目中,如何通过 AOP + 反射 + 自定义注解来统计接口的性能,且支持拔插式使用。在不引入分布式日志链路追踪技术的情况下,统计接口的性能一般有以下几种实现方式:
- (1) 基于 Filter(过滤器)实现
- (2) 基于 AOP + 反射 + 自定义注解实现
- (3) 在 Spring Cloud GateWay 中,基于 GlobalFilter(全局过滤器)实现
代码下载
完整的案例代码可以从 这里 下载得到。值得一提的是,本文的案例代码不仅适用于 SpringBoot 项目,理论上适用于任何 Spring 项目。
案例代码
- 引入依赖,如果仅仅是 Spring 项目,需要改为引入
spring-aop
和 aspectjweaver
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency> </dependencies>
|
1 2 3 4 5 6 7 8 9 10
| import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MethodExporter {
}
|
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import com.clay.log.tracing.annotations.MethodExporter; import com.fasterxml.jackson.databind.ObjectMapper; 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.reflect.MethodSignature; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j @Aspect @Component public class MethodExporterAspect {
@Around("@annotation(com.clay.log.tracing.annotations.MethodExporter)") public Object methodExporter(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object resultValue = null;
log.info("----- @Around before"); long startTime = System.currentTimeMillis();
resultValue = proceedingJoinPoint.proceed();
long endTime = System.currentTimeMillis(); long costTime = endTime - startTime;
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); Method method = signature.getMethod();
MethodExporter methodExporterAnnotation = method.getAnnotation(MethodExporter.class);
if (methodExporterAnnotation != null) { StringBuilder jsonParam = new StringBuilder(); Object[] parameterValues = proceedingJoinPoint.getArgs(); DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = discoverer.getParameterNames(method); for (int i = 0; i < parameterNames.length; i++) { jsonParam.append(parameterNames[i] + " = " + parameterValues[i].toString() + "; "); } String jsonResult = null; if (resultValue != null) { jsonResult = new ObjectMapper().writeValueAsString(resultValue); } else { jsonResult = "null"; }
log.info("\n方法分析上报中 " + "\n类名方法名: " + proceedingJoinPoint.getTarget().getClass().getName() + "." + proceedingJoinPoint.getSignature().getName() + "()" + "\n执行耗时: " + costTime + "毫秒" + "\n输入参数: " + jsonParam + "" + "\n返回结果: " + jsonResult + "" + "\n方法分析上报结束" ); log.info("----- @Around after"); }
return resultValue; }
}
|
- 主启动类,添加了
@EnableAspectJAutoProxy
注解
1 2 3 4 5 6 7 8 9
| @SpringBootApplication @EnableAspectJAutoProxy public class MainApplication {
public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); }
}
|
测试代码
- 控制器类,在需要统计性能的接口上添加
@MethodExporter
自定义注解即可
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import com.clay.log.tracing.annotations.MethodExporter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit;
@RestController @RequestMapping("/pay") public class PayController {
@MethodExporter @GetMapping(value = "/list") public Map list(@RequestParam(value = "page", defaultValue = "1") int page, @RequestParam(value = "rows", defaultValue = "5") int rows) {
Map<String, String> result = new LinkedHashMap<>(); result.put("code", "200"); result.put("message", "success");
try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); }
return result; }
@MethodExporter @GetMapping(value = "/get") public Map get() { Map<String, String> result = new LinkedHashMap<>(); result.put("code", "404"); result.put("message", "not-found");
try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); }
return result; }
@GetMapping(value = "/update") public String update() { System.out.println("invoke method without @MethodExporter"); return "success"; }
}
|
访问 http://127.0.0.1:8080/pay/list
接口后,控制台输出的日志信息如下:
1 2 3 4 5 6
| 方法分析上报中 类名方法名: com.clay.log.tracing.controller.PayController.list() 执行耗时: 865毫秒 输入参数: page = 1; rows = 5; 返回结果: {"code":"200","message":"success"} 方法分析上报结束
|
访问 http://127.0.0.1:8080/pay/update
接口后,控制台输出的日志信息如下:
1
| invoke method without @MethodExporter
|
参考资料