目录
1.springboot启动类样例
package com.chr.test.testspringboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringbootApplication.class, args);
}
}
2. springboot自动装配原理分析
从上面的启动类上看,有个@SpringBootApplication的很重要的注解,我们就从这个注解开始分析springboot自动装配的原理。
从图上我们看到注解是在spring-boot-autoconfigure jar包里实现的。
我们看到@SpringBootApplication上面还有很多组合的注解:
@SpringBootConfiguration (表明这是一个配置类,此注解最终会用到 @Configuration注解)
@EnableAutoConfiguration (开启自动装配,重要,我们接下来哟啊重点分析的注解)
@ComponentScan( (扫描主类所在的同级包以及下级包里的Bean)
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
接下来我们重点分析@EnableAutoConfiguration注解。
@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我们看到了重要的注解:
@Import({AutoConfigurationImportSelector.class})
Import注解的原理分析可以参考我的上篇文章:
https://blog.csdn.net/hairongok353043059/article/details/111473425
在这里通过@Import注解引入了AutoConfigurationImportSelector这个类到spring容器中。
我们来分析AutoConfigurationImportSelector这个类:
我们看到AutoConfigurationImportSelector实现了DeferredImportSelector接口,而DeferredImportSelector实现自ImportSelector接口。
在分析@Import注解的时候,我们分析了通过ImportSelector这种方式引入对象来ioc容器中,是通过在实现方法selectImports方法中实现的。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//在这里收集需要自动装配的类
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
进入 this.getAutoConfigurationEntry(annotationMetadata);这个方法:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//在这里找到所有需要自动装配的候选配置类集合
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
进入getCandidateConfigurations()方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//通过SpringFactoriesLoader来加载需要自动装配的配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
我们继续进入SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//开始加载配置类
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//类加载器找到META-INF/spring.factories目录下的文件,开始解析数据。吧对应的类放入list中
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
AutoConfigurationImportSelector中的方法getCandidateConfigurations,得到待配置的class的类名集合,这个集合就是所有需要进行自动配置的类,而是否配置的关键在于META-INF/spring.factories文件中是否存在该配置信息
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> 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;
}
打开,如下图可以看到所有需要配置的类全路径都在文件中,每行一个配置,多个类名逗号分隔,而\表示忽略换行。
3.总结图
如下图来源于网络:
SpringBoot自动化配置关键组件关系图
mybatis-spring-boot-starter、spring-boot-starter-web等组件的META-INF文件下均含有spring.factories文件,自动配置模块中,SpringFactoriesLoader收集到文件中的类全名并返回一个类全名的数组,返回的类全名通过反射被实例化,就形成了具体的工厂实例,工厂实例来生成组件具体需要的bean。
4.springboot 自动装配用到的条件化配置
SpringBoot运用条件化配置的方法,定义多个特殊的条件化注解,并将其用到配置类上。
SpringBoot提供的条件化注解:
条件化注解 | 配置生效条件 |
@ConditionalOnBean | 配置了某个特定Bean |
@ConditionalOnMissingBean | 没有配置特定的Bean |
@ConditionalOnClass | Classpath里有指定的类 |
@ConditionalOnMissingClass | Classpath里缺少指定的类 |
@ConditionalOnExpression | 给定的SpEL表达式计算结果为true |
@ConditionalOnJava | Java的版本匹配特定值或者一个范围值 |
@ConditionalOnJndi | 参数中给定的JNDI位置必须存在一个,如果没有参数,则需要JNDI InitialContext |
@ConditionalOnProperty | 指定的配置属性要有一个明确的值 |
@ConditionalOnResource | Classpath里有指定的资源 |
@ConditionalOnWebApplication | 这是一个Web应用程序 |
@ConditionalOnNotWebApplication | 这不是一个Web应用程序 |
springboot 条件装配原理,我们以@ConditionalOnBean为例开始分析此注解。
@ConditionalOnBean注解的作用是:当给定的在bean存在时,则实例化当前Bean,否则不实例化。
4.1 @ConditionalOnBean 使用例子
city类需要用到province类对象。
pojo类:
import lombok.*;
/**
* @author chenhairong3
* @description 省份
* @date 2020/12/28
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Province {
/**
* 省份名称
*/
private String name;
}
import lombok.*;
/**
* @author chenhairong3
* @description 城市
* @date 2020/12/28
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class City {
/**
* city需要provice的一个对象
*/
private Province province;
/**
* 城市名称
*/
private String name;
}
配置类:
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author chenhairong3
* @description
* @date 2020/12/28
*/
@Configuration
public class ConfigTest {
@Bean
public Province provice() {
Province province = new Province();
province.setName("陕西省");
return province;
}
@Bean
public City city(Province province) {
province.setName("陕西省1");
return new City(province,"西安市");
}
}
springboot启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author chenhairong3
* @description
* @date 2020/12/28
*/
@SpringBootApplication
public class ApplicaitonConfig {
public static void main(String[] args) {
SpringApplication.run(ApplicaitonConfig.class,args);
}
}
编写测试类:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author chenhairong3
* @description
* @date 2020/12/28
*/
@SpringBootTest(classes = ApplicaitonConfig.class)
@RunWith(SpringRunner.class)
public class TestMain {
/**
* 注意这里,就是一旦使用@Autowired那就默认代表当前Bean一定是已经存在的,如果为null,会报错。
* 所以这里要修改下。@Autowired(required=false) //required=false 的意思就是允许当前的Bean对象为null。
*/
@Autowired(required=false)
private City city;
@Test
public void test() {
System.out.println("= = = = = = = = = = = = = ");
System.out.println("city = " + city);
System.out.println("= = = = = = = = = = = = = ");
}
}
输出结果:
= = = = = = = = = = = = =
city = City(province=Province(name=陕西省1), name=西安市)
= = = = = = = = = = = = =
现在我们注释掉配置类里面province 这个bean的代码:
再次执行测试类代码:
发现报错,提示需要定义个Province类型的bean.
现在我们给city这个bean上面加上@ConditionalOnBean这个注解:
再来执行测试类看输出结果:
发现不再报错,输出了city=null.说明注解起作用了。当给定的在bean(这里是Province)不存在时,则不实例化当前对象(这里是city)实例。
4.2 @ConditionalOnBean 源码分析
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
}
ConditionalOnBean本质上用到了Conditional注解。
我们再看看下ConditionalOnBean的用法,我们看到了熟悉的bean注解。
我们先来猜一下springboot到底会如何实现?我们看到了Bean注解,在之前分析configuration注解的时候,我们知道有一个很重要的类:ConfigurationClassPostProcessor类。
在这个类的parse方法中会解析bean注解等。然后loadBeanDefinitions 加载并注册beanDefinition.
如果感兴趣Configuration注解的原理,可以看以前的文章:
Configuration注解分析原文链接:
https://blog.csdn.net/hairongok353043059/article/details/111329646
我们来看ConfigurationClassPostProcessor类:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList();
String[] candidateNames = registry.getBeanDefinitionNames();
String[] var4 = candidateNames;
int var5 = candidateNames.length;
for(int var6 = 0; var6 < var5; ++var6) {
String beanName = var4[var6];
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (!ConfigurationClassUtils.isFullConfigurationClass(beanDef) && !ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
if (!configCandidates.isEmpty()) {
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry)registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator)sbr.getSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator");
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
HashSet alreadyParsed = new HashSet(configCandidates.size());
do {
//在parse方法中解析bean注解的方法。
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
}
//在这里加载beanDefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet();
Iterator var12 = alreadyParsed.iterator();
while(var12.hasNext()) {
ConfigurationClass configurationClass = (ConfigurationClass)var12.next();
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
String[] var23 = newCandidateNames;
int var24 = newCandidateNames.length;
for(int var14 = 0; var14 < var24; ++var14) {
String candidateName = var23[var14];
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
} while(!candidates.isEmpty());
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory)this.metadataReaderFactory).clearCache();
}
}
}
继续进入标红的parse方法:最终调到如下的方法:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
return;
}
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass);
do {
//重点关注doProcessConfigurationClass方法
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass);
} while(sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
}
在doProcessConfigurationClass方法中:
@Nullable
protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass) throws IOException {
this.processMemberClasses(configClass, sourceClass);
Iterator var3 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();
AnnotationAttributes importResource;
while(var3.hasNext()) {
importResource = (AnnotationAttributes)var3.next();
if (this.environment instanceof ConfigurableEnvironment) {
this.processPropertySource(importResource);
} else {
this.logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
}
}
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
Iterator var13 = componentScans.iterator();
while(var13.hasNext()) {
AnnotationAttributes componentScan = (AnnotationAttributes)var13.next();
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
Iterator var7 = scannedBeanDefinitions.iterator();
while(var7.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var7.next();
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
this.parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
this.processImports(configClass, sourceClass, this.getImports(sourceClass), true);
importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
String[] var19 = resources;
int var21 = resources.length;
for(int var22 = 0; var22 < var21; ++var22) {
String resource = var19[var22];
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
//解析bean注解的方法,我们例子中的city bean就是这个时候被解析被放入configClass.addBeanMethod中。
Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
Iterator var17 = beanMethods.iterator();
while(var17.hasNext()) {
MethodMetadata methodMetadata = (MethodMetadata)var17.next();
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
this.processInterfaces(configClass, sourceClass);
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
return sourceClass.getSuperClass();
}
}
return null;
}
parse方法看完了,我们回到ConfigurationClassPostProcessor类中的loadBeanDefinitions方法:
继续跟进下去:
终于找到加载beanMethod的地方了:
我们看到了shouldSkip方法,debug的时候,发现methodName为city的时候,方法进入。
然后我们重点进入shouldSkip方法。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
//metadata.isAnnotated(Conditional.class.getName()) 这里我们看到了熟悉的Conditional注解。这里bean上是否包含Conditional注解,如果是,则获取注解上的conditionClass 。
构建condition : Condition condition = this.getCondition(conditionClass, this.context.getClassLoader()); 这里判断condition的条件是否满足:condition.matches(this.context, metadata)。来决定是否要加载指定的bean
if (metadata != null && metadata.isAnnotated(Conditional.class.getName())) {
if (phase == null) {
return metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata)metadata) ? this.shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION) : this.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
} else {
List<Condition> conditions = new ArrayList();
Iterator var4 = this.getConditionClasses(metadata).iterator();
while(var4.hasNext()) {
String[] conditionClasses = (String[])var4.next();
String[] var6 = conditionClasses;
int var7 = conditionClasses.length;
for(int var8 = 0; var8 < var7; ++var8) {
String conditionClass = var6[var8];
Condition condition = this.getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
var4 = conditions.iterator();
Condition condition;
ConfigurationPhase requiredPhase;
do {
do {
if (!var4.hasNext()) {
return false;
}
condition = (Condition)var4.next();
requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition)condition).getConfigurationPhase();
}
} while(requiredPhase != null && requiredPhase != phase);
} while(condition.matches(this.context, metadata));
return true;
}
} else {
return false;
}
}
最终 是从beanFactory容器中判断是否存在provice的bean,这里发现不存在,所以不匹配,故而city对应的beanDefinition不会被加载。也就不被创建和实例化了。
4.3 @ConditionalOnBean 的注意事项
@Configuration
public class ConfigTest {
@Bean
@ConditionalOnBean(name ="province")
public City city(Province province) {
province.setName("陕西省1");
return new City(province,"西安市");
}
@Bean
public Province provice() {
Province province = new Province();
province.setName("陕西省");
return province;
}
}
大家猜猜,我放开了provice的bean代码,这里city一定会被创建吗?
答案是不一定.
通过刚才的代码分析,
在spring ioc
的过程中,优先解析@Component,@Service,@Controller
注解的类。其次解析配置类,也就是@Configuration
标注的类。最后开始解析配置类中定义的bean
。
示例代码中city
是定义在配置类中的,当执行到配置类解析的时候,@Component,@Service,@Controller ,@Configuration
标注的类已经全部扫描,所以这些BeanDifinition
已经被同步。 但是city的条件注解依赖的是province,province是被定义的配置类中的,所以此时配置类的解析无法保证先后顺序,就会出现不生效的情况。
如果想要生效:完全可以用@ConditionalOnClass(Province.class)
来代替。