Spring事务增强

来源:互联网 发布:淘宝认证照片拍摄技巧 编辑:程序博客网 时间:2024/06/15 02:03

一:问题描述

1.这是具体的事务配置
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"/>    </bean>
 <tx:advice id="transactionAdvice" transaction-manager="transactionManager">        <tx:attributes>            <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>            <tx:method name="*" propagation="SUPPORTED" read-only="true"/>        </tx:attributes>    </tx:advice>     <aop:config>        <aop:pointcut id="transactionPointcut" expression="execution(* com.ph3636.*.service.*.*(..))"/>        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/>    </aop:config>
2.示例代码
包路径com.ph3636.test.service.Test1Service
@Servicepublic class Test1Service{
 @Autowired    private Test2Service test2Service;
     public void test(){
            test2Service.add();①
        add();
    }
    public void add(){
        test2Service.add();②
        int i = 1 / 0;//这里可以替换为一个更隐秘的获取数据库连接失败错误
        test3Service.add();③
    }

}
3.执行test方法后会成功执行①②,令人匪夷所思的是②为什么没有跟随这个错误一块回滚,而是自己单独提交成功?

二:事务解析

1.xml文件里面的那些方法正则表达式(add*)配置会随着Spring的IOC容器启动并且在Bean进行初始化的时候放出对应的类NameMatchTransactionAttributeSource的属性Map<String, TransactionAttribute> nameMap中,每个配置的方法的名字,隔离级别,事务传播属性都在TransactionAttribute中。

2.事务的aop处理会读取切点配置的类路径expression="execution(* com.ph3636.*.service.*.*(..))",以及需要的通知transactionAdvice也就是上面的方法处理,然后把这些处理组成一个事务拦截器TransactionInterceptor,最后形成一个代理。

3.因为这些Service类没有具体的实现接口,所以spring默认会用CglibAopProxy生成对应的代理类,然后等到执行目标方法时会经过回调Callback进而调用intercept方法开始执行具体的操作

4.创建代理的入口为bean初始化时遇到FactoryBean这个类型的类时会调用他ProxyFactoryBean的getObject方法

private synchronized Object getSingletonInstance() {if (this.singletonInstance == null) {this.targetSource = freshTargetSource();if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {// Rely on AOP infrastructure to tell us what interfaces to proxy.Class<?> targetClass = getTargetClass();if (targetClass == null) {throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");}setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));}// Initialize the shared singleton instance.super.setFrozen(this.freezeProxy);this.singletonInstance = getProxy(createAopProxy());}return this.singletonInstance;}
然后用默认的DefaultAopProxyFactory代理工厂创建代理类
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface()) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}
这里会判断适用那种方法创建代理类,Cglib还是JDK自带的,最后返回AopProxy。
5.根据这个aop代理CglibAopProxy创建具体的代理对象getProxy(createAopProxy());
public Object getProxy(ClassLoader classLoader) {
....
Callback[] callbacks = getCallbacks(rootClass);
...
return createProxyClassAndInstance(enhancer, callbacks);
}
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {// Choose an "aop" interceptor (used for AOP calls).Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);主要是这个
.....Callback[] mainCallbacks = new Callback[]{aopInterceptor, // for normal advicetargetInterceptor, // invoke target without considering advice, if optimizednew SerializableNoOp(), // no override for methods mapped to thistargetDispatcher, this.advisedDispatcher,new EqualsInterceptor(this.advised),new HashCodeInterceptor(this.advised)};                  ......Callback[] callbacks;                  ...callbacks = mainCallbacks;}return callbacks;}
这个就是具体的service类的代理类了。

6.开始最初的方法调用,会进入DynamicAdvisedInterceptor的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);}}}
this.advised.exposeProxy这个属性是本文的重点,是否暴露代理,默认是false。这个方法会获取具体的操作对象,以及getInterceptorsAndDynamicInterceptionAdvice家在这个切点的所有拦截器,最后组装成一个CglibMethodInvocation开始进行处理,retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();然后释放资源,恢复以前的的面貌,返回结果。

