SpringBoot下使用aspectJ进行性能诊断

  |   0 评论   |   0 浏览

背景

SpringBoot中的性能诊断。

本文分两个部分。先介绍SpringBoot-AOP方法,其使用方便,但是存在两个问题:

  1. 扩展性:不支持private方法,不支持本类内部调用的其他方法进行切面。
  2. 性能:继承、实现了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();
    }
}

参考

  1. 猫头鹰的深夜翻译:使用SpringBoot和AspectJ实现AOP
  2. spring 如何实现原生的 aspectj 支持
  3. [aspectj@github]https://github.com/dsyer/spring-boot-aspectj)
  4. AspectJ 1.8.12 Readme