Spring Cloud 之 EurekaServerMarkerConfiguration.Marker 与 EurekaServerAutoConfiguration神仙打架

本文基于: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~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值