==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍
前言:这一篇文章通过分析根源,基本可以解决所有事务失效的情况了,但是因为spring掌握的有限,所以写的时候可能会有些地方描述得不是很好!大家可以指出我来改进!
- 新手疑问之为什么我已经加上了@Transactional注解,还是失效呢???
这个先从表面上判断(其根本原因下面讲,新手可能较难理解),可以参考细说@Transactional用法及原理
,简单了解失效的表面原因。
-
老鸟致命疑问之为什么我已经加上了@Transactional注解,并且事务确认已经开启,最后已经生成代理类调用了,它还是失效???
这个问题可能不常见,一旦遇到,那么除非是spring高高手,对源码熟知又熟,才可以瞬间解之,否则只能像我这种菜鸟,慢慢的跟着源码一路找到底才可能找到一丝光明。
言归正传,我先来一个上述问题的例子。因为不好拿实际项目中的代码进行演示,所以我将问题抽出来复现一下即可。新建一个项目,按照细说@Transactional用法及原最后小白总结的来即可。最后如图所示
- RecordPointCut
@Aspect @Component public class RecordPointCut { @Pointcut("execution(public * com.acme.transactional.demo.service.*Impl.*(..))") public void myAnnotationPointcut(){ } @Before("myAnnotationPointcut()") public void before(JoinPoint joinPoint){ System.out.println(joinPoint.getTarget() + " begin:" + System.currentTimeMillis()); } @After("myAnnotationPointcut()") public void after(JoinPoint joinPoint){ System.out.println(joinPoint.getTarget() + " end:" + System.currentTimeMillis()); } }
- DatasourceConfig
@Configuration public class DatasourceConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(""); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); return dataSource; } }
- MyConfig
@Configuration public class MyConfig { /* * 看着名字就知道,其它服务的bean,相当于我这个服务使用到了其它服务的bean * @Qualifier作用是指定注入哪个bean,这里生成OtherServerBean的时候,就会去创建HelloImpl * */ @Bean OtherServerBean otherServerBean(@Qualifier("helloImpl")HelloImpl hello){ System.out.println(hello); return new OtherServerBean(); } }
- Curd
@Component public class Curd { public void add(){ //数据库插入操作 } }
- OtherServerBean
/* * 实现了FactoryBean接口,算是一个特殊的bean * */ public class OtherServerBean implements FactoryBean<Object> { @Override public OtherServerBean getObject() throws Exception { return new OtherServerBean(); } @Override public Class<OtherServerBean> getObjectType() { return OtherServerBean.class; } }
- HelloImpl
@Component public class HelloImpl{ @Autowired private Curd curd; @Transactional public void sayHello(){ curd.add(); } }
- 测试
@SpringBootTest class DemoApplicationTests { @Autowired HelloImpl hello; @Test void contextLoads() { hello.sayHello(); } }
先说接下来会发生的现象,hello是一个代理类,但是事务并不生效(没有事务拦截器,可在源码中看到)
接下来运行代码,如图所示,hello是一个代理类
再单步调试进去,红圈的地方,那三个拦截器,没有一个是事务拦截器的,这就可以反证出开头的那个现象,加了事务注解,是代理类,但是还是没有生效。
现在我们来找原因,首先,我的思路是,为什么hello这个bean没有事务拦截器?拦截器应该说明时候被加进去?是不是bean生成的时候?……(经过我长时间的脑暴+试验,终于找出一条路来,这里我直接说结论,因为路是自己走出来的,很难原样的告诉别人该怎么走)
-
找到拦截器被加进去的地方
寻找路线(中间有些会忽略,不然就太长了)
SpringApplication.run(DemoApplication.class, args)
->refreshContext
->refresh
->finishBeanFactoryInitialization
->preInstantiateSingletons
好,第一阶段的路线已经找到了,如图,像上面那行注释所说实例化所有非懒加载的bean。
第二阶段,走起,当实例化到demoApplication
的时候,路线是这样的
else中的getBean
->doGetBean
->mbd.isSingleton()
分支的createBean
->resolveBeforeInstantiation
->applyBeanPostProcessorsBeforeInstantiation
->postProcessBeforeInstantiation
(当ibp的类型为AnnotationAwareAspectJAutoProxyCreator
时单步进去)->shouldSkip
->findCandidateAdvisors
->findAdvisorBeans
好,到终点了,我们终于找到增强器(这里还只是增强器,拦截器还要在外面被生成,但是我们的目的已经达到了)被加进去的地方了。如图,看向标1和2的地方,beanNamesForTypeIncludingAncestors
这个方法,是去寻找spring工厂中是否有增强器,果不其然,被找到了一个事务增强器,标3所示。
接着往下走,可以看到标1的地方是判断这个bean是否正在创建中,如果是的话,是不会走到标2这个地方的。从图中可以看出,这个bean还没开始创建,所以用beanFactory.getBean
去创建它
-
拦截器创建的过程?
接下来,我们要找出拦截器的创建过程,第一阶段路线
beanFactory.getBean
->doGetBean
->createBean
->doCreateBean
->createBeanInstance
->instantiateUsingFactoryMethod
好,第一阶段就走完了,如图,因为org.springframework.transaction.config.internalTransactionAdvisor
有工厂方法,所以使用instantiateUsingFactoryMethod
这个去实例化它,标2的地方是去创建org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
这个工厂类去了。
创建ProxyTransactionManagementConfiguration
的逻辑和之前demoApplication
的是一样的,会进入到resolveBeforeInstantiation
中,然后试图去找增强器,发现它正在创建中(可不是嘛),直接就返回了,相当于没有找到增强器。
第二阶段,现在时间紧迫,直接开挂,到另一个地方吧,路线如下
createBeanInstance
->populateBean
->ibp.postProcessProperties
(这里需要找到正确的入口,当ibp为AutowiredAnnotationBeanPostProcessor
时)->inject
->inject
->beanFactory.resolveDependency
->doResolveDependency
->resolveMultipleBeans
->findAutowireCandidates
(else的Collection.class
分支)->beanNamesForTypeIncludingAncestors
->getBeanNamesForType
->doGetBeanNamesForType
->isTypeMatch
->getTypeForFactoryBean
(在FactoryBean.class.isAssignableFrom(predictedType)
下)
好,路线到这里就结束了,原因我们也快找出来了,我们直接来看看
getTypeForFactoryBean
这个方法,看之前,我们要知道,为什么会进入到这里来,如图,可以看到,居然是otherServerBean这个bean,为什么呢?因为它是一个工厂bean,我们再来看看它的作用是什么,单步进去。
我们可以看到,如果myConfig
这个类存在(因为otherServerBean
这个是在它下面管理的bean),那么就尝试根据类型去获取otherServerBean
,但是发现没有匹配的类型,为什么呢?我们回过头来看,otherServerBean的定义
,FactoryBean是Object类型,但是getObjectType返回的却是OtherServerBean类型,所以就匹配不上了。/* * 实现了FactoryBean接口,算是一个特殊的bean * */ public class OtherServerBean implements FactoryBean<Object> { @Override public OtherServerBean getObject() throws Exception { return new OtherServerBean(); } @Override public Class<OtherServerBean> getObjectType() { return OtherServerBean.class; } }
那么匹配不上会怎么办?继续往下看,因为是单例,所以调的是getSingletonFactoryBeanForTypeCheck这个方法。
它里面会调用createBeanInstance去实例化这个bean,为什么要这样?因为它需要实例化出来才能判断它是啥类型呀。所以问题就在这里。
这里先小结一下,再看最后的结果。因为
otherServerBean
的创建出了问题otherServerBean
的FactoryBean
类型是Object
的,getObjectType
又返回的是OtherServerBean
,导致不能直接拿到getObject
中的new OtherServerBean()
,所以需要去实例化OtherServerBean
出来才能去判断类型。- 实例化
OtherServerBean
又会去找MyConfig
中注解为@Bean
的otherServerBean
,这时候@Qualifier("helloImpl")HelloImpl hello
也被实例化出来了,最终结果就是,hello
先生成了,增强器还没创建出来,自然就生成不了拦截器,也就少了这个事务拦截器。
如图所示,走到这一步后,会去尝试为这个bean获取增强器
但是此时事务增强器还正在创建
所以最后肯定是拿不到它的
最后回到createBean
中的doCreateBean
,可以看到hello
先于org.springframework.transaction.config.internalTransactionAdvisor
创建了。
- RecordPointCut
- 重要细节分析
- 为什么
preInstantiateSingletons
中,到了demoApplication
的加载才开始去创建internalTransactionAdvisor
?
有这疑问的,应该都没有实际运行这段代码,跟着调试进去看,那现在我就演示一下,如图,就拿第一个bean来说
这里可以看到,它之前已经被加载过了!所以直接从缓存中拿到,就不会进入到else分支进行bean的创建了!所以也就不会再到internalTransactionAdvisor
这一步,同理,demoApplication
前面的bean都是这样。
- 为什么会在
postProcessBeforeInstantiation
中的shouldSkip
去实例化internalTransactionAdvisor
?
说到这里,确实是一个坑,postProcessBeforeInstantiation
这个方法相当于是bean实例化前,判断一下是否需要走代理?然后它会判断,isInfrastructureClass(beanClass)
是否是基础设施类(我直译过来的),判断代码如下,简单讲,如果是这三个类的子类,那么就算是。
然后判断是否应该跳过,代码如下,先去找到所有的增强器,然后再一一判断是否是protected boolean isInfrastructureClass(Class<?> beanClass) { boolean retVal = Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass); if (retVal && logger.isTraceEnabled()) { logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]"); } return retVal; }
AspectJPointcutAdvisor
类型的,如果有的话就返回true,就可以跳过,不代理了。这里有一个坑,作者也注释了TODO,考虑是否用缓存优化,因为现在每次调用shouldSkip
都会去调用findCandidateAdvisors
,它去找增强器的时候,如果找到了bean的定义,那么就会去创建它,这一步其实可以用缓存代替,只需要走一次findCandidateAdvisors
就行。所以当创建demoApplication
的时候,会走到这里,然后找到internalTransactionAdvisor
,并创建它,后来又导致一系列的递归创建。protected boolean shouldSkip(Class<?> beanClass, String beanName) { // TODO: Consider optimization by caching the list of the aspect names List<Advisor> candidateAdvisors = findCandidateAdvisors(); for (Advisor advisor : candidateAdvisors) { if (advisor instanceof AspectJPointcutAdvisor && ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) { return true; } } return super.shouldSkip(beanClass, beanName); }
- 创建
ProxyTransactionManagementConfiguration
的时候为什么会创建OtherServerBean
?
是的,这两个看起牛马不相及,可是他们就是有关系了!让我们来看看这是怎么回事,从doCreateBean
的populateBean
说起,populateBean
的作用就是为bean填充属性,比如A中有b、c属性,那么它就会去创建b、c然后填充进去。ProxyTransactionManagementConfiguration
的父类以及自身都有需要填充的属性,如图,先看看父类的,因为肯定会先填充父类的属性。
再看看调试的信息,先为setConfigurers
这个方法注入需要的bean
去找到适合这个类型的bean(根据类型查找)
因为毕竟深入,所以我直接贴出最后一层的截图(一步步跟进去即可找到),看方法名就知道,根据type获取bean,继续进去看看
重点来了,当判断到otherServerBean
的时候,调用isTypeMatch
的时候,问题来了,因为它是一个工厂bean,所以会走到一个比较特殊的地方。
没错,就是这里,如果是工厂bean,那么会调用getTypeForFactoryBean
去拿到这个bean的类型。
再深入到里面,可以看到,拿到的getObjectType
方法的返回类型是OtherServerBean
,但是FactoryBean
的类型却是Object
。
所以类型不匹配,找不到。如果找到的话,就直接返回,不会去实例化otherServerBean
。
接着就会去实例化otherServerBean
这个bean,看看是什么类型,接下来的就没啥可说的了,
- 为什么
- 解决办法
- 懒加载
很好理解,既然增强器还没创建出来,我为什么要急着先加载呢?所以在所有使用到HelloImpl
的地方,加上@Lazy
。
但是,这个例子不适用,为什么呢?可以看到,加上了@Bean OtherServerBean otherServerBean(@Lazy@Qualifier("helloImpl")HelloImpl hello){ System.out.println(hello); return new OtherServerBean(); }
@Lazy
,但是当实例化otherServerBean
的时候,helloImpl
还是被实例化出来了。@Lazy
有这么一条规则,如果标记为懒加载的类,需要注入到其它非懒加载的类的时候,懒加载失效!
所以,如果代码改成这样,就可以使用懒加载的方式
这样,相当于@Bean OtherServerBean otherServerBean(@Qualifier("THelloImpl")THelloImpl hello){ System.out.println(hello); return new OtherServerBean(); }
THelloImpl
被加载了,但是它的属性HelloImpl
却可以懒加载了。 - 修改
OtherServerBean
的FactoryBean
类型
这个方法可行性不高,原因是,如果这个是第三方bean,那么你修改不了。但是如果是自己写的话,就很好办了。直接修改类型即可。public class OtherServerBean implements FactoryBean<OtherServerBean> { @Override public OtherServerBean getObject() throws Exception { return new OtherServerBean(); } @Override public Class<OtherServerBean> getObjectType() { return OtherServerBean.class; } }
- 最直接能避免这种情况的,就是不要使用注入的方式,直接new出对象来
前提是不需要全局性,也就是单不单例的无所谓那种。但是很明显,这里不行,因为THelloImpl里面用到了spring的自动注入,所以THelloImpl也必须要由spring管理。@Bean OtherServerBean otherServerBean(){ System.out.println(new THelloImpl()); return new OtherServerBean(); }
- 懒加载
- 总结
@Transactional
失效的根本原因是事务增强器没有被加入到某个bean中,造成这种原因有两种,第一是根本找不到事务增强器,第二是某个bean先于事务增强器加载了,导致事务增强器没被加入。- 从表面上看,虽然它是一个代理类,但是里面却没有事务拦截器,有的是其它拦截器,所以当然会失效。
FactoryBean
的用法,需要注意,<T>
泛型参数不要设置为Object
,否则就会造成类型检查的时候不匹配,转而去实例化该类获取类型,这又会导致一系列的连锁反应。@Lazy
标记的类,需要注入到其它非懒加载的类,那么就会失效。这个很容易理解,本来懒加载就是用的是才会加载,那现在别人需要用到你了,当然需要加载了。- 使用到事务注解的类,最好是放在最里层,外面包一层,用
@Lazy
标记,而且不要在其它地方再直接使用它。例如,A中的方法使用了事务,那么可以用B将A包起来,A在B中标记为懒加载,之后其它地方仅仅引用B,不要直接引用A,就可以避免项目中误使用导致的事务失效。