共计 2680 个字符,预计需要花费 7 分钟才能阅读完成。
这是小伙伴们在微信上问的一个问题:
这个问题比拟典型,让我想到面试时有一个 Spring 事务生效的问题,跟这个起因以及解决方案是截然不同的,因而,抽空整篇文章和小伙伴们分享下。
1. AOP 的原理
小伙伴们晓得,AOP 底层就是动静代理,动静代理有两种实现形式:
- JDK 动静代理:利用拦截器(必须实现 InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来解决。举个例子,假如有一个接口 A,A 有一个实现类 B,当初要给 B 生成代理对象,那么实际上是给 A 接口主动生成了一个匿名实现类,并且在这个匿名实现类中调用到 B 中的办法。
- CGLIB 动静代理:利用 ASM 框架,对代理对象类生成的 class 文件加载进来,通过批改其字节码生成子类来解决。举个例子,当初有一个类 A,A 没有接口,当初想给 A 生成一个代理对象,那么实际上是主动给 A 生成了一个子类,在这个子类中笼罩了 A 中的办法, 所以,小伙伴们要留神,A 类以及它里边的办法不能是 final 类型的,否则无奈生成代理 。
如果被代理的对象有接口,则能够应用 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 动静代理?](https://mp.weixin.qq.com/s/V3…)。
2. 理论用的类
基于第一大节的解说,小伙伴们晓得,当你在我的项目中用到了 AOP 之后,其实你所以见到的类,并不是本来的类了。
松哥后面写了好几篇 AOP 相干的文章,如下:
- 手把手教你玩多数据源动静切换!
- Redis 做接口限流,一个注解的事!
- 解决接口幂等性的两种常见计划 | 手把手教你
- 数据权限,一个注解搞定!
尽管是解决不同的问题,然而有一个独特的点,那就是都是通过自定义注解 + 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 办法上的注解就会生效。
3. 问题解决
这个问题存在于所有应用了 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 中要开启循环依赖能力实现),大家在理论开发中,还是要从设计上尽量避免这种问题。
好啦,这个问题搞明确了,那么事务生效这个问题,也不必我多说了吧!