面试必问的事务-2.5:JDK动态代理给Spring事务埋下的坑!

有时候我们会遇到这样一种问题:

service本类中方法调用另一个方法事务不生效问题,

但是我们已经在每个方法上都加了事务控制,为什么还是不生效呢?

这里就有一个点比较重要了,service本类方法中调用另外一个方法。

 

serviceImpl1{
   public A(){  //给A加事务
	   B();    //A调用B
   }

   public B(){
   }
}

serviceImpl2{
  public C(){
  }   

}

问题来了。

为什么A方法调用B方法,B的事务没有生效的原因:

我们知道spring事务管理是通过JDK动态代理的方式进行实现的(另一种是使用CGLib动态代理实现的),也正是因为动态代理的特性造成了上述方法中的事务失效!

那么动态代理的这个特性到底是什么才会造成Spring事务失效呢?

1:动态代理的特性到底是什么?

首先我们看一下一个简单的动态代理实现方式:

//接口
public interface OrderService{
	void test1();
	void test2();
}

//接口实现类
public class OrderServiceImpl implements OrderService{
	
	@Override
	public void test1(){
		System.out.println("--执行test1--");
	}
	
	@Override
	public void test2(){
		System.out.println("--执行test2--");
	}
}
//代理类
class OrderProxy implements InvocationHandler{
	private static final String METHOD_PREFIX = "test";
	
	private Object target;
	
	public OrderProxy(Object target){
		this.target=target;
	}
	
	@Override
	public Object invoke(Object object,Method method,Object[] args)throws Throwable{
		//我们使用这个标志来识别是否使用dialing还是使用方法本体
		if(method.getName().startsWith(METHOD_PREFIX)){
			System.out.println("=====分隔符=====");
		}
		return method.invoke(target, args);
	}
	
	public Object getProxy(){
		return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
				target.getClass().getInterfaces(), this);
	}
}
//测试方法
public class ProxyDemo{
	public static void main(String[] args) {
		OrderService orderService = new OrderServiceImpl();
		OrderProxy proxy = new OrderProxy(orderService);
		orderService = (OrderService)proxy.getProxy();
		orderService.test1();
		orderService.test2();
	}
}

此时我们执行以下测试方法,注意了此时是同时调用了test1()和test2()的,执行结果如下:

可以看出,在OrderServiceImpl 类中由于test1()没有调用test2(),他们方法的执行都是使用了代理的,也就是说test1和test2都是通过代理对象调用的invoke()方法。

我们模拟一下场景在test1()中调用test2(),那么代码修改为如下:

执行结果如下:

这里可以很清楚的看出来test1()走的是代理,而test2()走的是普通的方法,没有经过代理!看到这里你是否已经恍然大明白了呢?

这个应该可以很好的理解为什么是这样子!

这是因为在Java中test1()中调用test2()中的方法,本质上就相当于把test2()的方法体放入到test1()中,也就是内部方法,同样的不管你嵌套了多少层,只有代理对象proxy 直接调用的那一个方法才是真正的走代理的,如下:

测试方法和上边的测试方法一样,执行结果如下:

记住:只有代理对象proxy直接调用的那个方法才是真正的走代理的!

2:怎么解决动态代理导致的这个方法调用之间不走代理的问题?

上文的分析中我们已经了解了为什么在该特定场景下使用Spring事务的时候造成事务无法回滚的问题,下边我们谈一下几种解决的方法:

1、我们可以选择逃避这个问题!我们可以不使用以上这种事务嵌套的方式来解决问题,最简单的方法就是把问题提到Service或者是更靠前的逻辑中去解决,使用service.xxxtransaction是不会出现这种问题的。

2、通过AopProxy上下文获取代理对象:

(1)SpringBoot配置方式:注解开启 exposeProxy = true,暴露代理对象 (否则AopContext.currentProxy()) 会抛出异常。

添加依赖:

添加注解:

修改原有代码的执行方式为:

可见,child方法由于异常已经回滚了,而parent可以正确的提交,这才是我们想要的结果!注意的是在parent调用child的时候是通过try/catch捕获了异常的!

(2)传统Spring XML配置文件只需要添加依赖个设置如下配置即可,使用方式一样:

<aop:aspectj-autoproxy expose-proxy="true"/>

3、通过ApplicationContext上下文进行解决:

因此:

避免在一个Service内部进行事务方法的嵌套调用。

在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务

3:springAOP 同理,对事务的影响。

serviceImpl1{
   public A(){  给A加事务
	B();
    }

    public B(){
   }
}

serviceImpl2{
  public C(){
  }   
}

为什么A方法调用B方法,B的事务没有生效的原因:

也就是说我们首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。

简单的一个例子:

public interface AService {  
    public void a();  
    public void b();  
}  
   
@Service()  
public class AServiceImpl1 implements AService{  
    @Transactional(propagation = Propagation.REQUIRED)  
    public void a() {  
        this.b();  
    }  
    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void b() {  
    }  
}  

目标对象内部的自我调用将无法实施切面中的增强。

此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,

因此b方法的事务定义@Transactional(propagation = Propagation.REQUIRES_NEW)将不会实施.

(此处:如果不定义,会使用默认的传播行为:require 需要)

即结果是b和a方法的事务定义是一样的。

解决方案:

此处a方法中调用b方法时,只要通过AOP代理调用b方法即可走事务切面,即可以进行事务增强。

public void a() { 
   aopProxy.b();//即调用AOP代理对象的b方法即可执行事务切面进行事务增强 

 

修改我们的业务实现类

this.b();

-----------修改为--------->
((AService) AopContext.currentProxy()).b();

当然使用AopContext需要开启aop框架暴露该代理对象

springboot开启的方式是@EnableAspectJAutoProxy(,exposeProxy = true)

 

 

本文是搜集的网上一些好的说明,整理整合,帮助理解。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值