@Transaction注解哪些情况不生效?

2021-11-04 16:20:31 浏览数 (1)

看一个最简单的CGLIB的例子,感受一下AOP是如何做到的?

代码语言:javascript复制
/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 上午9:25
 */
public class Target {
    public String execute() {
        String message = "----------test()----------";
        System.out.println(message);
        return message;
    }
}

/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 上午9:25
 */
public class MyMethodInterceptor  implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(">>>MethodInterceptor start...");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println(">>>MethodInterceptor ending...");
        return "haha";
    }
}

/**
 * Created with vernon-test
 * Description:
 * User: chenyuan
 * Date: 16/4/25
 * Time: 上午9:28
 */
public class CglibProxyTest {

    public Object createProxy(Class targetClass) {
       // 第一步
       Enhancer enhancer = new Enhancer();
       // 第二步
       enhancer.setSuperclass(targetClass);
       // 第三步
       enhancer.setCallback(new MyMethodInterceptor());
       // 第四步
       return enhancer.create();
    }

    public static void main(String rags[]) {
        CglibProxyTest cglibProxyTest = new CglibProxyTest();
        Target proxyTarget = (Target) cglibProxyTest.createProxy(Target.class);
        String res = proxyTarget.execute();
        System.out.println(res);
    }

}

执行后的结果显示

代码语言:javascript复制
Connected to the target VM, address: '127.0.0.1:55868', transport: 'socket'
>>>MethodInterceptor start...
----------test()----------
>>>MethodInterceptor ending...
haha
Disconnected from the target VM, address: '127.0.0.1:55868', transport: 'socket'

实际上在执行execute()的前后就各自做了自己想要的操作。其实这个就是Spring AOP对简单的一个原型。

@Transaction的工作原理

在Spring中TransactionInterceptorPlatformTransactionManager这两个类是整个事务模块的核心,TransactionInterceptor负责拦截方法执行,进行判断是否需要提交或者回滚事务。PlatformTransactionManager是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。我们重点研究下这两个类的源码。

TransactionInterceptor类中的代码有很多,我简化一下逻辑,方便说明:

代码语言:javascript复制
//以下代码省略部分内容
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取事务调用的目标方法
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
//执行带事务调用
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

其实,这里也就是因为用到了动态代理。在事务回滚这个动作的前前后后可以做自己想要的东西。这是一个非常重要的设计思想。如果我们自己要写框架,这个模式可以作为你的第一参考。

基于注解的实现机制
  • 调用注解方法
  • 生成代理对象 - CglibAopProxy 调用内部类的方法DynamicAdvisedInterceptor.intercept()
  • TransactionInterceptor.invoke()拦截器拦截,在目标方法执行之前创建并加入事务
  • AbstractPlatformTransactionManager抽象事务管理器操作数据源DataSource提交或回滚事务

我们看了解一下它是如何仿照最上面的code来写的?

仿照上面Demo的第一步:

