Spring 中同类内部调用导致 AOP 失效的解决方案(如 @Transactional 失效)
这是 Spring 项目里非常常见、也很容易误判的一个坑:注解明明加了,但放到同类内部调用时却不生效。
现象
示例:
1 | public class A { |
这个问题在事务、缓存、权限校验等场景里都很常见。以上面代码为例,如果在 Controller 层直接调用 serviceA(),事务通常可以正常生效;但如果在同一个类的 serviceB() 内部直接调用 serviceA(),且 serviceB() 本身没有开启事务,那么此时 serviceA() 上的 @Transactional 实际上往往不会生效。
原因
想讲清这个问题就必须了解spring AOP的内部实现原理。
在spring中经常使用自定义注解或是spring已封装好的注解,通过AOP的方式复用代码、避免重复劳动。而spring实现AOP是通过动态代理来实现的,默认有接口的情况使用JDK的动态代理(也可以通过配置proxyTargetClass来指定使用CGLIB)、没有接口的情况使用CGLIB。
但无论是哪一种代理,都是在原有方法的外面包一层、在方法外的代理层来实现AOP的逻辑。以上面为例,在代理后会在serviceA()外层增加事务相关逻辑:
1 | //原始代码 |
但这个前提就是spring直接调用加了注解的方法才会调用代理方法,如果serviceA被同一个类内部其他方法调用,那servcieA()就只是一段普通代码、AOP相关的信息不会被spring看到,那自然就无法执行AOP的逻辑。
解决
知道原理后,自然也就好解决了。
AOP的逻辑在代理后的方法中,那么我们就去执行spring生成的代理后的方法。获取方法如下:
方法1:通过当前类对象获取
这个有点取巧。还是以上面serviceB()为例,我们如果调用this.serviceA(),this指向对象本身、不会指向代理后的对象,因此肯定不可以,但我们可以让spring提供给我们代理后的对象:
1 | public class A{ |
方法2 从ApplicationContext中直接获取
获取ApplicationContext有多种方法:
方法2.1 使用AopContext.currentProxy()
1 | public class A { |
使用AopContext.currentProxy()注意必须在程序启动时开启EnableAspectJAutoProxy注解,设置代理暴露方式为true,如下面所示:
1 | /** |
方法2.2 使用ApplicationContextAware
通过spring生命周期,直接将ApplicationContext注入进来:
1 | public class A implements ApplicationContextAware { |