springboot-变乱失效以及排查过程

[复制链接]
发表于 2025-12-22 03:03:39 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
排查了好久,终于办理,渴望这次的排查过程对各人也有资助,废话少说,上源码
开发情况

springboot 2.3.11
jdk8
gradle6.4
HikariDataSource
ps: 本环节使用双数据源,在service层做切面拦截,切换详细的数据源
题目

在界说了详细的变乱管理以后,想着不要手动切库,由于变乱管理那边已经表现的注入了详细的数据源,然后效果导致的是变乱失效,发生非常不回滚。代码如下
  1.     @Override
  2.     @Transactional(rollbackFor = Exception.class, transactionManager = DsConst.AGLOUD_TRANSACTION_MANAGER)
  3.     public void ex() {
  4.         jdbcTemplate.execute("insert into table_name1(name) values (1)");
  5.         if (1 == 1) throw new RuntimeException("出异常了啊!!!!");
  6.         jdbcTemplate.execute("insert into table_name2(name) values (1)");
  7.     }
复制代码
排查过程

第一步,先排查源码,spring拦截变乱的拦截器是TransactionInterceptor类的invokeWithinTransaction方法,源码如下
  1. @Nullable
  2. protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
  3.     TransactionAttributeSource tas = this.getTransactionAttributeSource();
  4.     //1:获取事务属性配置信息:通过 TransactionAttributeSource.getTransactionAttribute解析@Trasaction注解得到事务属性配置信息
  5.     TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
  6.     //2:获取事务管理器
  7.     TransactionManager tm = this.determineTransactionManager(txAttr);
  8.     //将事务管理器tx转换为 PlatformTransactionManager
  9.     PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);
  10.     String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
  11.     //createTransactionIfNecessary内部,这里就不说了,内部主要就是使用spring事务硬
  12.     //编码的方式开启事务,最终会返回一个TransactionInfo对象
  13.     TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
  14.     //业务方法返回值
  15.     Object retVal;
  16.     try {
  17.         //调用aop中的下一个拦截器,最终会调用到业务目标方法,获取到目标方法的返回值
  18.         retVal = invocation.proceedWithInvocation();
  19.     } catch (Throwable var18) {
  20.         //3:异常情况下,如何走?可能只需提交,也可能只需回滚,这个取决于事务的配置
  21.         this.completeTransactionAfterThrowing(txInfo, var18);
  22.         throw var18;
  23.     } finally {
  24.         //清理事务信息
  25.         this.cleanupTransactionInfo(txInfo);
  26.     }
  27.     if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
  28.         TransactionStatus status = txInfo.getTransactionStatus();
  29.         if (status != null && txAttr != null) {
  30.             retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
  31.         }
  32.     }
  33.     //4:业务方法返回之后,只需事务提交操作
  34.     this.commitTransactionAfterReturning(txInfo);
  35.     return retVal;
  36. }
