【源码】Spring —— @Configuration 2 ConfigurationClassParser、Static 修饰配置类的优先级

前言

配置类的解析最终是交由 ConfigurationClassParser 来处理的,本章节结合 ConfigurationClassParser 源码解读 配置类 解析的部分细节

版本

springframework:5.2.x

ConfigurationClassParser

	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) {
				
				// ...
				
			}
		}

		// deferredImportSelector 最后才处理
		this.deferredImportSelectorHandler.process();
	}

	-------------------------------------------------

	protected final void parse(@Nullable String className, String beanName) throws IOException {
		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
		processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
	}

	protected final void parse(Class<?> clazz, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
	}

	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
	}
  • parse(Set<BeanDefinitionHolder> configCandidates) 方法是对外的入口,接收配置类集合并进行解析
  • 根据配置类的 BeanDefinition 分发给对应的 parse
  • 这组方法的性质一致,都是入口级别
  • 最终都是委托到 processConfigurationClass 方法

ConfigurationClassParser#processConfigurationClass

	protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
		// Condition 条件注解过滤
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			/**
			 * 如果之前已经解析过,且当前该类是被 Import 的:
			 * 1)之前的类也是被 Import 的,那么合并 ImportedBy
			 * 		场景如:一个配置类被重复 import
			 * 2)之前的类并不是 Import 的,那就已经处理过了,什么也不干
			 * 		场景如:扫描路径下组件的静态内部类,在被作为成员类处理
			 * 			之前已经被处理过
			 */
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				return;
			}
			/**
			 * 如果之前已经解析过,但当前该类不是被 Import 的,
			 * 		那么进行覆盖处理(即重新解析)
			 */
			else {
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}
		
		// 递归解析,此处指递归父类
		SourceClass sourceClass = asSourceClass(configClass, filter);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);

		// 配置类的解析完成后才把它放到 configurationClasses 中
		this.configurationClasses.put(configClass, configClass);
	}
  • processConfigurationClass 方法是最全周期的配置类处理方法,它包括
  • 依赖 conditionEvaluator 进行条件过滤决定是否继续解析
  • 重复 import 的处理
  • 递归解析配置类
  • 对上述步骤完成后才把它放到 configurationClasses 集合中,也就是说过程中解析的配置类是优先于当前配置类的

ConfigurationClassParser#doProcessConfigurationClass

所谓的配置类解析,实际上就是分别解析配置类的 成员类@PropertySource 注解、@ComponentScan 注解、@Import 注解、@ImportResource 注解 以及 @Bean 注解的方法,因此我们一个个解读

成员类解析

	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
		// 解析成员类
		processMemberClasses(configClass, sourceClass, filter);
	}

	---------------- processMemberClasses ----------------

	private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
			Predicate<String> filter) throws IOException {

		// 获取所有成员类
		Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
		if (!memberClasses.isEmpty()) {
			List<SourceClass> candidates = new ArrayList<>(memberClasses.size());

			/**
			 * 遍历所有成员类,如果是配置类,则加入 candidates
			 */
			for (SourceClass memberClass : memberClasses) {
				if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
						!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
					candidates.add(memberClass);
				}
			}
			OrderComparator.sort(candidates);
			for (SourceClass candidate : candidates) {

				/**
				 * 利用 importStack 解决“重复解析”的问题
				 * 解析前将其压入 importStack
				 * 如果在出栈之前又被解析到,说明存在“循环引入”
				 */
				if (this.importStack.contains(configClass)) {
					this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
				}
				else {
					this.importStack.push(configClass);
					try {
						// 嵌套解析目标成员配置类
						processConfigurationClass(candidate.asConfigClass(configClass), filter);
					}
					finally {
						this.importStack.pop();
					}
				}
			}
		}
	}
  • 针对目标成员类中的 配置类,递归解析,这里的成员类包括 静态内部类普通内部类,且这里的 普通内部类 优先级是高于 静态内部类
  • 利用 importStack 解决 “重复解析” 的问题
  • 对满足条件的成员配置类,是基于 processConfigurationClass 方法嵌套处理,不难理解,其对应 BeanDefinition 的注册是先于主类的(当然不包括主配置类被注册的场景,比如主配置类是被路径扫描的)
关于解析成员配置类时 普通内部类 优先级高于 静态内部类 是个人验证的结果,没
有刻意去找官方结论

