一、定义注解类TimeLog
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeLog {
String value() default "";
}
二、定义切面AOP类
@Slf4j
@Aspect
@Component
public class TimeLogAspect {
private static final ThreadLocal<Long> firstThreadLocal = new ThreadLocal<>();
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
// 解决方法调用见清除 threadLocal 空指针
private static final ThreadLocal<Integer> countThreadLocal = new ThreadLocal<>();
@Pointcut("execution(* *(..)) && @annotation(timeLog )")
public void pointcut(TimeLog timeLog ) {
}
@Around(value = "pointcut(timeLog)", argNames="joinPoint, timeLog")
public Object doAround(ProceedingJoinPoint point, TimeLog timeLog) throws Throwable {
Long startTime = System.currentTimeMillis();
Object result = point.proceed();
// 获取方法的参数值
Method method = this.getMethod(point);
String methodName = method.toString();
Object[] args = point.getArgs();
EvaluationContext context = this.bindParam(method, args);
// 根据spel表达式获取值
Expression expression = parser.parseExpression(timeLog.value());
Object methodDesc= expression.getValue(context);
// 打印
Long endTime = System.currentTimeMillis();
Long costTime = endTime - startTime;
log.info("methodName: {}, methodDesc: {}, costTime {}ms",methodName,methodDesc,costTime);
return result;
}
/**
* 获取当前执行的方法
*
* @param pjp
* @return
* @throws NoSuchMethodException
*/
private Method getMethod(ProceedingJoinPoint point) throws NoSuchMethodException {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
Method targetMethod = point.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
return targetMethod;
}
/**
* 将方法的参数名和参数值绑定
*
* @param method 方法,根据方法获取参数名
* @param args 方法的参数值
* @return
*/
private EvaluationContext bindParam(Method method, Object[] args) {
//获取方法的参数名
String[] params = discoverer.getParameterNames(method);
//将参数名与参数值对应起来
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], args[len]);
}
return context;
}
}
三、Aspectj LoadTimeWeaving
1、创建aop.xml配置,放到/src/main/resources/META-INF目录下
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-XnoInline -Xset:weaveJavaxPackages=true -Xlint:ignore -verbose -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<!-- 只对指定包下的类进行编织 -->
<include within="test..*"/>
</weaver>
<aspects>
<!-- 使用指定的切面类进行编织 -->
<aspect name="xxx.TimeLogAspect"/>
</aspects>
</aspectj>
2、在程序启动时开启agent
@Slf4j
public class MyAspectjAgentInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>
@Override
public void initialize(ConfigurableApplicationContext context) {
//关键代码,用于在程序中开启agent
//通过bytebuddy拿到当前jvm的Instrumentation实例
Instrumentation instrumentation = ByteBuddyAgent.install();
//激活Aspectj的代理对象
Agent.agentmain("", instrumentation);
//激活spring代理对象
InstrumentationSavingAgent.agentmain("", instrumentation);
}
}
3、创建spring.factories,放到/src/main/resources/META-INF目录下
org.springframework.context.context.ApplicationContextInitializer=\
xxx.MyAspectjAgentInitializer
Maven关键依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.9</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5</version>
</dependency>
参考
- SpringBoot基于AOP 注解打印方法耗时
- SpringBoot自定义注解 + AOP 实现必填参数非空校验、接口传入参数和应答数据打印
- Spring技巧之程序方式开启Aspectj LoadTimeWeaving AOP
- SpringBoot中使用LoadTimeWeaving技术实现AOP功能
- 解决方案:Spring AOP 方法内部调用不生效
- AspectJ切面执行两次原因分析