一:问题描述
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
@Service
public 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 advice
targetInterceptor, // invoke target without considering advice, if optimized
new SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, 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
@Override
public 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() {
@Override
public 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 exception
completeTransactionAfterThrowing(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,这个值的具体设置在每个传播级别里面,所以就会影响到具体事务的提交。
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();
这个时候当代吗执行到这里时,就不会把他当做内部的普通方法调用,而是把它作为一个单独完整的代理进行处理,这个时候他们也就会被包裹到一个事务。