@PropertySource(s) 注解解析

	for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), PropertySources.class,
			org.springframework.context.annotation.PropertySource.class)) {
		if (this.environment instanceof ConfigurableEnvironment) {
			processPropertySource(propertySource);
		}
		else {
			
			// ...
			
		}
	}

该注解用于从外部导入配置文件,从注解信息中获取对应路径的配置文件资源,解析成对应的 PropertySource,维护到 Environment

关于 PropertySource,可以参考

【Spring】PropertySource 的解读与示例(MapPropertySource CommandLinePropertySource)

@ComponentScan(s) 注解解析

	// 条件过滤
	if (!componentScans.isEmpty() &&
			!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {

		/**
		 * 遍历所有 componentScan
		 * 交给 ComponentScanAnnotationParser componentScanParser 解析
		 */
		for (AnnotationAttributes componentScan : componentScans) {
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
			/**
			 * 解析到的所有 BeanDefinition,如果是配置类,
			 * 		则继续进行解析
			 */
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
				BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = holder.getBeanDefinition();
				}
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}

ComponentScanAnnotationParser componentScanParser 扫描所有的 componentScan

  • 注册 对应的 BeanDefinition 并返回
  • 对于其中的 配置类,继续进行解析

整体上其实很好理解,但这里需要指出一个细节,可以引申如下 重要结论扫描路径下 的配置类 Static 关键字修饰的 静态内部配置类 会被优先解析(注册),这其中的缘由需要追踪到方法 ComponentScanAnnotationParser#parse

ComponentScanAnnotationParser#parse
	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

		// ...
		
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

	----------------------- doScan -----------------------

	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			// 获取所有的备选配置类
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				
				// ... 遍历处理注册对应的 BeanDefinition 
				
			}
		}
		return beanDefinitions;
	}

	-------------- findCandidateComponents --------------

	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
			return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
		}
		else {
			return scanCandidateComponents(basePackage);
		}
	}

	-------------- scanCandidateComponents --------------

	private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;

			// 读取所有的资源类
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

						/**
						 * 过滤判断一
						 * 1)非主类,因为一般是从主类扫描过来的,所以肯定排除自己
						 * 2)标注了 @Component 元注解的也算,即 @Configuration 注解也算)
						 * 3)或 标注有 javax.annotation.@ManagedBean 注解
						 */
						if (isCandidateComponent(metadataReader)) {
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setSource(resource);

							/**
							 * 过滤判断二
							 * AnnotationMetadata metadata = beanDefinition.getMetadata();
							 * return (metadata.isIndependent() && (metadata.isConcrete() ||
							 * 		(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
							 *
							 * isIndependent():Top Level Class or Nested Class
							 * Top Level Class:顶层类,即普通类
							 * Inner Class:非静态内部类
							 * Nested Class:嵌套类(静态内部类)
							 * Local Class:方法内声明的局部类
							 * Anonymous Class:匿名类
							 *
							 * isConcrete:isInterface or isAbstract
							 */
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							else {
								
								// ...
								
							}
						}
						else {
							
							// ...
							
						}
					}
					catch (Throwable ex) {
						
						// ...
						
					}
				}
				else {
					
					// ...
					
				}
			}
		}
		catch (IOException ex) {
			
			// ...
			
		}
		return candidates;
	}

调用链路很长,我们作一个总结:

  1. 首先对于所有的 componentScan 都是由 ClassPathBeanDefinitionScanner 来扫描的
  2. 其次,ClassPathBeanDefinitionScanner 在注册对应的 BeanDefinition 前肯定是要扫描出所有的备选类,这部分逻辑聚焦于方法 scanCandidateComponents
    1)加载所有的资源类,见图
    在这里插入图片描述
    可以看到内部静态类是优先于主类被加载的
    2)加载的所有资源类,要进行过滤筛选:首先,源头类(主类)肯定被排除,其次,至少要被元注解 @Component 标注。
    3)同时,成员内部类被过滤,这里也很好理解:成员类的解析已经发生在之前的 processMemberClasses 方法了(但对应 BeanDefinition 的注册在后面)
    4)配置类被加载后,对应的 BeanDefinition 被注册,其中如果存在配置类,同样会被解析(递归)
    5)因此,扫描路径下的配置类本身及其静态内部类的 BeanDefinition 在此处注册,且 静态内部类优先于主类,而 成员内部类的注册发生在之后

