本文基于:spring-boot-2.3.12-RELEASE、spring-cloud-Hoxton.SR12(即2.2.9-RELEASE)
在看spring cloud eureka源码的时候,发现一段有意思的代码:
1、spring cloud eureka的spi配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
2、我们来看看 EurekaServerAutoConfiguration 这个类
@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
// 重点在这里
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
@OnBeanCondition是springboot的注解,这个注解在后面有用
看来 EurekaServerAutoConfiguration 的解析和加载 依赖 EurekaServerMarkerConfiguration.Marker 的解析和加载
3、我们再看看 EnableEurekaServer 和 EurekaServerMarkerConfiguration.Marker.class 这个类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
@Import 引入 EurekaServerMarkerConfiguration.class
package org.springframework.cloud.netflix.eureka.server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Responsible for adding in a marker bean to activate
* {@link EurekaServerAutoConfiguration}.
*
* @author Biju Kunjummen
*/
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker { }
}
通过上面的代码看出来:
1、EurekaServerMarkerConfiguration是一个 被 @Configuration 标注的配置类【重要关注点】
2、 Marker 是通过 @Bean注解引入的
我们知道只要加了 @EnableEurekaServer 这个注解(这个注解是一个复合注解,会@Inport EurekaServerMarkerConfiguration 这个类),springbot就会执行eurrka的自动装配类;
这个说法是没有问题的,但是很多人会忽略一个问题:为什么能保证 Marker 这个类会在EurekaServerAutoConfiguration 前被加入容器?
原因分析:
-
两者都属于注解驱动的,那么加载顺序受到 ConfigurationClassPostProcessor 的绝对控制。这个后置处理器的源码分析可以看我这一篇 Spring之ConfigurationClassPostProcessor配置类后置处理器源码分析
-
引入路径可以简单理解成如下:
- Marker 的 引入路径:@EnableEurekaServer -> @Import(EurekaServerMarkerConfiguration.class)
- EurekaServerAutoConfiguration 加载路径:@SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class) -> selectImports 方法计算引入
因为 @EnableEurekaServer 和 @SpringBootApplication 都是@Import的复合注解,可以直接看成@Import注解;
-
ConfigurationClassPostProcessor 解析器会 递归解析 该类引入的各种类,限被
@Component
@PropertySources
@ComponentScan、@ComponentScans注解的类
@Import注解的类
@ImportResource
@Bean
等注解引入的类 -
核心:
@Import(EurekaServerMarkerConfiguration.class)和@Import(AutoConfigurationImportSelector.class) 的解析优先级顺序决定了EurekaServerMarkerConfiguration.Marker和EurekaServerAutoConfiguration的先后顺序!
核心代码如下:
配置类总入口:
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
然后该方法会调用到
org.springframework.context.annotation.ConfigurationClassParser#getImports
主要功能是获得启动类上被@Import的类,进而解析
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
debug如下:
根据主类解析到了这两个类:EurekaServerMarkerConfiguration 和 AutoConfigurationImportSelector ,imports 里元素顺序会跟我们的注解顺序有关系,但不影响后续解析;
接下来是最最最复杂的方法 org.springframework.context.annotation.ConfigurationClassParser#processImports,不得不说框架里的各种递归看的人头晕:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// @A
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
// @B
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// @C
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
通过我另一篇文章 Spring之ConfigurationClassPostProcessor配置类后置处理器源码分析
可知
1、@A和 @B的分支都不会立即注册beandefinition,而是将解析到的@Import(ImportSelector)和@Import(ImportBeanDefinitionRegistrar)缓存起来延迟处理,在ConfigurationClassParser#parse方法最后将其解析;
延迟处理的代码如下:
ConfigurationClassParser#parse
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// @ 处理需要延迟处理的类
this.deferredImportSelectorHandler.process();
}
2、相反在@C分支处会直接注册bendefinition,即被@Import(@Configuration)的类;就是说上面的EurekaServerMarkerConfiguration会走这个分支,然后会将这个bean放到 parser.getConfigurationClasses() 中;
3、核心来了,这个延迟处理机制就保证了被@Import的 Configuration 类会比 被@Import的其他类优先处理被定义成beandefinition,EurekaServerAutoConfiguration就是后者,因为它是被@Import({AutoConfigurationImportSelector.class})后续处理得到的,AutoConfigurationImportSelector
4、代码回到总入口 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions 这个配置类解析总入口,我截取核心的地方:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// ...省略...
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// @A 核心代码
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// ...省略...
}
while (!candidates.isEmpty());
// ...省略...
}
上面标准 标注@A 的地方会把按【优先级】排好的configClasses注册成beandefinition,当然这个方法里会用到最开始提到的 @OnBeanCondition注解,对bean进行评估后注册!
总结:EurekaServerMarkerConfiguration.Marker 会比 EurekaServerAutoConfiguration先解析入容器!
over~~