在最近项目开发中遇到一个问题,平时也没有太注意,但是最近有一个导入功能相对比较复杂,结果就暴漏出了我们今天要讨论的问题,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>
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>
<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,简单如下:
假如都对这两个方法进行拦截。当你调用,B对象.foo1()仅仅对foo1整个方法拦截,对于它内部调用的foo2()方法不会进行拦截。
源码分析:
判断上述this.foo2()方法是否被拦截的最本质的东西是看this到底是谁?有如下对象B类的对象b,和cglib生成的代理对象bProxy,代理对象bProxy内部拥有b。如果调用b对象的任何方法,肯定不会发生任何拦截,当调用bProxy的方法则都会进入拦截函数。
当我们调用bProxy对象的foo1()方法时,先执行cglib之前设置的callback对象的intercept拦截函数,如下:
这个过程之前的文章已经分析过,这里就是首先取出拦截器链List<Object> chain,当foo1方法不符合我们所配置的pointcut时,拦截器链必然为空,然后就是直接执行目标对象的方法。
当foo1方法符合所配置的pointcut时,拦截器链不为空,执行相应的通知advice,currentInterceptorIndex 从-1开始,如下:
随着通知不断的传递执行,最终this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1将会满足条件,将会来到执行目标对象的方法invokeJoinpoint():
在这里不管要拦截的目标方法是不是public方法,最终所传递的对象都是this.target,他是目标对象而不是代理对象,即执行上述foo1()函数的对象是目标对象而不是代理对象,所以它内部所调用的this.foo2()也是目标对象,因此不会发生拦截,如果是执行的是代理对象.foo2()则必然会进入intercept拦截过程。所以上述调用foo1函数,其内部调用的foo2函数是不会发生拦截的,因为this指的是目标对象,不是代理对象。
如果你想实现foo1调用时内部的foo2也进行拦截,就必须把this换成代理对象。这时就要用到了,xml配置中的expose-proxy="true",即暴露出代理对象,它使用的是ThreadLocal设计模式,我们可以这样获取代理对象,AopContext.currentProxy()就是代理对象,然后转换成目标对象或者目标接口,执行相应的方法:
(1)SpringAOP对于最外层的函数只拦截public方法,不拦截protected和private方法,另外不会对最外层的public方法内部调用的其他方法也进行拦截,即只停留于代理对象所调用的方法。如下案例:
B类有两个public方法,foo1()和foo2(),foo1内部调用了foo2,简单如下:
- public void foo2() {
- System.out.println("foo2");
- }
- public void foo1(){
- System.out.println("foo1");
- this.foo2();
- }
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拦截函数,如下:
- 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);
- }
- }
- }
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开始,如下:
- 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);
- }
- }
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():
- protected Object invokeJoinpoint() throws Throwable {
- if (this.publicMethod) {
- return this.methodProxy.invoke(this.target, this.arguments);
- }
- else {
- return super.invokeJoinpoint();
- }
- }
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()就是代理对象,然后转换成目标对象或者目标接口,执行相应的方法:
- public void foo1(){
- System.out.println("run foo1");
- BServiceImpl proxy=(BServiceImpl) AopContext.currentProxy();
- proxy.foo2();
- }