Spring AOP 动态代理机制在不同版本中的差异
这里仅作为备忘点介绍一下, 大多数情况下在使用的时候并不会关注到.
- Spring 5.x 版本开始, AOP 默认依旧使用 JDK 动态代理, 并非网上说的默认改成了 CGLIB.
- SpringBoot 2.x 开始, 由于使用 JDK 动态代理可能导致的类型转化异常问题, 默认使用 CGLIB.
- SpringBoot 2.x 中, 如果需要默认使用 JDK 动态代理可以通过配置项
spring.aop.proxy-target-class=false
来进行修改,proxyTargetClass 配置已无效.
作者: 磊叔的技术博客
链接:https://juejin.cn/post/6986112993259945992
JDK 动态代理可能导致的类型转化异常问题
看到上文的作者提到了这个问题, 我有点好奇, 这个类型转化的问题会在什么场景下触发.
Spring AOP 代理机制
Spring 代理机制造成的.
简单的说, 就是通过 Spring 容器获取的类对象, 很多情况下并不是原类, 而是被 Spring 修饰过了的代理类.
例如你执行 A 类对象的方法 A.invoke(), 而 spring 对 A 类做了修饰, 实际你运行的是 spring 修饰过的代理类 proxyAbean.invoke()
方法.
这样就会造成一个问题, 如果你在 invoke()
中调用 A 类的其余方法 invoke2()
, 此时 invoke2()
是直接调用的原类的 A.invoke2()
, 而不是代理类 proxyAbean.invoke2()
,Spring 对方法做的修饰增强 (@Async
,@Transational
,AOP) 全部不会实现.
有意思的问题
问题 1: 调用 saveInit()
方法, 下面的代码插入了几条数据?
/**
* @author wangzhi
*/
@Service
public class TransactionService {
@Autowired
private CourseMapper courseMapper;
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void save() {
CourseEntity course = new CourseEntity();
course.setCourseName("语文");
course.setPrice(new BigDecimal(100));
courseMapper.insert(course);
//故意制造异常
System.out.println(1/0);
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void saveInit() {
CourseEntity course = new CourseEntity();
course.setCourseName("数学");
course.setPrice(new BigDecimal(100));
courseMapper.insert(course);
//调用 save 方法
try{
save();
}catch (Exception e){
e.printStackTrace();
}
}
}
问题 2: save()
的事务传播改成 Request_NEW
, 那么会插入几条数据呢?
/**
* @author wangzhi
*/
@Service
public class TransactionService {
@Autowired
private CourseMapper courseMapper;
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void save() {
CourseEntity course = new CourseEntity();
course.setCourseName("语文");
course.setPrice(new BigDecimal(100));
courseMapper.insert(course);
//故意制造异常
System.out.println(1/0);
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void saveInit() {
CourseEntity course = new CourseEntity();
course.setCourseName("数学");
course.setPrice(new BigDecimal(100));
courseMapper.insert(course);
//调用 save 方法
try {
save();
}catch (Exception e){
e.printStackTrace();
}
}
}
以上来源: https://blog.csdn.net/qq_42290561/article/details/105913690
答案都是插入了两条数据, 想一想 Spring 动态代理在这个地方的坑吧, 抓住要点, 谁是切面以及切面有没有切进去.
注解@EnableAspectJAutoProxy(exposeProxy = true)
@EnableAspectJAutoProxy(exposeProxy = true)
TransactionService proxy = (TransactionService) AopContext.currentProxy();
/**
* ThreadLocal holder for AOP proxy associated with this thread.
* Will contain {@code null} unless the "exposeProxy" property on
* the controlling proxy configuration has been set to "true".
* @see ProxyConfig#setExposeProxy
*/
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<Object>("Current AOP proxy");
这个地方就是存放 AOP 代理对象的, 不是真实的对象.
/**
* Try to return the current AOP proxy. This method is usable only if the
* calling method has been invoked via AOP, and the AOP framework has been set
* to expose proxies. Otherwise, this method will throw an IllegalStateException.
* @return Object the current AOP proxy (never returns {@code null})
* @throws IllegalStateException if the proxy cannot be found, because the
* method was invoked outside an AOP invocation context, or because the
* AOP framework has not been configured to expose the proxy
*/
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException(
"Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}
currentProxy()
试图返回当前的 AOP 代理对象. 这个方法只能被 AOP 环绕的中的方法调用, 并且 AOP 框架还要设置为可以暴露代理才行. 否则, 这个方法将会抛出 IllegalStateException
异常.
我在 IDEA 中点了一下方法 StaticUnadvisedExposedInterceptor#intercept
, 发现了这个有意思的代码, 证实了我的猜想: 即每次切进去 AOP 的时候都会把上一个的 AOP 对象给弹出来, 并且将新的 AOP 对象设置进去, 然后等新的 AOP 执行完了, 再把旧的 AOP 对象设置进去.
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(this.target, args);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
AopContext.setCurrentProxy(oldProxy);
}
}
这个是否暴露 AOP 对象的开关位置在:
public class ProxyConfig implements Serializable {
/** use serialVersionUID from Spring 1.2 for interoperability */
private static final long serialVersionUID = -8409359707199703185L;
private boolean proxyTargetClass = false;
private boolean optimize = false;
boolean opaque = false;
// here!!!
boolean exposeProxy = false;
private boolean frozen = false;
}
然后在这里进行判断的:
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
AopContext 还有没有其他用途?
使用这个工具类的时候, 还需要关注另一个问题, 就是当在跨线程, 或者使用线程池的情况下, 需要手动将 proxy 透传到新的线程中, 否则, 即使开始 exposeProxy = true
, 同样也会出现 Cannot find current proxy: Set 'exposeProxy'
报错.
链接:https://juejin.cn/post/6986112993259945992
本文链接地址:Spring事务和动态代理,英雄不问来路,转载请注明出处,谢谢。
有话想说:那就赶紧去给我留言吧。
文章评论