这是小伙伴们在微信上问的一个问题:
这个问题比较典型,让我想到面试时有一个 Spring 事务失效的问题,跟这个原因以及解决方案是一模一样的,因此,抽空整篇文章和小伙伴们分享下。
小伙伴们知道,AOP 底层就是动态代理,动态代理有两种实现方式:
如果被代理的对象有接口,则可以使用 JDK 动态代理,没有接口就可以使用 CGLIB 动态代理。
在 Spring 中,默认情况下,如果被代理的对象有接口,就使用 JDK 动态代理,如果被代理的对象没有接口,则使用 CGLIB 动态代理。
在 Spring Boot 中,2.0 之前也跟 Spring 中的规则一样,2.0 之后则统一都使用 CGLIB 动态代理。
不过这些都是默认的规则,如果有接口,但是你又希望使用 CGLIB 动态代理,通过修改配置,也都是可以实现的:
如果是 XML 配置,想使用 CGLIB 动态代理,可以按如下方式实现:
<aop:config proxy-target-class="true"> <aop:pointcut id="pc1" expression="。。。"/> <aop:aspect ref="logAdvice"> 。。。 </aop:aspect> </aop:config>
如果是 Java 配置,想使用 CGLIB 动态代理,可以按如下方式实现:
@Component @Aspect @EnableAspectJAutoProxy(proxyTargetClass = true) public class LogAspect { }
当然,在新版 Spring Boot 项目中,有接口的类默认就是使用 CGLIB 动态代理的。但是此时如果有接口的类你又想使用 JDK 动态代理,那么可以通过如下配置:
spring.aop.proxy-target-class=false
关于 Spring Boot 中的 AOP 代理问题,可以参考去年松哥写的文章:
Spring Boot 中的 AOP,到底是 JDK 动态代理还是 Cglib 动态代理?。
基于第一小节的讲解,小伙伴们知道,当你在项目中用到了 AOP 之后,其实你所以见到的类,并不是原本的类了。
松哥前面写了好几篇 AOP 相关的文章,如下:
虽然是解决不同的问题,但是有一个共同的点,那就是都是通过自定义注解+ AOP 解决问题的。
现在我就以手把手教你玩多数据源动态切换!为例,来和大家说说这里的动态代理到底是咋回事,没看过这篇文章的小伙伴可以先看下。
小伙伴们看下,我的 UserService 大致上是下面这样:
@Service public class UserService { @Autowired UserMapper userMapper; @DS("master") public Integer count() { return userMapper.getCount(); } }
小伙伴们看到,count() 方法上加了 @DS 注解,所以这个 count() 方法将来是要被自动代理的。换言之,当你在另外一个类中注入 UserService 的时候,其实不是这个 UserService,我 DEBUG 小伙伴们来看一下:
小伙伴们从图中可以看到,此时我注入的 UserService 并不是真正的 UserService,而是一个通过 CGLIB 动态代理为 UserService 生成的子类,这个子类里边的 count 方法大致逻辑类似下面这样(其实就是 AOP 中的代码,具体小伙伴们可以参考 手把手教你玩多数据源动态切换!一文):
# 切换数据源 # 去数据库查询 count # 清空 ThreadLocal 中的变量 # ...
但是,如果我的调用逻辑是这样呢:
@Service public class UserService { @Autowired UserMapper userMapper; public Integer count2() { return count(); } @DS("master") public Integer count() { return userMapper.getCount(); } }
小伙伴们来看,count2 方法,这个时候直接在 count2 方法中调用了 count 方法,当然,count2() 方法中的调用也可以写作 this.count();
,这样看起来就更明确了,我们调用 count 方法,使用的是当前对象,而当前对象是不包含代理对象中的代码的,我们通过 DEBUG 来看下:
所以,当我们在 count2 中直接调用 count 方法的时候,那么加在 count 方法上的注解就会失效。
这个问题存在于所有使用了 AOP 的地方,存在的原因第二小节已经分析的很清楚了。
解决办法其实也有很多种,最为简单省事的一种,就是在当前类中注入代理对象,然后通过代理对象去调用其他方法,如下:
@Service public class UserService { @Autowired UserMapper userMapper; @Autowired UserService userService; public Integer count2() { return userService.count(); } @Transactional @DS("master") public Integer count() { return userMapper.getCount(); } }
虽然问题解决了,不过这毕竟不是一个好的解决办法(因为自己中注入自己,在新版 Spring Boot 中要开启循环依赖才能实现),大家在实际开发中,还是要从设计上尽量避免这种问题。
好啦,这个问题搞明白了,那么事务失效这个问题,也不用我多说了吧!