spring事物失效分析及解决方案

在最近项目开发中遇到一个问题,平时也没有太注意,但是最近有一个导入功能相对比较复杂,结果就暴漏出了我们今天要讨论的问题,spring失效的问题。
首先在我们的在我们项目配置事物:
<!--事物拦截器-->
<bean id='transactionInterceptor'
  class='org.springframework.transaction.interceptor.TransactionInterceptor'>
  <property name="transactionManager" ref="jdbcTransactionManager" />
  <property name="transactionAttributes">
   <props>
    <prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
    <prop key="create*">PROPAGATION_REQUIRED,-Exception</prop>
    <prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>
    <prop key="dele*">PROPAGATION_REQUIRED,-Exception</prop>
    <prop key="get*">PROPAGATION_REQUIRED,readOnly,-Exception</prop>
    <prop key="findEntitiesForPag*">PROPAGATION_REQUIRED,readOnly,-Exception</prop>
    <prop key="copy*">PROPAGATION_REQUIRED,readOnly,-Exception</prop>
   </props>
  </property>
 </bean>
<!-- 自动代理 -->
 <bean id='autoproxy'
  class='org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator'>
  <property name="beanNames">
   <list>
    <value>*Delegate</value>
   </list>
  </property>
  <property name="interceptorNames">
   <list>
    <value>transactionInterceptor</value>
   </list>
  </property>
 </bean>
在我的项目中需要有这样一个业务,通过读取excel信息,然后添加计划信息,计划信息及日志信息,大概业务是这样的比如

public void saveInfo(){
   ......读取excel信息

   addPlanAndLog();

   ...其他逻辑

}

public void addPlanAndLog(){
    savePLan();
     .......
    saveLog();
  .....
}

在我们保存计划成功但是日志失败,事物并不会回滚,这是一个很典型的事物控制问题,其实事物他是通过代理及反射实现的,在spring事物源码中我们可以得知,事物拦截他是通过一系列的拦截器进行拦截处理,然后通过反射实现目标方法的执行。所以代理很重要,上述问题失效的原因是我们在saveInfo方法(关注点)拦截之后,cglib进行了目标方法的代理,那么这个aop代理类中有我们的目标对象,所以目标对象调用我们的savelog方法并没有进行事物控制,也就是说只是一个普通类的调用,如果想要同一事物操作,就一个该被我们的代理类进行方法调用,那么需要我们的代理类进行显示获取就是暴漏出来,通过配置 expose-proxy="true"然后在我们业务中通过AOP上下文获取 AopContext.currentProxy();另外注意我们的方法需要使用public修饰。具体参考:
SpringAOP要注意的地方有很多,下面就举一个,之后想到了再列出来:
(1)SpringAOP对于最外层的函数只拦截public方法,不拦截protected和private方法,另外不会对最外层的public方法内部调用的其他方法也进行拦截,即只停留于代理对象所调用的方法。如下案例:
B类有两个public方法,foo1()和foo2(),foo1内部调用了foo2,简单如下:

Java代码   收藏代码
  1. public void foo2() {    
  2.         System.out.println("foo2");    
  3.     }    
  4.       
  5.     public void foo1(){  
  6.         System.out.println("foo1");  
  7.         this.foo2();  
  8.     }  
public void foo2() {  
        System.out.println("foo2");  
    }  
    
    public void foo1(){
    	System.out.println("foo1");
    	this.foo2();
    }

假如都对这两个方法进行拦截。当你调用,B对象.foo1()仅仅对foo1整个方法拦截,对于它内部调用的foo2()方法不会进行拦截。
源码分析:
判断上述this.foo2()方法是否被拦截的最本质的东西是看this到底是谁?有如下对象B类的对象b,和cglib生成的代理对象bProxy,代理对象bProxy内部拥有b。如果调用b对象的任何方法,肯定不会发生任何拦截,当调用bProxy的方法则都会进入拦截函数。

当我们调用bProxy对象的foo1()方法时,先执行cglib之前设置的callback对象的intercept拦截函数,如下:

