1.出现的背景
在做Atlas项目结构重构时,原来的项目结构是一个模块的结构,如图1,如果项目越来越大时不好管理,所以将项目做了一个结构上的升级,如图2。
一个@Asyn注解引起的问题图1 原项目结构
image图2 重构后的项目结构
对重构后的项目打包运行,没发现问题,通过页面测试了下也没有问题,因为项目结构改动,docker脚本需要做下微调,图3是原来的dockerfile文件,重构后脚本需要改为图4所示的
image图3 原dockerfile
image图4 修改后的dockerfile
进行到这里,其实重构基本结束,让同事在测试环境部署一下看看,然后就出现了问题,下面主要来说说出现的问题。
2.问题描述
首先,测试环境启动报错,错误信息如下(只截取了一部分):
(Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'actionButtonController': Unsatisfied dependency expressed through field 'actionButtonService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'actionButtonServiceImpl' defined in URL [jar:file:/opt/atlas-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/atlas-service-0.0.1-SNAPSHOT.jar!/com/dxc/atlas/service/impl/ActionButtonServiceImpl.class]: Initialization of bean failed; nested exception is java.lang.IllegalStateException: Need to invoke method 'getWorkFlowTaskState' declared on target class 'ActionButtonServiceImpl', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1420)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
......
Caused by: java.lang.IllegalStateException: Need to invoke method 'getWorkFlowTaskState' declared on target class 'ActionButtonServiceImpl', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
at org.springframework.core.MethodIntrospector.selectInvocableMethod(MethodIntrospector.java:132)
at org.springframework.aop.support.AopUtils.selectInvocableMethod(AopUtils.java:135)
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.createRunnable(ScheduledAnnotationBeanPostProcessor.java:526)
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled(ScheduledAnnotationBeanPostProcessor.java:393)
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.lambda$null$1(ScheduledAnnotationBeanPostProcessor.java:374)
at java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.lambda
大概的意思就是:ActionButtonServiceImpl类有个方法,在实现的接口中没有这个方法,然后我就想起了那个定时任务的代码:
@Scheduled(cron = "0/5 * * * * ?")
public void getWorkFlowTaskState() throws Exception{
// get unfinished workflow tasks
QueryWrapper<WorkFlowEntityDO> workflowQueryWrapper = new QueryWrapper<>();
workflowQueryWrapper.notIn("state", Arrays.asList(5, 6, 7, 9));
List<WorkFlowEntityDO> workFlowEntityList = workFlowMapper.selectList(workflowQueryWrapper);
for (WorkFlowEntityDO workFlowEntity : workFlowEntityList) {
getTaskState(workFlowEntity);
}
}
ActionButtonServiceImpl里面的这个方法因为是定时任务,没有其他服务会来调用,所以我就没在ActionButtonServiceImpl的接口类ActionButtonService写这个方法,所以就报错了,但是为什么本地启动就没有报错呢,所有配置本地和测试环境基本是一样的,但是解决问题要紧,我先在接口中加了这个方法,然后重新部署,本来满心希望差不多了吧,但是事与愿违,嘉琪说测试环境还是报错,但是报错日志有了变化,说明上个问题已经解决了,新的错误日志是:
(Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'actionButtonController': Unsatisfied dependency expressed through field 'actionButtonService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'actionButtonServiceImpl': Bean with name 'actionButtonServiceImpl' has been injected into other beans [accountServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1420)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at
通过日志分析,是因为循环依赖引起的问题,但是Spring不是已经解决了循环依赖吗(三级缓存),找到报错日志的代码
image根据这个日志大概意思就是:ActionButtonServiceImpl由于循环依赖注入到其他bean中,但是ActionButtonServiceImpl最终又被包装了,这就意味着:其他的bean使用的是ActionButtonServiceImpl这个bean的非最终版本,相当于代码中会有ActionButtonServiceImpl的两个版本的bean,spring检测到这个报错了,这是字面意思,解决方案其实也不难,主要有如下几种:
1.因为是循环依赖引起的,所以新建一个新的类,消除循环依赖,我看了下代码ActionButtonServiceImpl和AccountServiceImpl确实是存在循环依赖;
imageAccountServiceImpl部分代码
imageActionButtonServiceImpl部分代码
2.allowRawInjectionDespiteWrapping开关打开
allowRawInjectionDespiteWrapping字段提供了public方法,我们在类里面实现BeanFactoryAware接口注入BeanFactory,拿到BeanFactory后修改这个开关,但是这种修改方案会有风险,因为根据错误日志描述,程序中存在一个类的两个版本的bean,就是破坏了单例的限制,会不会对业务有影响不能确定
按照这两种修改方法试验了一番,确实解决了问题,不报错了,启动成功,对页面接口进行了测试也没有问题,如果到此结束我心有不甘,然后我就去找了资料,花了不少时间,慢慢才找到问题的根源,因为目前的疑问有两点:
1.为啥本地都是启动成功,无任何问题,测试环境就有问题,难道本地是MAC,测试是Linux,然后我把测试环境的jar包拷贝到我本地启动,确实还是报错,消除了系统不同的原因,这里说的本地是指用idea里面用源码文件运行;
2.循环依赖Spring已经通过三级缓存解决了,为什么还会出现循环依赖引起的问题
3.问题的根源
imagespring三级缓存解决循环依赖
去百度了下Java循环依赖相关问题,很多文章集中在一个@Asyn注解上,说该注解在循环依赖的时候会启动失败,报刚才那个错,然后我特意把这个注解的原理了解了一下,其实不难,网上有很多文章介绍该注解实现的原理,它的自动配置是通过@EnableAsync来实现的,简单点说就是通过自定义一个BeanPostProcessor后置处理器,每个bean的创建都会走到这个后置处理器,然后判断这个bean类或者方法是否有@Asyn注解,没有注解忽略,有注解,生成对应的代理(AOP),具体原理可以看看https://blog.csdn.net/windrui/article/details/101366430 这篇文章。
知道了@Asyn实现原理,然后我去百度了下@Asyn可能会引起的问题,其中就包括循环依赖,其中有篇文章我啃了下,大概清楚了原因:https://blog.csdn.net/luoyang_java/article/details/105835112,我就简单描述下:
举个例子:
A类和B类存在循环依赖,A类有个方法上加了@Asyn注解,启动时,A和B的创建流程如下:
1.进行A的bean的创建,先实例化,填充属性,把A放进正在创建列表中,在填充属性时,发现有B,则进入到2;
2.进行B的bean的创建,也是实例化,填充属性,在填充属性时发现有A,则进入到3;
3.先从已经注册好的单例列表中获取A,这个肯定没有,然后发现A正在创建列表中,会去earlySingletonObjects列表进行查询,这个也查询不到,所以会从singletonFactories获取,调用getObject方法
4.工厂方法会返回A对应的引用,然后填充B的属性,初始化完B后返回B对应的引用,然后完成A的属性填充,接着进行A的初始化,最后结束
然后我看了下工厂的方法源码:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
网上有对该源码的分析,大概的意思就是:如果该bean不是系统创建的而且有对应的继承SmartInstantiationAwareBeanPostProcessor的子BeanPostProcessor加工它,返回的是加工后的结果,否则其实就是该bean本身,本身就是实例后对应的引用,加工的话可能返回的就是代理。因为我们项目中的类里面还有@Transactional注解,我们先把所有的Transactional去掉,发现出现的问题还是一样的,所以先排除是Transactional引起的问题,去掉了Transactional注解,而Asyn注解实现的原理是通过AsyncAnnotationBeanPostProcessor,这个类不是SmartInstantiationAwareBeanPostProcessor的子集,所以getEarlyBeanReference其实返回的只是A的实例引用,没进行过任何包装,所以B注入进去的A的实例是未经过任何包装的bean。
但是A属性填充完之后要进行初始化,初始化前后会进行一些前后置处理:
image前置代码
image后置代码
image这里未对BeanPostProcessor进行过滤,所以会调用所有的BeanPostProcessor处理器,而AsyncAnnotationBeanPostProcessor肯定也在其中,AsyncAnnotationBeanPostProcessor的处理代码:
image通过这个处理,返回的bean是代理的引用,所以A初始化完后返回给Spring管理的bean是代理的引用,而B注入进去的是A实例的引用,出现了不一致,所以会报错。
没有循环依赖加@Asyn会报错吗???
不存在循环依赖的情况下,加这个注解是没有问题的,因为不会触发三级缓存,更不会使用工厂方法进行创建。
目前解决了第一个疑问,加@Asyn注解在循环依赖情况下确实会出现问题,但是为什么本地idea启动没有出问题,而测试环境出了问题呢,这个问题困扰了我一天,我放弃使用公司代码中的那两个类,自己创建了简单的类。
第一次尝试:
package com.dxc.atlas.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class DxcAsyncTest {
@Autowired
private DxcAsyncTest dxcAsyncTest;
@Async
public void print(){
}
}
这个类非常简单,自己依赖自己,最简单的循环依赖的例子,在本地进行启动,发现本地也报错了,报错原因同理。
2021-09-09 11:50:33.754 94616 [main] ERROR org.springframework.boot.SpringApplication - (Application run failed
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dxcAsyncTest': Bean with name 'dxcAsyncTest' has been injected into other beans [dxcAsyncTest] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:623)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at com.dxc.atlas.AtlasServiceWalmartApplication.main(AtlasServiceWalmartApplication.java:15)
放到测试环境,测试环境也报错了,报错原因一样,这一次本地和测试都出问题了。
第二次尝试:
新建两个类,让两个类相互进行循环依赖
package com.dxc.atlas.service;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class StudentService {
@Resource
private TeacherService teacherService;
// @Async
public void print(){
}
}
package com.dxc.atlas.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class TeacherService {
@Resource
private StudentService studentService;
@Async
public void print(){
}
}
对TeacherService的print方法加注解@Async,然后本地进行启动,发现没有报错 -----无语
我一气之下将StudentService的print也加了Async注解,然后本地再次启动运行,然后出现了熟悉的错误
2021-09-09 12:01:38.592 94709 [main] ERROR org.springframework.boot.SpringApplication - (Application run failed
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'studentService': Bean with name 'studentService' has been injected into other beans [teacherService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:623)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at com.dxc.atlas.AtlasServiceWalmartApplication.main(AtlasServiceWalmartApplication.java:15)
再次,我将TeacherService的Async去掉,只在StudentService加注解,也是再次启动,发现还是报错,为什么只加在TeacherService不报错,两个都加或者加在StudentService的必报错,到这里我就怀疑了是否与文件的加载顺序存在关系,然后我梳理了下上面的循环依赖的问题,为了简单,我还是以A和B为例,A类和B类存在循环依赖,A类有个方法上加了@Asyn注解,B没有加,启动时,我把加载流程换了下,先加载B再加载A,流程如下:
1.进行B的bean的创建,先实例化,填充属性,把B放进正在创建列表中,在填充属性时,发现有A,则进入到2;
2.进行A的bean的创建,也是实例化,填充属性,在填充属性时发现有B,则进入到3;
3.先从已经注册好的单例列表中获取B,这个肯定没有,然后发现B正在创建列表中,会去earlySingletonObjects列表进行查询,这个也查询不到,所以会从singletonFactories获取,调用getObject方法
4.工厂方法会返回B对应的引用,因为B上没有任何相关的注解,所以直接返回的是B的实例
5.A填充好B属性后,A进行初始化,因为A上有@Asyn注解,所以返回代理后的引用
6.B填充完A属性后,进行B初始化,B初始化不会改变引用
这样下来,初始化完全正常,不会报错,如果两个类都有@Asyn注解,那不管初始化那个结果都一样,所以这个报错不报错难道的看运气吗,为啥测试环境运行了那么久都没报错,难道这个加载的顺序是确定的吗,为了验证加载的顺序,我在logback.xml加了打印spring的日志配置:
<logger name="org.springframework" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
观察下打印的数据:
imagespring是先加载StudentService再加载TeacherService,所以TeacherService加注解是不会报错的,StudentService加注解才会报错,IDEA加载的顺序难道会和jar包运行的顺序不一致吗,为了验证,我们在IDEA通过class运行和打开终端进行jar包运行看看文件加载顺序是否一致:
imageIDEA启动类加载顺序
imagejar包启动类加载顺序
区别:IDEA启动找的是/Users/dxc/IdeaProjects/atlas-ass/**/target(本地源码)目录下的class文件,而jar包启动加载的是jar里面的class文件,貌似IDEA加载的文件顺序是按照文件名的字典顺序加载,而jar包则不是,为了验证我的想法,直接去看源码实现:
文件扫描代码主要是:
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
进入doScan方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
然后是findCandidateComponents方法,通过断点一层层下去最后走到:
org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources
image通过IDEA运行会运行到:
image然后是:
image再看listDirectory:
image恍然大悟,原来通过IDEA运行的时候,加载的文件顺序是按照文件名称(包括目录和文件)的字典顺序加载的。
回到findPathMatchingResources方法
image通过jar包运行会走到org.springframework.core.io.support.PathMatchingResourcePatternResolver#doFindPathMatchingJarResources这个方法,这里面加载文件的顺序不是按照字典顺序了,具体什么顺序我也不清楚
image加载文件的方式是通过java.util.zip.ZipFile#getNextEntry本地方法实现的,感兴趣的可以去看下c++代码,现在只能知道这个顺序肯定不是字典顺序。
4. 本次问题的总结
回到我们自己项目中的问题,项目中的是ActionButtonServiceImpl和AccountServiceImpl,这两个文件又会被AccountIntegrationController和ActionButtonController依赖,他们之间的依赖关系为:
AccountIntegrationController->AccountServiceImpl<-->ActionButtonServiceImpl<ActionButtonController,打印测试环境包的加载顺序
imageAtlas小助手测试环境启动日志
测试环境是先加载ActionButtonController,再加载AccountIntegrationController,所以是先加载ActionButtonServiceImpl再加载AccountServiceImpl,因为只有ActionButtonServiceImpl有@Asyn注解,根据上面的分析,这个时候会产生错误,再打印下本地的加载顺序:
image加载顺序正好和测试环境相反,所以本地没有报错。
文章开头说的定时任务接口找不到的问题也是循环依赖引起的吗?
我开始也是觉得可能是循环依赖引起的,最后弄懂了以后发现不是,原因也简单分析下
@Asyn开启的自动配置注解是@EnableAsync,这个注解有个属性proxyTargetClass,这个属性的意义大家可以百度,true表示用的CGLIB,false表示如果有实现接口用动态代理,没有用CGLIB
image 我们开启的地方就是直接用的@EnableAsync,而且ActionButtonServiceImpl实现了接口ActionButtonService,所以会使用动态代理,在进行bean的初始化后会调用各个BeanPostProcessor的postProcessAfterInitialization,ScheduledAnnotationBeanPostProcessor在AsyncAnnotationBeanPostProcessor之后运行,这个时候ScheduledAnnotationBeanPostProcessor拿到的是已经被动态代理的bean,然后ScheduledAnnotationBeanPostProcessor会对这个bean的接口的所有方法进行扫描,因为定时任务方法在接口方法找不到,所以就会报错,而原来的项目没有定义接口,所以代理只会走CGLIB,不会报这个问题。 image所有的PostProcessor
网友评论