复制代码
当我跟进去时间,发现变乱并不是一个新的变乱了,spring判定回滚的代码如下completeTransactionAfterThrowing,进去,末了实验到这里
  1. private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
  2.                 try {
  3.                         boolean unexpectedRollback = unexpected;
  4.                         try {
  5.                                 triggerBeforeCompletion(status);
  6.                                 if (status.hasSavepoint()) {
  7.                                
  8.                                         status.rollbackToHeldSavepoint();
  9.                                 }
  10.                   //判断是否是一个新的事务,如果是就回滚
  11.                                 else if (status.isNewTransaction()) {
  12.                                        
  13.                                         doRollback(status);
  14.                                 }
复制代码
我很迷惑,为什么不是一个新的变乱,而且我也没有整嵌套变乱什么花里胡哨的东西,怎么就不是新的变乱了。于是我别的起了一个项目对比debugger,好久才发,详细如下

乍一看,我是不是眼花了!!!为什么目的对象和署理对象都是署理对象呢。我xx,Cglib创建署理的时间,是可以署理类也再创建署理的吗?本着求真务实的精神,用cglib的原生Api做了测试,发生会报错,不能如许子。代码如下
  1. package com.shiguiwu.springmybatis.spring.aop.cglib;
  2. import org.springframework.cglib.proxy.Enhancer;
  3. import org.springframework.cglib.proxy.MethodInterceptor;
  4. /**
  5. * @description: callbak
  6. * 1. Cglib根据父类,Callback, Filter 及一些相关信息生成key
  7. * 2. 然后根据key 生成对应的子类的二进制表现形式
  8. * 3. 使用ClassLoader装载对应的二进制,生成Class对象,并缓存
  9. * 4. 最后实例化Class对象,并缓存
  10. * @author: stone
  11. * @date: Created by 2021/5/18 19:54
  12. * @version: 1.0.0
  13. * @pakeage: com.shiguiwu.springmybatis.spring.aop.cglib
  14. */
  15. public class CglibCallbackObjTest {
  16.     interface Service1 {
  17.         void m1();
  18.     }
  19.     interface Service2 {
  20.         void m2();
  21.     }
  22.     public static class Service implements Service1, Service2 {
  23.         @Override
  24.         public void m1() {
  25.             System.out.println("m1");
  26.         }
  27.         @Override
  28.         public void m2() {
  29.             System.out.println("m2");
  30.         }
  31.     }
  32.     public static void main(String[] args) {
  33.         Enhancer enhancer = new Enhancer();
  34.         //设置父类
  35.         enhancer.setSuperclass(Service.class);
  36.         //设置代理对象需要实现的接口
  37.         enhancer.setInterfaces(new Class[]{Service1.class, Service2.class});
  38.         //通过Callback来对被代理方法进行增强
  39.         enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
  40.             long l = System.nanoTime();
  41.             Object result = methodProxy.invokeSuper(o, objects);
  42.             System.out.println(method.getName() + "耗时为:" + (System.nanoTime() - l));
  43.             return result;
  44.         });
  45.         Object proxy = enhancer.create();
  46.         //if (proxy instanceof Service) {
  47.         //    ((Service) proxy).m1();
  48.         //    ((Service) proxy).m2();
  49.         //}
  50.         System.out.println("父类" + proxy.getClass().getSuperclass());
  51.         System.out.println(proxy.getClass());
  52.         System.out.println("创建代理类实现的接口如下:");
  53.         for (Class<?> cs : proxy.getClass().getInterfaces()) {
  54.             System.out.println(cs);
  55.         }
  56.         Enhancer enhancer1 = new Enhancer();
  57.         enhancer1.setSuperclass(proxy.getClass());
  58.         enhancer1.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
  59.             long l = System.nanoTime();
  60.             Object result = methodProxy.invokeSuper(o, objects);
  61.             System.out.println(method.getName() + "耗时11为:" + (System.nanoTime() - l));
  62.             return result;
  63.         });
  64.         Object proxy1 = enhancer1.create();
  65.         System.out.println("父类" + proxy1.getClass().getSuperclass());
  66.         System.out.println(proxy1.getClass());
  67.         //if (proxy1 instanceof Service) {
  68.         //    ((Service) proxy1).m1();
  69.         //    ((Service) proxy1).m2();
  70.         //}
  71.     }
  72. }
复制代码
运行这段代码会报错
  1. Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.ClassFormatError-->Duplicate method name&signature in class file com/shiguiwu/springmybatis/spring/aop/cglib/CglibCallbackObjTest$Service$$EnhancerByCGLIB$$c12e4df0$$EnhancerByCGLIB$$eed200b8
  2.         at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
  3.         at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
  4.         at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
  5.         at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.ap
复制代码
但是,要知道,spring鬼的很,说不定他就是可以的,效果测试代码,发现,spring基于cglib创建的署理是可以,他会判定当前的目的对象是不是署理对象,假如是,则以目的对象的父类来创建署理对象,但是目的对象照旧署理对象。代码org.springframework.aop.framework.CglibAopProxy#getProxy(@Nullable ClassLoader classLoader) 如下
  1.         try {
  2.                         Class<?> rootClass = this.advised.getTargetClass();
  3.                         Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
  4.                         Class<?> proxySuperClass = rootClass;
  5.           //判断目标对象是不是代理对象
  6.                         if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
  7.                                 proxySuperClass = rootClass.getSuperclass();
  8.                                 Class<?>[] additionalInterfaces = rootClass.getInterfaces();
  9.                                 for (Class<?> additionalInterface : additionalInterfaces) {
  10.                                         this.advised.addInterface(additionalInterface);
  11.                                 }
  12.                         }
复制代码
这个时间,我们须要相识springbean的生命周期了,spring是什么时间,偷梁换柱的,把目的对象变成署理对象。现有我知道的就是引入@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)注解,然后spring向容器中添加了一个AnnotationAwareAspectJAutoProxyCreator,这个类很告急,它是一个SmartInstantiationAwareBeanPostProcessor的子类,他就是springbean在初始化后调用的方法,然后判定bean 需不须要天生署理对象。重要逻辑在抽象类AbstractAutoProxyCreator#postProcessAfterInitialization
  1.       @Override
  2.         public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  3.                 if (bean != null) {
  4.                         Object cacheKey = getCacheKey(bean.getClass(), beanName);
  5.                         if (this.earlyProxyReferences.remove(cacheKey) != bean) {
  6.                                 return wrapIfNecessary(bean, beanName, cacheKey);
  7.                         }
  8.                 }
  9.                 return bean;
  10.         }
复制代码
然后我把断点打到这个位置,由于类比力多,为了不干扰我们调试,使用条件debugger,详细如下