代码语言:javascript复制
public Object getProxy(@Nullable ClassLoader classLoader) {
  if (logger.isTraceEnabled()) {
   logger.trace("Creating CGLIB proxy: "   this.advised.getTargetSource());
  }

  try {
   Class<?> rootClass = this.advised.getTargetClass();
   Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

   Class<?> proxySuperClass = rootClass;
   if (ClassUtils.isCglibProxyClass(rootClass)) {
    proxySuperClass = rootClass.getSuperclass();
    Class<?>[] additionalInterfaces = rootClass.getInterfaces();
    for (Class<?> additionalInterface : additionalInterfaces) {
     this.advised.addInterface(additionalInterface);
    }
   }

   // Validate the class, writing log messages as necessary.
   validateClassIfNecessary(proxySuperClass, classLoader);

   // 第一点:Configure CGLIB Enhancer...
   Enhancer enhancer = createEnhancer();
   if (classLoader != null) {
    enhancer.setClassLoader(classLoader);
    if (classLoader instanceof SmartClassLoader &&
      ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
     enhancer.setUseCache(false);
    }
   }
      // 第二点:setSuperclass
   enhancer.setSuperclass(proxySuperClass);
   enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
   enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
   enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
   // 第三点:获取Callback
   Callback[] callbacks = getCallbacks(rootClass);
   Class<?>[] types = new Class<?>[callbacks.length];
   for (int x = 0; x < types.length; x  ) {
    types[x] = callbacks[x].getClass();
   }
   // fixedInterceptorMap only populated at this point, after getCallbacks call above
   enhancer.setCallbackFilter(new ProxyCallbackFilter(
     this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
   enhancer.setCallbackTypes(types);

   // Generate the proxy class and create a proxy instance.
   return createProxyClassAndInstance(enhancer, callbacks);
  }
  catch (CodeGenerationException | IllegalArgumentException ex) {
   throw new AopConfigException("Could not generate CGLIB subclass of "   this.advised.getTargetClass()  
     ": Common causes of this problem include using a final class or a non-visible class",
     ex);
  }
  catch (Throwable ex) {
   // TargetSource.getTarget() failed
   throw new AopConfigException("Unexpected AOP exception", ex);
  }
 }

仿照上面Demo的第三步:

代码语言:javascript复制
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
  // Parameters used for optimization choices...
  boolean exposeProxy = this.advised.isExposeProxy();
  boolean isFrozen = this.advised.isFrozen();
  boolean isStatic = this.advised.getTargetSource().isStatic();

  // Choose an "aop" interceptor (used for AOP calls).
  Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

  // Choose a "straight to target" interceptor. (used for calls that are
  // unadvised but can return this). May be required to expose the proxy.
  Callback targetInterceptor;
  if (exposeProxy) {
   targetInterceptor = (isStatic ?
     new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
     new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
  }
  else {
   targetInterceptor = (isStatic ?
     new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
     new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
  }

  // Choose a "direct to target" dispatcher (used for
  // unadvised calls to static targets that cannot return this).
  Callback targetDispatcher = (isStatic ?
    new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

  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;

  // If the target is a static one and the advice chain is frozen,
  // then we can make some optimizations by sending the AOP calls
  // direct to the target using the fixed chain for that method.
  if (isStatic && isFrozen) {
   Method[] methods = rootClass.getMethods();
   Callback[] fixedCallbacks = new Callback[methods.length];
   this.fixedInterceptorMap = new HashMap<>(methods.length);

   // TODO: small memory optimization here (can skip creation for methods with no advice)
   for (int x = 0; x < methods.length; x  ) {
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
    fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
      chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
    this.fixedInterceptorMap.put(methods[x].toString(), x);
   }

   // Now copy both the callbacks from mainCallbacks
   // and fixedCallbacks into the callbacks array.
   callbacks = new Callback[mainCallbacks.length   fixedCallbacks.length];
   System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
   System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
   this.fixedInterceptorOffset = mainCallbacks.length;
  }
  else {
   callbacks = mainCallbacks;
  }
  return callbacks;
 }

仿照上面Demo的第四步:

代码语言:javascript复制
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
  enhancer.setInterceptDuringConstruction(false);
  enhancer.setCallbacks(callbacks);
  return (this.constructorArgs != null && this.constructorArgTypes != null ?
    enhancer.create(this.constructorArgTypes, this.constructorArgs) :
    enhancer.create());
 }

上面的几步就完成了一个动态代理的流程,就只需要真的发生调用的时候去执行动态代理类了。

哪些场景事务会失效?
  • 1、只对public修饰方法才起作用
  • 2、@Transaction默认检测异常为RuntimeException及其子类 如果有其他异常需要回滚事务的需要自己手动配置,例如:@Transactional(rollbackFor = Exception.class)
  • 3、确保异常没有被try-catch{},catch以后也不会回滚
  • 4、检查下自己的数据库是否支持事务,如mysql的mylsam
  • 5、Springboot项目默认已经支持事务,不用配置;其他类型项目需要在xml中配置是否开启事务
  • 6、如果在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效,因为代理问题

这里说下在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效。如果是在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用,必须将方法放入另外一个类中,并且该类通过Spring注入。

Spring 采用动态代理(AOP)实现对Bean的管理和切片,它为我们的每个class生成一个代理对象,只有在代理对象之间进行调用时,可以触发切面逻辑。

而在同一个类中,方法B调用A,调用的事元对象的方法,而不是通过代理对象,所以spring无法切到这次调用,也就是无法通过注解保证事务性。

参考地址
  • https://blog.csdn.net/joker8023joker/article/details/103277571
  • https://blog.csdn.net/Dragon_1999/article/details/93059495

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

0 人点赞