7.开始拦截器的调用proceed,先判断是否执行完全部的拦截器,是的话就执行目标方法

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) {............................}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);}}
8.这里主要介绍TransactionInterceptor
@Overridepublic Object invoke(final MethodInvocation invocation) throws Throwable {Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {@Overridepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}});}
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)throws Throwable {// If the transaction attribute is null, the method is non-transactional.final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = determineTransactionManager(txAttr);final String joinpointIdentification = methodIdentification(method, targetClass);if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);return retVal;}
.......
}
getTransactionAttributeSource().getTransactionAttribute(method, targetClass)从这里取出方法对应的事务配置信息
for (String mappedName : this.nameMap.keySet()) {if (isMatch(methodName, mappedName) &&(bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {attr = this.nameMap.get(mappedName);bestNameMatch = mappedName;}}
这里根据最少匹配规则选出xml对应的配置信息。
determineTransactionManager(txAttr)选择具体的事务管理器,也就是解析你使用的是Jta还是DataSource。
9.创建事务信息createTransactionIfNecessary,完成之后就会把当前事务信息TransactionInfo绑定到这个线程里(ThreadLocal)以供下次可能使用,
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {TransactionStatus status = null;if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);}
获取事务状态信息

public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {Object transaction = doGetTransaction();if (definition == null) {// Use defaults if no transaction definition given.definition = new DefaultTransactionDefinition();}if (isExistingTransaction(transaction)) {// Existing transaction found -> check propagation behavior to find out how to behave.return handleExistingTransaction(definition, transaction, debugEnabled);}else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {SuspendedResourcesHolder suspendedResources = suspend(null);if (debugEnabled) {logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);}try {boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}catch (RuntimeException ex) {resume(null, suspendedResources);throw ex;}catch (Error err) {resume(null, suspendedResources);throw err;}}else {// Create "empty" transaction: no actual transaction, but potentially synchronization.boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);}}
首先从doGetTransaction线程私有的ThreadLocal获取已经存在的事务对象DataSourceTransactionObject,然后判断有没有具体配置的方法事务配置DefaultTransactionDefinition,没有的话就是用默认的,然后判断isExistingTransaction这个事务对象中是否存在已有的事务链接信息,

10.下来就是事务传播级别起作用的时候了,

int PROPAGATION_REQUIRED = 0;支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
int PROPAGATION_SUPPORTS = 1;支持当前事务,如果当前没有事务,就以非事务方式执行。
int PROPAGATION_MANDATORY = 2;支持当前事务,如果当前没有事务,就抛出异常。
int PROPAGATION_REQUIRES_NEW = 3;新建事务,如果当前存在事务,把当前事务挂起。外层事务失败回滚的话里面新建的那个事务不会回滚
int PROPAGATION_NOT_SUPPORTED = 4;以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
int PROPAGATION_NEVER = 5;以非事务方式执行,如果当前存在事务,则抛出异常。
int PROPAGATION_NESTED = 6;支持当前事务,如果当前事务存在,则执行一个嵌套事务(外层事务失败回滚的话里面新建的那个事务也会回滚,因为他设置了保存点Savepoint),
如果当前没有事务,就新建一个事务。

根据这7个级别分别挂起使用事务。然后retVal = invocation.proceedWithInvocation();进行下一个拦截器的调用。DefaultTransactionStatus事

if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}

务状态有个特别重要的属性private final boolean newTransaction;它关系到是否提交现在这个事务,当方法具体执行完毕之后就会开始回滚或者提交事务信息,提交事务发生在AbstractPlatformTransactionManager的processCommit,这个值的具体设置在每个传播级别里面,所以就会影响到具体事务的提交。

                               else if (status.isNewTransaction()) {doCommit(status);}
三:原因解析

因为上面这些事务拦截器处理是针对一个具体代理的,但是最初的案例从别的地方开始执行Test1Service.test()时,他其实执行的是Test1Service的代理对象,所以就会执行上面的整个流程,但是刚开始的时候没有事务包裹,因为是PROPAGATION_SUPPORTS ,①执行的时候会自己建立一个新事务,完事之后就会提交,后面发生任何异常都不会影响它。等到执行到this.add()时也就会把他当做一个内部的普通方法调用,而不会把他当成一个具体的对象需要代理,但是如果执行到test2Service.add();②时候,就会执行test2Service的代理也就会创建一个完整的新事务,因为他已经显式表明了我就是test2Service代理对象,最后事务也就会提交成功,到达这个时候int i = 1 / 0;虽然会抛出一个错误,但是他的外层方法并没有包裹事务,而前面的②自己有一个单独的事务,所以他失败也不会影响到②。

四:解决办法

这种代码本身就是错误的,但是不排除有这种特殊的需求,如果是这样的话,spring事务处理设置了一个开关进行代理暴露  <aop:config expose-proxy="true"/>这个配置对应到

if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}
这里就会把正在执行的代理设置到与线程相关的ThreadLocal中,然后在代码里获取线程绑定的这个代理进行显式调用即可,((TestService) AopContext.currentProxy()).add();
这个时候当代吗执行到这里时,就不会把他当做内部的普通方法调用,而是把它作为一个单独完整的代理进行处理,这个时候他们也就会被包裹到一个事务,荣辱与共了。


原创粉丝点击