SpringBoot下使用aspectJ进行性能诊断
背景
SpringBoot中的性能诊断。
本文分两个部分。先介绍SpringBoot-AOP方法,其使用方便,但是存在两个问题:
- 扩展性:不支持private方法,不支持本类内部调用的其他方法进行切面。
- 性能:继承、实现了spring的代理类和拦截器,性能相对差一些。
然后介绍原生的AspectJ方法。
SpringBoot-AOP初体验
依赖
需要添加两个依赖,版本号可以使用最新。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.0.1.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.12</version> <scope>compile</scope> </dependency>
增加Config和Before advice
通过Config来使用AOP。
使用Before advice来打点。
@Aspect @Configuration public class UserAccessAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); //What kind of method calls I would intercept //execution(* PACKAGE.*.*(..)) //Weaving & Weaver @Before("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))") public void before(JoinPoint joinPoint) { //Advice logger.info(" Check for user access "); logger.info(" Allowed execution for {}", joinPoint); } }
After advice
也可以使用After advice来打点。
@After
: 在两种场景下执行 - 当一个方法成功执行或是抛出异常@AfterReturning
: 只有在方法成功执行后运行@AfterThrowing
: 只有在方法抛出异常后运行
@Aspect @Configuration public class AfterAopAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @AfterReturning(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { logger.info("{} returned with value {}", joinPoint, result); } @After(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))") public void after(JoinPoint joinPoint) { logger.info("after execution of {}", joinPoint); } }
Around advice
最后介绍Around打点方式。
@Aspect @Configuration public class MethodExecutionCalculationAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Around("@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object proceed = joinPoint.proceed(); long timeTaken = System.currentTimeMillis() - startTime; logger.info("Time Taken by {} is {}", joinPoint, timeTaken); return proceed; } }
原生AspectJ初体验
依赖
<plugin> <groupId>com.github.m50d</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.11.1</version> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.8.9</version> </dependency> </dependencies> <configuration> <complianceLevel>${maven.compiler.target}</complianceLevel> <source>${maven.compiler.target}</source> <target>${maven.compiler.target}</target> <proc>none</proc> <showWeaveInfo>true</showWeaveInfo> <verbose>true</verbose> <Xlint>ignore</Xlint> <encoding>${project.build.sourceEncoding}</encoding> <forceAjcCompile>true</forceAjcCompile> <sources/><!-- this is important!--> <weaveDirectories> <weaveDirectory>${project.build.directory}/classes</weaveDirectory> </weaveDirectories> </configuration> <executions> <execution> <phase>process-classes</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin>
代码切片
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; @Aspect public class FunctionAspect { public static final Map<String, AtomicLong> functionTimeCost = new ConcurrentHashMap<>(); public static final Map<String, AtomicLong> functionTimes = new ConcurrentHashMap<>(); @Around(value = "execution(* com.abeffect..*(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // System.out.println("around execution " + joinPoint); long startTime = System.currentTimeMillis(); Object proceed = joinPoint.proceed(); long timeTaken = System.currentTimeMillis() - startTime; String key = joinPoint.toString(); functionTimeCost.putIfAbsent(key, new AtomicLong()); functionTimes.putIfAbsent(key, new AtomicLong()); functionTimeCost.get(key).addAndGet(timeTaken); functionTimes.get(key).incrementAndGet(); return proceed; } }
接口查看
import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.*; @RestController public class FunctionController { @AllArgsConstructor @Getter private class Unit { private String key; private int count; private long timeCost; private double avgTimeCost; } @GetMapping("/stat/reset") public String reset() { FunctionAspect.functionTimes.clear(); FunctionAspect.functionTimeCost.clear(); return "ok"; } @GetMapping("/stat/fun") public String fun() { List<Unit> list = new ArrayList<>(); Set<String> keys = FunctionAspect.functionTimes.keySet(); keys.retainAll(FunctionAspect.functionTimeCost.keySet()); for (String key : keys) { list.add(new Unit( key, FunctionAspect.functionTimes.get(key).intValue(), FunctionAspect.functionTimeCost.get(key).get(), 1.0 * FunctionAspect.functionTimeCost.get(key).get() / FunctionAspect.functionTimes.get(key).intValue() )); } Collections.sort(list, Comparator.comparingLong(t -> -t.timeCost)); StringBuilder builder = new StringBuilder(); builder.append("method\ttime cost\tcount\tavg time cost\n"); list.forEach(m -> { builder.append(m.key).append("\t") .append(m.timeCost).append("\t") .append(m.count).append("\t") .append(m.avgTimeCost).append("\n"); }); return builder.toString(); } }