终极发现,有在创建署理对象的时间,上面的方法进了两次。然后发现他们是差别的类进来的,原来我项目代码中,创建署理用到了两个类分别是AnnotationAwareAspectJAutoProxyCreator和DefaultAdvisorAutoProxyCreator。这个两个范例具有雷同的父类,也是BeanPostProcessor,也会拦截bean的创建过程。
但是我当地的项目重要AnnotationAwareAspectJAutoProxyCreator,于是为了模仿现场的情况,我手动注入了DefaultAdvisorAutoProxyCreator,末了发现当地是跟前次的情况一样,创建了嵌套署理。
  1.     @Bean
  2.     public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  3.         DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
  4.         defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
  5.         return defaultAdvisorAutoProxyCreator;
  6.     }
复制代码
那是不是由于嵌套署理让变乱失效呢,末了的答案是:不是。当地照旧能回滚,原来我第一个办理断的时间,只关注了是不是新变乱,但是spring aop是一个方法调用链,以是当旧的变乱通过调用栈出来的时间,这个变乱就是一个新变乱,也实验了回滚操纵.
心累。。。
然后我在想是不是postgres没有开启变乱支持呢,大概是数据库须要手动设置变乱支持。通过实验我又打扫了这个大概。代码如下
  1.    @Autowired
  2.     private DataSource dataSource;
  3.     @Resource
  4.     private DataSource masterDataSource;
  5.     @Test
  6.     public void tx() {
  7.         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  8.         //1.定义一个事务管理器
  9.         PlatformTransactionManager transactionManager = new DataSourceTransactionManager(masterDataSource);
  10.         boolean a = true;
  11.         //清空数据
  12.         jdbcTemplate.update("truncate table table_name1");
  13.         System.out.println("PROPAGATION_REQUIRED start ==========================================================");
  14.         //2.定义一个事务属性
  15.         TransactionDefinition transactionDefinition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
  16.         //3.取一个事务状态,开启事务了
  17.         TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
  18.         //设置扩展点
  19.         //addSynchronization("ts-1", 2);
  20.         //        //addSynchronization("ts-2", 1);
  21.         try {
  22.             //4.执行业务
  23.             jdbcTemplate.execute("insert into table_name1(name) values (1)");
  24.             if (a) {
  25.                 throw new RuntimeException("===");
  26.             }
  27.             //jdbcTemplate.update("insert into book(book_name) values (?)", "成是非");
  28.             jdbcTemplate.execute("insert into table_name1(name) values (100)");
  29.             //此时,在执行一个方法事务
  30.             //other(jdbcTemplate, transactionManager);
  31.             //5.提交事务
  32.             System.out.println("PROPAGATION_REQUIRED 准备commit");
  33.             transactionManager.commit(transactionStatus);
  34.             System.out.println("PROPAGATION_REQUIRED commit完毕");
  35.         } catch (Exception e) {
  36.             e.printStackTrace();
  37.             //6.回滚事务:platformTransactionManager.rollback
  38.             transactionManager.rollback(transactionStatus);
  39.         }
  40.         System.out.println("after==========================>:" + jdbcTemplate.queryForList("SELECT * from table_name1"));
  41.     }
复制代码
颠末上面代码,终极发现,JdbcTemplate 的数据源和PlatformTransactionManager 管理器的数据源不是同一个,为什么不是同一个就失效呢?这里须要相识spring内部是怎么控制变乱的。
这里简单说一下,源码不是我们的重点:
spring在开始变乱之前,会在变乱管理器中拿数据源,通过该数据源获取一个数据库毗连,同时将数据库毗连设置为手动提交,然后通过ThreadLocal绑定到当火线程中,绑定的格式map范例datasource->ConnectionHolder。再来就是实验业务了,jdbcTemplate中有数据源,这个时间,重点来了啊。根据jdbcTemplate的数据源,从当地线程中拿毗连,此时由于数据源不一样,根本拿不到毗连,spring帮我们本次数据源中再创建一个毗连,用此时的毗连实验sql,然后主动提交了。而我们生存线程当地的毗连不绝没干活,以是变乱失效!
再次排查

于是呢,我在变乱方法那里又手动举行切库,这回总该数据源划一了吧。固然说是动态数据源,但是详细返回的数据源应该由我来设置的。

但是,事与愿违,照旧不可,于是我在切库的切面那边打了个断点,代码如下

然后发现,代码居然是先实验了变乱拦截器,在实验的切面,炸了啊。怎么大概呢,查资料也是变乱拦截器优先级很低的,但是不管你怎么设置的切面次序,始终但是先实验的变乱拦截器。也就是说,变乱管理里的数据源,不管你怎么切换,始终都是默认的数据源,如许就实现不了其他库的变乱控制。末了回到最开始的题目,有一个变乱拦截器是署理的署理,切面是目的对象的署理,以是不管你怎么设置,署理的署理方法始终是开始实验的。
末了整个人都麻了,然后只能从为什么有两个署理天生器的人手,末了通过探求,在jar内里,有人表现注入了DefaultAdvisorAutoProxyCreator,这个是由于项目遗留的bug,将谁人源码改掉就办理了。
回首

本题目涉及的知识点比力多,变乱,署理,切面,spring bean的创建过程。本人水平有限,如有错误,请品评指正,多谢!!!88888
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表