@AutoConfigureAfter和@AutoConfigureBefore排序失效了
@AutoConfigureAfter和@AutoConfigureBefore排序失效了?
正文
聊聊SpringBoot Bean加载和 自动配置的顺序问题。
实际误区
来看看实际开发中存在的问题。我们想要的结果A-->B-->C,下面写法是最常见的错误。
@Configuration
public class ConfigurationA {ConfigurationA(){
System.out.println("CofigurationA已经被初始化!");
}
}@Configuration
@AutoConfigureAfter(ConfigurationA.class)
public class ConfigurationB {ConfigurationB(){
System.out.println("ConfigurationB已经被初始化!");
}
}@Configuration
@AutoConfigureAfter(ConfigurationB.class)
public class ConfigurationC {ConfigurationC(){
System.out.println("CofigurationC已经被初始化!");
}
}
来来来执行一把。。。。
执行结果:CofigurationA已经被初始化!
ConfigurationB已经被初始化!
CofigurationC已经被初始化!
【@AutoConfigureAfter和@AutoConfigureBefore排序失效了】WTF!!!!!!!!不是说有问题吗?
文章图片
别急!!调整下代码,想要的结果是C-->B-->A,来将代码调整下:
@Configuration
@AutoConfigureAfter(ConfigurationB.class)
public class ConfigurationA {ConfigurationA(){
System.out.println("CofigurationA已经被初始化!");
}
}@Configuration
@AutoConfigureAfter(ConfigurationC.class)
public class ConfigurationB {ConfigurationB(){
System.out.println("ConfigurationB已经被初始化!");
}
}@Configuration
public class ConfigurationC {ConfigurationC(){
System.out.println("CofigurationC已经被初始化!");
}
}
来来开席开席!!!!
执行结果:CofigurationA已经被初始化!
ConfigurationB已经被初始化!
CofigurationC已经被初始化!
从上面的结果是可以看出@AutoConfigureAfter并没有什么卵用。第一次能够得到正确结果是老天保佑。。。。。
所以来看看源码:*@AutoConfigureAfter归结来说得益于SpringBoot自动配置(@EnableAutoConfiguration); @EnableAutoConfiguration通过@Import将EnableAutoConfigurationImportSelector引入,
这边主要看下EnableAutoConfigurationImportSelector-->selectImports()方法*
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled(metadata)) {
return NO_IMPORTS;
}
try {
AnnotationAttributes attributes = getAttributes(metadata);
//加载META-INF下spring.factories配置类
List configurations = getCandidateConfigurations(metadata,
attributes);
configurations = removeDuplicates(configurations);
Set exclusions = getExclusions(metadata, attributes);
configurations.removeAll(exclusions);
//将配置类进行排序,sort方法最终会调用到AutoConfigurationSorter中的getInPriorityOrder()
configurations = sort(configurations);
recordWithConditionEvaluationReport(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}protected List getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
看到这里法外狂徒 "张三"默默的在META-INF下spring.factories加上了.......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.*******.Demo.ConfigurationA,\
com.*******.Demo.ConfigurationB,\
com.*******.Demo.ConfigurationC
张三:就这,就这......?张三启动了核按钮:
执行结果:CofigurationA已经被初始化!
ConfigurationB已经被初始化!
CofigurationC已经被初始化!
文章图片
这里就存在另一个易错点。简单的理解,当配置类在Spring扫描路径里面(scanBasePackages)会优先解析,后面在通过ImportSelector(spring.factories加载就是通过实现这个接口加载的配置类)加载进来的配置类就不会处理了,相当于一个类有两种加载方式,谁先加载谁就厉害。。。
空口无凭上菜:ConfigurationClassParser-->doProcessConfigurationClass()方法加载顺序是@ComponentScan(扫描文件路径,路径里面元注解为@Component(@Cofiguration元注解也是@Component)都会被扫描到)--->加载@Import注解(配合ImportSelector接口)--->加载 @ImportResource--->加载@Bean--->.......
大致顺序理清楚了。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}// Process any @ComponentScan annotations
Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
//@1@Configuration注解的类会被解析到
Set scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if necessary
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
//@2对加载的类进行必要的解析
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}// Process individual @Bean methods
Set beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}// No superclass -> processing is complete
return null;
}
@1会将@Configurtion注解扫描处理 @2会将@1扫描处理的类进行必要的加工,@2最后会调用到ConfigurationClassParser-->processConfigurationClass()方法
这个方法里会将已经解析的类放入configurationClasses缓存中,当相同类通过不同的方法解析的时候会进行合并或者跳过。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class;
existing non-imported class overrides it.
return;
}
else {
//省略
}
}
}// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
来来来,那个张三你知道了吗?
张三:知道了,处理方案有两种
1.将我们的配置类放在Spring扫描路径里外面(scanBasePackages)
2.如果还是想放在扫描路径里面就需要将@Configuration注解去掉
推荐阅读
- 急于表达——往往欲速则不达
- 第三节|第三节 快乐和幸福(12)
- 20170612时间和注意力开销记录
- 2.6|2.6 Photoshop操作步骤的撤消和重做 [Ps教程]
- 对称加密和非对称加密的区别
- 眼光要放高远
- 樱花雨
- 前任
- 2020-04-07vue中Axios的封装和API接口的管理
- 烦恼和幸福