@Import 注解解析

	processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

	----------------- 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) {
					/**
					 * 如果是个 ImportSelector
					 * 对于 DeferredImportSelector,则会延迟处理
					 * 对于其他 ImportSelector,调用 selectImports 方法获取
					 * 		所有类名,然后递归 processImports
					 */
					if (candidate.isAssignable(ImportSelector.class)) {
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						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);
						}
					}

					/**
					 * 如果是个 ImportBeanDefinitionRegistrar
					 * 则 configClass.addImportBeanDefinitionRegistrar
					 * 		(registrar, currentSourceClass.getMetadata())
					 * 后续 loadBeanDefinition 阶段处理
					 */
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}

					/**
					 * 如果既不是 ImportSelector 也不是 ImportBeanDefinitionRegistrar
					 * 则直接当作一个 配置类 解析
					 * 大多数 ImportSelector 都是递归到这
					 */
					else {
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				
				// ...
				
			}
			finally {
				this.importStack.pop();
			}
		}
	}
  • getImports 获取所有需要被 import 的配置类,此处实际是 @Import 注解的递归解析,比如 Spring Boot auto-configure 的解析就发生在此处
  • 对于 DeferredImportSelector,此处收集起来,上文提到在所有相关解析完成后才会统一处理这部分 DeferredImportSelector,这实际上是为了实现 Conditional 条件注入,Spring Boot 的强大依托于此
  • 对于其他 ImportSelector,则获取其返回的类名,继续进行 processImports 处理,当然最终都是递归成其他几种情况
  • 对于 ImportBeanDefinitionRegistrar,此处也是收集起来,但它的处理时机在最最后
  • 对于普通类,则直接作为配置类再次 processConfigurationClass,因此也不难理解,@Import 的配置类解析(注册)都在主配置类之前(当然不包括主配置类被注册的场景,比如主配置类是被路径扫描的)

@ImportResource 注解解析

	AnnotationAttributes importResource =
			AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
	if (importResource != null) {
		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);
		}
	}

该注解用于导入其他 配置资源(比如 .groovy .xml),此处解析其属性,并最终维护在当前 ConfigurationClass importedResources 属性中后续处理

@Bean 注解方法解析

	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}

所有的方法会被封装成 BeanMethod 实例,并最终维护在当前 ConfigurationClassSet<BeanMethod> beanMethods 属性中后续处理

接口方法解析

	processInterfaces(configClass, sourceClass);

	----------------- processInterfaces -----------------

	private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
		/**
		 * 对于所有接口中注解了 @Bean 的默认方法,也会进行处理
		 */
		for (SourceClass ifc : sourceClass.getInterfaces()) {
			Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
			for (MethodMetadata methodMetadata : beanMethods) {
				if (!methodMetadata.isAbstract()) {
					configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
				}
			}
			processInterfaces(configClass, ifc);
		}
	}

对于接口中标注了 @Bean 的默认方法,也会进行处理

父类处理

	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;

继续 doProcessConfigurationClass 处理 配置类 的父类,如果没有,则返回 null 打断最外层的递归

总结

上述就是 ConfigurationClassParser 的解析细节,其中就 static 修饰配置类的场景做一些讨论,以 Spring Boot 场景为主:

  • 大多数的场景,我们自定义的配置组件都是从主类以 @ComponentScan 的形式被扫描进去的,结合上文分析,此时最终配置类(及其内部 BeanDefinition)注册顺序为:static 修饰的内部配置类 先于 Top Level 配置类 先于 普通内部配置类 先于 Top Level 配置类 @Import 的配置类
  • 配置类被 @Import 的场景,大多数即 Spring Boot 自动装配 的配置类,其优先级为 普通内部配置类 先于 static 修饰的内部配置类 先于 Top Level 配置类 @Import 的配置类 先于 Top Level 配置类

可以看到,只有 static 修饰的内部配置类 是稳定先于 Top Level 配置类 的,可能这就是为什么绝大多数的装配类都是以 static 修饰的内部配置类 的形式定义的吧

因此 @AutoConfigureBefore + @AutoConfigureAfter + static关键字 就可以控制自动装配
的顺序以完成各种场景的组件注册

个人认为应该尽量避免以其他机制比如 @Import 来控制,原因如上

上一篇:【源码】Spring —— @Configuration 1 ConfigurationClassPostProcessor

下一篇:【源码】Spring —— @Configuration 3 FULL 配置类的原理

参考

static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值