在同一个类的两个方法内部互相调用中,如何使 AOP 生效
熟悉 Spring AOP 的都知道,如果同一个类中的两个方法在内部互相调用,那么此时 AOP 是不会生效的,因为 Spring AOP 是通过代理类来实现的,而类内部的方法调用并不会走到代理对象。那么,有没有办法让内部调用的时候也让 AOP 生效呢?万能的 ChatGPT 告诉我,方法是有的,还有好几种。
使用 @Autowired
通过代理调用
这个方法的思路是,利用 @Autowired
注解注入自身的代理对象,然后通过代理对象完成方法调用。
1 |
|
这个操作我也在本地的一个 Spring Boot 项目中验证确实是可行的,前提是要在 application.properties
里面配置允许循环引用 spring.main.allow-circular-references=true
。而且这个操作有一个缺点,就是自身代理对象 demoService
必须通过字段注入的方式完成依赖注入,如果用构造方法注入,启动的时候就会报循环依赖错误导致项目无法成功启动(不用想也知道,在构造方法里面依赖自己肯定不行啊)。
我个人并不喜欢这个方法,一个原因是因为我不喜欢循环引用,另一个原因是我不喜欢字段注入,毕竟 Spring 早就推荐改成构造方法注入了。所以,我们继续看下一个方法。
使用 AopContext.currentProxy()
另一个方法是使用 AopContext#currentProxy()
静态方法获取到当前的代理对象,也就是对象自己,然后再通过这个代理对象进行方法调用。
1 |
|
但是如果就这样运行,你就会得到这样一条错误信息:
1 | java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context. |
看来 ChatGPT 没把话说全啊,好在我的 IDEA 里面装了通义灵码,把代码上下文和这个异常抛给它之后,它告诉我要通过注解 @EnableAspectJAutoProxy(exposeProxy = true)
配置 Spring AOP 允许暴露当前代理对象。那么按照它的说法,我给切面配置类加上这个注解:
1 |
|
再重启应用,就发现 demoService
里面的内部调用成功触发了 AOP 切面。
通义灵码还提醒我,在多线程环境中,要确保 AopContext#currentProxy()
必须在与 AOP 调用相同的线程中调用。此外,根据 currentProxy()
的 JavaDoc,调用它的方法也必须经过了 AOP 调用,否则会抛出 IllegalStateException
异常。点进 currentProxy()
方法的实现,发现它内部是用 ThreadLocal 来保存代理对象的,同时在这个类中还有一个 setCurrentProxy(Object)
方法来把当前的代理对象保存到 ThreadLocal 中。下断点调试后发现,setCurrentProxy(Object)
这个方法会先被执行,然后再走到我们实际调用的方法。这正好解释了为什么要注意在相同的线程中调用 AopContext#currentProxy()
,并且调用它的方法必须是经过了 AOP 调用的,因为不这样的话 ThreadLocal 中根本就没东西可拿。