在TestComponent中有task1和task2两个方法,都用了TaskMonitor注解。其中task1中调用了task2的方法,在controller测试勒中调用了task1方法。预期的结果是task1和task2都会记录运行时间,但是测试结果只记录了task1的运行时间。想请问这是为什么,也想知道如何修改才能同时输出task1和task2的运行时间。ps:将task1和task2放在不同的bean中,然后通过bean调用确实可以同时输出task1和task2的记录时间,但是我不想这么做。求其他解决方案。
注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TaskMonitor {
String taskName() default "";
}
Aspect类
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Component
@Order(-1)
@Slf4j
public class TaskMonitorAspect {
@Pointcut("execution(@com.TaskMonitor * *(..))")
private void timeMonitor() {}
@Around("timeMonitor()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
TaskMonitor monitor = method.getAnnotation(TaskMonitor.class);
String splitLine = "=========================";
log.info(String.format("%s开始执行任务%s%s: ", splitLine, monitor.taskName(), splitLine));
Object result = joinPoint.proceed(joinPoint.getArgs());
stopWatch.stop();
log.info(String.format("运行时间: %sms",stopWatch.getTotalTimeMillis()));
log.info(String.format("%s%s任务执行结束%s: ", splitLine, monitor.taskName(), splitLine));
return result;
} catch (Throwable e) {
throw e;
}
}
}
测试类
@Component
public class TestComponent {
@Autowired TestComponent2 testComponent2;
@TaskMonitor(taskName = "任务1")
public void task1() {
try {
Thread.sleep(500);
this.task2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@TaskMonitor(taskName = "任务2")
public void task2() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@RestController
public class TestController {
@Autowired TestComponent testComponent;
@GetMapping("/test")
public String test(){
testComponent.task1();
return "SUCCESS";
}
}
原因:
spring在启动时,扫描TestComponent类会生成这个类的代理类。那么在调用TestComponent类中的task1的时候,实际上是通过代理类先对task1方法进行了切面处理(也就是先执行切面中的逻辑)。
但当task1里调用task2的时候,属于类里面的内部调用,而并非aop切面的代理对象调用,所以这时候切面注解失效
解决:
这切面方法嵌套调用时,在调用task2的时候,需要使用 AopContext.currentProxy() 获取当前的代理对象,然后使用代理对象调用方法task2。
@TaskMonitor(taskName = "任务1")
public void task1() {
try {
Thread.sleep(500);
TestComponent testComponent = (TestComponent) AopContext.currentProxy();
testComponent .task2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
aop其实是一种装饰模式,你添加的增强功能在装饰类里面,你必须访问装饰类才能生效(你依赖注入进来的实例就是装饰实例)。而你task1调用task2是直接this.,这种会绕过装饰类,直接调用他自己,所以达不到你要的效果。类似的其他spring注解不生效也可能是类似的问题,比如事物注解。
随便看看任何说aop的文章都会给你 说aop的失效场景
你这么写,task2就相当于task1中的代码块,代理只会代理task1,上面已经给出了处理方案
原因就不说了,上面的楼主都已经说明了,一个简单的做法:
@Component
public class TestComponent {
@Autowired
TestComponent testComponent;
@TaskMonitor(taskName = "任务1")
public void task1() {
try {
Thread.sleep(500);
testComponent.task2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@TaskMonitor(taskName = "任务2")
public void task2() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
没有走spring boot,直接this是不走代理的,需要用aopcontext 或 注入自己 ,注入自己时候需要注意Springboot版本,低版本需要lazy
代理有问题,aop切面拿不到
代理失效了,调用方法前用 AopContext.currentProxy() 重新获取当前的代理对象试试