美文网首页
整理spring事务失效的场景(源码解析)

整理spring事务失效的场景(源码解析)

作者: Longer_JzL | 来源:发表于2020-06-16 16:09 被阅读0次

    Spring事务管理方式,我们大部分都是使用声明式来实现,即贴@Transacational注解。但是在我们使用的过程中,会因为使用不当而导致事务失效的问题。下面就罗列出事务失效的常见使用场景,并加以讲解。

    场景1:spring的事务注解@Transactional只能放在public非final修饰的方法上才起作用,如果放在其他非public(private,protected)方法上,事务不起作用。

    在应用系统调用声明了 @Transactional 的目标方法时,Spring 默认使用 AOP 代理,而Spring AOP实现方式有两种:jdk动态代理实现和cglib动态代理实现,但是无论使用jdk动态代理还是cglib动态代理,@Transactional也只能放在public或public final修饰的方法上才起作用。
    下面是从源码的角度来看其中的原因。

    jdk动态代理实现

    目前,我们大部分业务代码都是写在Service层,即创建一个Service接口,然后创建ServiceImpl类,且该类实现Service接口,再在实现类的对应实现方法上写相关业务逻辑。
    这种方式,如果我们在ServiceImpl类或方法上贴上@Transactional 注解,实际上底层是使用了jdk动态代理,会动态的生成一个Service代理对象。

    类似这样:

    /**
     * @author: Longer
     */
    public interface PersonService {
        
        void eat();
    }
    
    /**
     * @author: Longer
     */
    @Service
    public class PersonServiceImpl implements PersonService {
    
        @Transactional
        @Override
        public void eat() {
    
        }
    }
    
    

    源码解析
    newProxyInstance类是jdk动态生成代理对象的时候需要调用的类。

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * 首先从缓存查找是否有代理类,没有就生成一个
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * 通过InvocationHandler调用目标类的构造函数
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                //如果构造函数不是public修饰,修改
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    

    其中查找Proxy类的源码如下:

     private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            //长度检查
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
             
            //调用了下面的WeakCache<K, P, V>.get(K key, P parameter)方法,loader作为key,interfaces作为parameter参数
            //定义如下:proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory())
            return proxyClassCache.get(loader, interfaces);
        }
    
    
       //首先当前key(也就是上面的ClassLoader)已经加载存在,就直接从缓存中返回
       //如果不存在,就会通过ProxyClassFactory来创建代理对象
       public V get(K key, P parameter) {
            Objects.requireNonNull(parameter);
    
            expungeStaleEntries();
             //根据key的hash值和一个ReferenceQueue来构造
            Object cacheKey = CacheKey.valueOf(key, refQueue);
    
            // 从map中取出cacheKey的值
            ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
            if (valuesMap == null) {
                ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                                      valuesMap = new ConcurrentHashMap<>());
                if (oldValuesMap != null) {
                    valuesMap = oldValuesMap;
                }
            }
    
            Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
            Supplier<V> supplier = valuesMap.get(subKey);
            Factory factory = null;
    
            while (true) {
                if (supplier != null) {
                    // supplier可能是Factory或者CacheValue<V>
                    V value = supplier.get();
                    if (value != null) {
                        return value;
                    }
                }
                // 缓存中没有supplier,同时supplier中没有
                // 懒加载的方式创建一个Factory
                if (factory == null) {
                    factory = new Factory(key, parameter, subKey, valuesMap);
                }
    
                if (supplier == null) {
                    supplier = valuesMap.putIfAbsent(subKey, factory);
                    if (supplier == null) {
                        // 安装 Factory
                        supplier = factory;
                    }
                } else {
                    if (valuesMap.replace(subKey, supplier, factory)) {
                        supplier = factory;
                    } else {
                        supplier = valuesMap.get(subKey);
                    }
                }
            }
        }
    

    再看上面提到的ProxyClassFactory类

    //类定义
     private static final class ProxyClassFactory
            implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
    
            @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
                for (Class<?> intf : interfaces) {
                    /*
                     * 校验当前类加载器ClassLoader解析到的名称和定义的名称是否相同 
                     */
                    Class<?> interfaceClass = null;
                    try {
                        interfaceClass = Class.forName(intf.getName(), false, loader);
                    } catch (ClassNotFoundException e) {
                    }
                    if (interfaceClass != intf) {
                        throw new IllegalArgumentException(
                            intf + " is not visible from class loader");
                    }
                    /*
                     * 校验是否是接口类型,这也就是为什么JDK动态代理只能基于接口
                     */
                    if (!interfaceClass.isInterface()) {
                        throw new IllegalArgumentException(
                            interfaceClass.getName() + " is not an interface");
                    }
                    /*
                     * 防重
                     */
                    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                        throw new IllegalArgumentException(
                            "repeated interface: " + interfaceClass.getName());
                    }
                }
    
               // 代理对象的目录
                String proxyPkg = null;     
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
                .....
    
                /*
                 * 生成指定Proxy代理对象的字节码
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                    //调用的native方法
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    /*
                     * 生成的代理类有bug
                     */
                    throw new IllegalArgumentException(e.toString());
                }
            }
    
    }
    

    从ProxyClassFactory类,我们可以看到,该类是会去校验目标类是否是接口类型。这也就是为什么jdk动态代理只能基于接口实现。

    JDK动态代理生成的代理class类名实际上是这样的:public final class $Proxy0 extends Proxy implements PersonService{}(PersonService是被代理接口)
    继承了Proxy 和实现了目标接口类PersonService。因为Java不允许多重继承,这就限制了:使用JDK代理不能是普通类或者抽象类,只能是接口类型。
    由于接口定义的方法是public的,java要求实现类所实现接口的方法必须是public的(不能是protected,private等),同时不能使用static的修饰符。所以,可以实施接口动态代理的方法只能是使用“public”或“public final”修饰符的方法,其它方法不可能被动态代理,相应的也就不能实施AOP增强,也即不能进行Spring事务增强

    cglib动态代理实现

    对于普通@Service注解的类(未实现接口)并通过 @Autowired直接注入类的方式,是通过cglib动态代理实现的。

    类似这样:

    /**
     * @author: Longer
     */
    @Service
    public class PersonServiceImpl{
    
        @Transactional
        public void eat() {
    
        }
    }
    public class PersonController{
        @Autowired
        private PersonServiceImpl personService;
        @PostMapping(value = "/eat")
        public Result eat() {
          personService.eat();
          return Result.ok();
        }
    }
    
    

    cglib动态代理生成的代理class类名实际上是这样的:

    public class Student$$EnhancerByCGLIB$$92f3e3f6 extends Student implements Factory(){}
    

    Student 是被代理类。可以看到代理类是继承了被代理类Student ,这就是与jdk动态代理的区别。jdk代理类是实现被代理接口。
    由于cglib是继承代理类,而Java继承由于使用final,static,private修饰符的方法都不能被子类复写,所以这些方法将不能被实施的AOP增强,即不会生成cglib代理对象。所以事务是不生效的。
    结论:
    cglib字节码动态代理的方案是通过扩展被增强类,动态创建子类的方式进行AOP增强植入的,由于使用final,static,private修饰符的方法都不能被子类复写,所以这些方法将不能被实施的AOP增强。即除了public的非final的实例方法,其他方法均无效。

    场景2:方法自调用

    目标类直接调用该类的其他标注了@Transactional 的方法(相当于调用了this.对象方法),事务不会起作用。事务不起作用其根本原因就是未通过代理调用,因为事务是在代理中处理的,没通过代理,也就不会有事务的处理。
    类似下面的写法,事务是不生效:

    /**
     * @author: Longer
     */
    @RestController
    @RequestMapping("/person")
    public class PersonController {
        private PersonServiceImpl personServiceImpl;
        @GetMapping(value = "/eat")
        public Result<?> eat() {
            personServiceImpl.eat();
            return Result.ok();
        }
    }
    
    /**
     * @author: Longer
     */
    @Service
    public class PersonServiceImpl{
    
        public void eat() {
            run();
        }
        @Transactional
        public void run(){
    
        }
    }
    
    

    场景3:事务方法抛出非RuntimeException异常,事务不回滚

    原因:Spring 默认只为 RuntimeException 异常回滚事务,如果方法往外抛出 checked exception,该方法虽然不会再执行后续操作,但仍会提交已执行的数据操作。这样可能使得只有部分数据提交,造成数据不一致。
    比如下面的代码,不会回滚:

     @Transactional
        public void eat() throws IOException {
            Person person = new Person();
            person .setId(1);
            this.save(person);
            throw new IOException("testCheckedTran");
        }
    

    代码不回滚的原因是在插入数据库之后,抛出了IOException,这个异常不属于RuntimeException ,所以不会回滚。

    解决办法:自定义回滚策略,可使用@Transactional 的 noRollbackFor,noRollbackForClassName,rollbackFor,rollbackForClassName 属性。如使用:@Transactional(rollbackFor = Exception.class)或@Transactional(rollbackFor = Throwable.class)

    场景4:方法部分代码try...catch住,而catch语句块没有往外跑出回滚异常,事务也不会回滚。

    在业务代码上,一般不try...catch住,如果要捕获异常的话,需要在catch里跑出回滚异常,否则事务不会回滚。

    场景5:数据库存储引擎不支持事务

    如:使用mysql作为数据库的话,如果存储引擎不是INNODB,而是MyISAM的话,则事务不生效。

    场景6:如果采用spring+spring mvc,则context:component-scan重复扫描问题可能会引起事务失败

    如果spring和mvc的配置文件中都扫描了service层,那么事务就会失效。
    原因:因为按照spring配置文件的加载顺序来讲,先加载springmvc配置文件,再加载spring配置文件,我们的事物一般都在srping配置文件中进行配置,如果此时在加载srpingMVC配置文件的时候,把servlce也给注册了,但是此时事物还没加载,也就导致后面的事物无法成功注入到service中。所以把对service的扫描放在spring配置文件中或是其他配置文件中。

    场景7: 多个事务管理器

    当一个应用存在多个事务管理器时,如果不指定事务管理器,@Transactional 会按照事务管理器在配置文件中的初始化顺序使用其中一个。
    如果存在多个数据源 datasource1 和 datasource2,假设默认使用 datasource1 的事务管理器,当对 datasource2 进行数据操作时就处于非事务环境。
    解决办法是,可以通过@Transactional 的 value 属性指定一个事务管理器。在使用多个事务管理器的情况下,事务不生效的原因在本系列后续文章中会有分析

    参考文献链接:
    Spring事务Transactional和动态代理-事务失效的场景
    jdk动态代理
    cglib动态代理

    相关文章

      网友评论

          本文标题:整理spring事务失效的场景(源码解析)

          本文链接:https://www.haomeiwen.com/subject/fduvzhtx.html