【源码】Spring —— @Configuration 1 ConfigurationClassPostProcessor

前言

@Configuration 注解注释的类,我们一般称之为 配置类,实际上,同样被 @Import @ComponentScan 等注解注释的类,甚至一个含有 @Bean 注解注释方法的 普通类 也算是一个 配置类,官方以 FULLLITE 区分它们

  • FULL 配置类Spring 会为我们生成它的代理类,代为管理 bean 方法调用的依赖关系。换句话说,在该配置类中调用其他 @Bean 注解注释的方法,会转而去 容器 中获取对应的 bean 实例,而不是再创建一次
  • LITE 配置类:即普通的配置类,Spring 不会代理它,进而换取的是性能开销上的节省

版本

springframework:5.2.x

@Configuration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	// 配置类对应的 beanName
	@AliasFor(annotation = Component.class)
	String value() default "";

	// 是否代理 bean 方法,默认 true,false 时该配置类相当于 Lite Mode
	// @since 5.2
	boolean proxyBeanMethods() default true;

}

其中,proxyBeanMethods 属性自 5.2 后提供,默认 truefalse 时不对该配置类进行代理,即 LITE MODE

ConfigurationClassPostProcessor

ConfigurationClassPostProcessor
ConfigurationClassPostProcessorSpring 提供的 唯一的BeanDefinitionRegistryPostProcessor ,它的调用发生在 AbstractApplicationContext#refresh 方法的第五步 invokeBeanFactoryPostProcessors 方法中,该方法的部分逻辑不完全地概括如下:

  1. 执行所有容器中 BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry 方法
  2. 执行所有容器中 BeanDefinitionRegistryPostProcessorpostProcessBeanFactory 方法
  3. 执行所有容器中 BeanFactoryPostProcessorpostProcessBeanFactory 方法
关于更多细节,可以阅读下面的文章

【Spring】ApplicationContext 二 AbstractApplicationContext

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		
		// 已解析
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);

		// 开始解析
		processConfigBeanDefinitions(registry);
	}

解析发生在 processConfigBeanDefinitions 方法

ConfigurationClassPostProcessor#processConfigBeanDefinitions

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();

		/**
		 * 获取所有的 BeanDefinitionName 作为备选
		 * 这个地方,一般就是 spring 内部注册的一些 BeanDefinition
		 * 		和我们的 主(配置)类
		 */
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);

			// 已经进行过解析
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				// ...
			}

			// 如果是配置类,则加入备选,无论 FULL or LITE
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// 没有需要解析的配置类
		if (configCandidates.isEmpty()) {
			return;
		}

		// 排序
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});
	
		// 略

		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 解析配置类
			parser.parse(candidates);

			/**
			 * 校验
			 * 如果是 FULL 配置类
			 * 则类不能为 final,方法需要被复写
			 */
			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
			/**
			 * configClasses 中的 BeanDefinition
			 * 		由 ConfigurationClassBeanDefinitionReader 解析
			 */
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();

			/**
			 * registry.getBeanDefinitionCount() > candidateNames.length
			 * 		意味着容器中又注册了新的 BeanDefinition(一般不会)
			 * 我们需要拿出新注册的这些(可能是配置类),再进行解析
			 */
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);

						/**
						 * 新注册的 BeanDefinition 是配置类,且之前没解析过
						 * 则继续解析,直到 candidates 为空
						 */
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// 略
		
	}

因为方法篇幅过长,略去相对无关的代码,则主要逻辑概括为:

  • 从目前为止容器中注册的配置类中筛选配置类,在 SpringBoot 环境中,这里一般就是主类
  • 过滤备选配置类,主要委托给了 ConfigurationClassUtils#checkConfigurationClassCandidate 方法,核心逻辑如下:
		Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		// 有 Configuration 注解且 proxyBeanMethods 属性为 true,即 FULL
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		/**
		 * 1)不是接口
		 * 2)标注有 Component ComponentScan Import ImportResource 注解
		 * 3)还有标注了 Bean 注解的方法
		 * 则为 LITE
		 */
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		// 否则不是配置类
		else {
			return false;
		}
  • 交给 ConfigurationClassParser 类解析配置类,解析过程可能会注册必要的 BeanDefinition,同时所有的解析类会被封装到 parser 中,对于解析的细节我们在下文详解
  • 解析完后进行必要的校验
  • ConfigurationClassBeanDefinitionReader 注册所有配置类中对应的 BeanDefinition,比如 @Import@Bean 等注解的处理
  • 如果注册的 BeanDefinition 里又有新的 配置类,继续解析,但一般情况这里不会再有新增的配置类了

ConfigurationClassBeanDefinitionReader#loadBeanDefinitions

刚才提到,在 ConfigurationClassParser 解析完 配置类 后,由 ConfigurationClassBeanDefinitionReader 注册其中的 BeanDefinition

	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();

		/**
		 * 遍历所有 ConfigurationClass 进行处理
		 */
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

	------ loadBeanDefinitionsForConfigurationClass ------

	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}

		/**
		 * 对于被 Import 的类:
		 * 1)比如普通成员内部配置类
		 * 2)被 @Import 的类本身
		 * 注册对应的 BeanDefinition
		 */
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}

		/**
		 * 处理所有 @Bean 对应的方法
		 * 注册对应的 ConfigurationClassBeanDefinition
		 */
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		// 处理 @ImportResource
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());

		// 执行 ImportBeanDefinitionRegistrar#registerBeanDefinitions
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

解析出来的 BeanDefinition 是在这里按顺序注册的:

  1. Imported 类的注册,这里涉及的场景比如:
    1. 配置类的普通成员内部类(Inner Class
    2. @Import 注解引入的类本身
  2. @Bean 方法以 factoryMethod 形式注册的 Bean 组件
  3. @ImportResource 对应的 BeanDefinition
  4. ImportBeanDefinitionRegistrar#registerBeanDefinitions 的执行

总结

可以看到,配置类的解析最终是交由 ConfigurationClassParser 来处理的,该过程发生在 容器启动的很早期,此时无论 BeanPostProcessor 还是自定义的 Bean 实例都没有初始化

下一篇:【源码】Spring —— @Configuration 2 ConfigurationClassParser 、Static 关键字提高配置类优先级

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值