Java代码   收藏代码
  1. public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
  2.             Object oldProxy = null;  
  3.             boolean setProxyContext = false;  
  4.             Class<?> targetClass = null;  
  5.             Object target = null;  
  6.             try {  
  7.                 if (this.advised.exposeProxy) {  
  8.                     // Make invocation available if necessary.  
  9.                     oldProxy = AopContext.setCurrentProxy(proxy);  
  10.                     setProxyContext = true;  
  11.                 }  
  12.                 // May be null. Get as late as possible to minimize the time we  
  13.                 // "own" the target, in case it comes from a pool...  
  14.                 target = getTarget();  
  15.                 if (target != null) {  
  16.                     targetClass = target.getClass();  
  17.                 }  
  18.                 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);  
  19.                 Object retVal;  
  20.                 // Check whether we only have one InvokerInterceptor: that is,  
  21.                 // no real advice, but just reflective invocation of the target.  
  22.                 if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {  
  23.                     // We can skip creating a MethodInvocation: just invoke the target directly.  
  24.                     // Note that the final invoker must be an InvokerInterceptor, so we know  
  25.                     // it does nothing but a reflective operation on the target, and no hot  
  26.                     // swapping or fancy proxying.  
  27.                     retVal = methodProxy.invoke(target, args);  
  28.                 }  
  29.                 else {  
  30.                     // We need to create a method invocation...  
  31.                     retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();  
  32.                 }  
  33.                 retVal = processReturnType(proxy, target, method, retVal);  
  34.                 return retVal;  
  35.             }  
  36.             finally {  
  37.                 if (target != null) {  
  38.                     releaseTarget(target);  
  39.                 }  
  40.                 if (setProxyContext) {  
  41.                     // Restore old proxy.  
  42.                     AopContext.setCurrentProxy(oldProxy);  
  43.                 }  
  44.             }  
  45.         }  
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;
				}
				// May be null. Get as late as possible to minimize the time we
				// "own" the target, in case it comes from a pool...
				target = getTarget();
				if (target != null) {
					targetClass = target.getClass();
				}
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// Check whether we only have one InvokerInterceptor: that is,
				// no real advice, but just reflective invocation of the target.
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					// We can skip creating a MethodInvocation: just invoke the target directly.
					// Note that the final invoker must be an InvokerInterceptor, so we know
					// it does nothing but a reflective operation on the target, and no hot
					// swapping or fancy proxying.
					retVal = methodProxy.invoke(target, args);
				}
				else {
					// We need to create a method invocation...
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null) {
					releaseTarget(target);
				}
				if (setProxyContext) {
					// Restore old proxy.
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}

这个过程之前的文章已经分析过,这里就是首先取出拦截器链List<Object> chain,当foo1方法不符合我们所配置的pointcut时,拦截器链必然为空,然后就是直接执行目标对象的方法。
当foo1方法符合所配置的pointcut时,拦截器链不为空,执行相应的通知advice,currentInterceptorIndex 从-1开始,如下:

Java代码   收藏代码
  1. public Object proceed() throws Throwable {  
  2.         //  We start with an index of -1 and increment early.  
  3.         if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {  
  4.             return invokeJoinpoint();  
  5.         }  
  6.   
  7.         Object interceptorOrInterceptionAdvice =  
  8.                 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);  
  9.         if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {  
  10.             // Evaluate dynamic method matcher here: static part will already have  
  11.             // been evaluated and found to match.  
  12.             InterceptorAndDynamicMethodMatcher dm =  
  13.                     (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;  
  14.             if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {  
  15.                 return dm.interceptor.invoke(this);  
  16.             }  
  17.             else {  
  18.                 // Dynamic matching failed.  
  19.                 // Skip this interceptor and invoke the next in the chain.  
  20.                 return proceed();  
  21.             }  
  22.         }  
  23.         else {  
  24.             // It's an interceptor, so we just invoke it: The pointcut will have  
  25.             // been evaluated statically before this object was constructed.  
  26.             return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);  
  27.         }  
  28.     }  
public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

随着通知不断的传递执行,最终this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1将会满足条件,将会来到执行目标对象的方法invokeJoinpoint():
Java代码   收藏代码
  1. protected Object invokeJoinpoint() throws Throwable {  
  2.             if (this.publicMethod) {  
  3.                 return this.methodProxy.invoke(this.target, this.arguments);  
  4.             }  
  5.             else {  
  6.                 return super.invokeJoinpoint();  
  7.             }  
  8.         }  
protected Object invokeJoinpoint() throws Throwable {
			if (this.publicMethod) {
				return this.methodProxy.invoke(this.target, this.arguments);
			}
			else {
				return super.invokeJoinpoint();
			}
		}

在这里不管要拦截的目标方法是不是public方法,最终所传递的对象都是this.target,他是目标对象而不是代理对象,即执行上述foo1()函数的对象是目标对象而不是代理对象,所以它内部所调用的this.foo2()也是目标对象,因此不会发生拦截,如果是执行的是代理对象.foo2()则必然会进入intercept拦截过程。所以上述调用foo1函数,其内部调用的foo2函数是不会发生拦截的,因为this指的是目标对象,不是代理对象。

如果你想实现foo1调用时内部的foo2也进行拦截,就必须把this换成代理对象。这时就要用到了,xml配置中的expose-proxy="true",即暴露出代理对象,它使用的是ThreadLocal设计模式,我们可以这样获取代理对象,AopContext.currentProxy()就是代理对象,然后转换成目标对象或者目标接口,执行相应的方法:

Java代码   收藏代码
  1. public void foo1(){  
  2.         System.out.println("run foo1");  
  3.         BServiceImpl proxy=(BServiceImpl) AopContext.currentProxy();  
  4.         proxy.foo2();  
  5.     }  


猜你喜欢

转载自blog.csdn.net/xiaocai9999/article/details/79774161