Spring Framework 7.0.9 深度剖析-05-配置元数据解析:XML、注解与 JavaConfig

Spring Framework 7.0.9 深度剖析-05-配置元数据解析:XML、注解与 JavaConfig

1. 本章导读

Spring 容器启动后做的第一件正事,不是创建 bean,而是收集配置元数据 。一个 <bean id="xxx" class="..."/>、一个标注了 @Component 的类、一个带 @Bean 的 @Configuration 类------这三者在写法上毫无相似之处,但在容器内部最终都会被翻译成同一种东西:BeanDefinition(bean 定义)。本章要讲透的,就是这三条"从文本/类字节码到 BeanDefinition 集合"的完整流水线,每一步都回到 7.0.9 的真实源码。

本章涉及的模块与核心类:

  • spring-beans (XML 解析全部在这个模块):spring-beans/src/main/java/org/springframework/beans/factory/xml/ 下的 XmlBeanDefinitionReader、DefaultBeanDefinitionDocumentReader、BeanDefinitionParserDelegate、NamespaceHandlerSupport、DefaultNamespaceHandlerResolver;以及 spring-beans/src/main/java/org/springframework/beans/factory/support/ 下的 AbstractBeanDefinitionReader、BeanDefinitionReaderUtils。
  • spring-context (注解扫描与 JavaConfig 全部在这个模块):spring-context/src/main/java/org/springframework/context/annotation/ 下的 ClassPathBeanDefinitionScanner、ClassPathScanningCandidateComponentProvider、ComponentScanAnnotationParser、ConfigurationClassPostProcessor、ConfigurationClassParser、ConfigurationClassBeanDefinitionReader、ConfigurationClassEnhancer、AnnotatedBeanDefinitionReader。

这里有一个必须先点破的模块边界:XML 的 DOM 解析逻辑全部在 spring-beans,而 context:component-scan 这个"XML 标签"对应的处理器却在 spring-context 。原因是 spring-beans 定义"如何解析 XML"的骨架(命名空间机制),而各命名空间的具体处理器(context、jee、lang、task、cache)作为插件放在各自的模块里,运行时通过 META-INF/spring.handlers 文件装配。搞错这个边界,读源码时会一路串味。

前置知识:读者应知道 BeanFactory 是从 BeanDefinition 注册表中读元数据、再实例化 bean 的;知道 refresh() 的存在,但本章只关心 refresh() 中 invokeBeanFactoryPostProcessors 这一个阶段------因为配置元数据解析就发生在这里(以及更早的 refresh() 之前的 register/scan 阶段)。

读完本章你应能回答:一个 XML 文件从 Resource 变成注册表中几百条 BeanDefinition 经过了哪些方法?@Component 扫描为什么不需要加载类(ASM 是什么角色)?@Configuration 类为什么会被 CGLIB 增强、而 proxyBeanMethods=false 又为什么能逃掉这一步?@Import 的三种形态在 ConfigurationClassParser.processImports 里如何分叉?同名 bean 被 XML 与 @Bean 同时声明时谁赢?

2. 核心概念

2.1 一切元数据的归宿:BeanDefinition

org.springframework.beans.factory.config.BeanDefinition(spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java)是一个描述"如何创建并管理一个 bean"的元数据接口:bean 的类名、作用域、是否懒加载、构造器参数、属性值、工厂方法名、依赖关系......XML 的 <bean> 标签、扫描得到的类、@Bean 方法,都只是这个接口的不同来源实现:

  • XML 解析产出 GenericBeanDefinition(BeanDefinitionParserDelegate.createBeanDefinition 经由 BeanDefinitionReaderUtils.createBeanDefinition 创建);
  • 类路径扫描产出 ScannedGenericBeanDefinition(基于 MetadataReader 的 ASM 元数据);
  • @Bean 方法产出 ConfigurationClassBeanDefinition(一种特殊的 RootBeanDefinition,持有 MethodMetadata);
  • AnnotationConfigApplicationContext.register(AppConfig.class) 直接注册类时产出 AnnotatedGenericBeanDefinition。

理解本章的钥匙就在这句话上:解析器做的全部事情,就是把各种形态的外部元数据翻译成 BeanDefinition 并塞进 BeanDefinitionRegistry 。DefaultListableBeanFactory 实现了 BeanDefinitionRegistry,它内部那个 beanDefinitionMap 就是所有解析工作的最终落点。

2.2 BeanDefinitionReader 与 AbstractBeanDefinitionReader

BeanDefinitionReader 接口(spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java)抽象了"从某种资源读取 bean 定义"的能力,其核心方法是 int loadBeanDefinitions(Resource... resources)。XML 场景的实现是 XmlBeanDefinitionReader;注解场景没有继承它,而是由 AnnotatedBeanDefinitionReader 和扫描器各自直接操作 registry。

AbstractBeanDefinitionReader(spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java)为所有 reader 提供公共底盘,看它的字段与构造器就知道 reader 手里握着哪些环境(第 49-101 行):

java 复制代码
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {

	protected final Log logger = LogFactory.getLog(getClass());

	private final BeanDefinitionRegistry registry;

	private @Nullable ResourceLoader resourceLoader;

	private @Nullable ClassLoader beanClassLoader;

	private Environment environment;

	private BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;

逐字段解读:registry 是 bean 定义的最终归宿,reader 解析出的每一个 BeanDefinition 都注册到这里;resourceLoader 负责把 location 字符串解析成 Resource;beanClassLoader 默认为 null------注意这个 null 的含义:reader 阶段不急于加载 bean 类 ,只登记类名字符串,真正的 Class 解析推迟到 bean 创建时;environment 用于占位符(如 ${user.dir})与 profile 判定;beanNameGenerator 给匿名 bean 起名,默认是 DefaultBeanNameGenerator.INSTANCE。

构造器里两段"智能推断"值得细看(第 82-101 行):

java 复制代码
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	this.registry = registry;

	// Determine ResourceLoader to use.
	if (this.registry instanceof ResourceLoader _resourceLoader) {
		this.resourceLoader = _resourceLoader;
	}
	else {
		this.resourceLoader = new PathMatchingResourcePatternResolver();
	}

	// Inherit Environment if possible
	if (this.registry instanceof EnvironmentCapable environmentCapable) {
		this.environment = environmentCapable.getEnvironment();
	}
	else {
		this.environment = new StandardEnvironment();
	}
}

也就是说:如果传入的 registry 本身就是一个 ResourceLoader(典型场景:GenericApplicationContext 及其子类 AnnotationConfigApplicationContext 都实现了 ResourceLoader),reader 就直接借用它作为资源加载器------上下文既能按 classpath:... 找文件,reader 也就有这个能力;否则退化成一个独立的 PathMatchingResourcePatternResolver。环境同理:registry 是 EnvironmentCapable(上下文都是)就继承它的 Environment,否则新建一个 StandardEnvironment。这个"能借就借"的设计保证了 reader 拿到的 Environment 与容器后续做属性解析、profile 判断用的是同一份。

loadBeanDefinitions(String location, Set<Resource> actualResources)(第 205-242 行)则把 location 字符串的解析也统一收口:若 resourceLoader 实现了 ResourcePatternResolver(支持 classpath*: 通配),就 getResources(location) 批量解析再逐个加载;否则只能按单资源处理。XML 的 <import resource="..."/>、@ImportResource 最终都会走这条 location → Resource 的通用路径。

2.3 其余将反复出现的术语

  • EncodedResource (spring-core):在 Resource 之上包一层字符集信息。XML 解析必须知道文件编码,否则 DOM 解析器用平台默认编码读中文注释会乱码。
  • BeanDefinitionDocumentReader / DefaultBeanDefinitionDocumentReader :XML 文档级解析器。XmlBeanDefinitionReader 把 DOM Document 交出去,具体怎么遍历子节点由它负责。
  • BeanDefinitionParserDelegate :DOM 元素到 BeanDefinition 的"翻译官",parseBeanDefinitionElement、parseCustomElement 都是它的方法。
  • NamespaceHandler :XML 命名空间处理器。<context:component-scan/> 里 context 前缀对应的那串解析逻辑,就是一个 NamespaceHandler 实现。
  • MetadataReader / MetadataReaderFactory :基于 ASM 的类元数据读取抽象。扫描时不加载类,只读 class 文件字节码的类描述与注解结构,这是扫描器能在类未链接进 JVM 前工作的关键。
  • ConfigurationClass :ConfigurationClassParser 内部的中间模型(不是 bean,是"待处理配置类"的内存对象),携带 @Bean 方法集合、imported 资源、importBeanDefinitionRegistrars 等。解析器先把所有 @Configuration 类加工成 ConfigurationClass 图,再由 ConfigurationClassBeanDefinitionReader 把图落成 BeanDefinition------解析与注册两阶段分离,这是 JavaConfig 体系最核心的结构特征。
  • full / lite :@Configuration(proxyBeanMethods=true)(默认)为 full 模式,需要 CGLIB 增强;@Configuration(proxyBeanMethods=false) 或只有 @Component/@Bean 方法的"轻量类"为 lite 模式,不增强。判定依据见 ConfigurationClassUtils.checkConfigurationClassCandidate。

3. 源码剖析

3.1 XML 解析全链路:从 Resource 到 Document

XML 这条线的入口是 XmlBeanDefinitionReader(spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java)。它继承 AbstractBeanDefinitionReader,类注释写明分工:"Delegates the actual XML document reading to an implementation of the BeanDefinitionDocumentReader interface"------本类只负责把 XML 变成 DOM Document 并计数,真正逐节点翻译的活外包给 documentReader。

先看加载入口的重载链。最常用的入口是按 Resource 加载(第 315-318 行):

java 复制代码
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}

把裸 Resource 包成 EncodedResource 再进入主流程,这个包装在 7.0.9 里是无条件的------即使没有指定编码,EncodedResource 也提供了"当前正在加载的资源"这个线程级上下文的载体。主流程 loadBeanDefinitions(EncodedResource)(第 327-357 行):

java 复制代码
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	Assert.notNull(encodedResource, "EncodedResource must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Loading XML bean definitions from " + encodedResource);
	}

	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}

	try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
		InputSource inputSource = new InputSource(inputStream);
		if (encodedResource.getEncoding() != null) {
			inputSource.setEncoding(encodedResource.getEncoding());
		}
		return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	}
	catch (IOException ex) {
		throw new BeanDefinitionStoreException(
				"IOException parsing XML document from " + encodedResource.getResource(), ex);
	}
	finally {
		currentResources.remove(encodedResource);
		if (currentResources.isEmpty()) {
			this.resourcesCurrentlyBeingLoaded.remove();
		}
	}
}

逐段解读:

  1. 循环 import 检测 。resourcesCurrentlyBeingLoaded 是一个 NamedThreadLocal<Set<EncodedResource>>(类字段区声明)。<import resource="a.xml"/> 触发递归加载时,若 a.xml 又 import 了自己(直接或间接成环),currentResources.add(encodedResource) 返回 false------Set 不允许重复元素------于是立刻抛出 "Detected cyclic loading"。这就是"循环 import XML 启动报错"的源码出处。finally 里加载完成后把当前资源从集合移除,集合空了就清理 ThreadLocal,避免线程池复用时残留脏状态。
  2. 编码注入 SAX 。打开 InputStream 后包成 InputSource;若 EncodedResource 带了显式编码(典型场景:FileSystemXmlApplicationContext 或用户手工 new EncodedResource(resource, "UTF-8")),就 inputSource.setEncoding(...),SAX 解析器据此选择字符集。
  3. 异常包装 。doLoadBeanDefinitions 抛出的各类异常在底层被统一包装成 BeanDefinitionStoreException 家族,SAXParseException 会被特意带上行号("Line X in XML document ... is invalid"),这就是 XML 写错标签时错误信息里行号的来源。

3.2 doLoadBeanDefinitions:解析与注册两步走

进入 doLoadBeanDefinitions(InputSource, Resource)(第 393-427 行),整个 XML 阶段的主结构只有两行有效代码:

java 复制代码
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {

	try {
		Document doc = doLoadDocument(inputSource, resource);
		int count = registerBeanDefinitions(doc, resource);
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + count + " bean definitions from " + resource);
		}
		return count;
	}
	catch (BeanDefinitionStoreException ex) {
		throw ex;
	}
	catch (SAXParseException ex) {
		throw new XmlBeanDefinitionStoreException(resource.getDescription(),
				"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
	}
	// ......其余异常分支略
}

它把工作严格切成两段:doLoadDocument 负责"XML 文本 → DOM 树"(解析语法、校验 schema),registerBeanDefinitions 负责"DOM 树 → 注册进 registry"(翻译语义)。后面那一长串 catch 只是把底层异常翻译成带资源描述信息的 BeanDefinitionStoreException,不影响主流程。

doLoadDocument(第 438-441 行)本身只有一行委托:

java 复制代码
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}

documentLoader 默认是 DefaultDocumentLoader(spring-beans/.../xml/DefaultDocumentLoader.java),内部用 JAXP DocumentBuilderFactory 建 DOM 解析器。这里传入四个关键配角:

  • getEntityResolver():返回的是 ResourceEntityResolver 包裹的 DelegatingEntityResolver,它组合了 BeansDtdResolver(处理 Spring 自己的 DTD)和 PluggableSchemaResolver(处理 XSD,后者读 META-INF/spring.schemas 把 http://www.springframework.org/schema/... 映射到 jar 包内本地的 .xsd 文件)。这就是 Spring XML 不需要联网下载 XSD 的原因------schema 地址被劫持成本地 classpath 资源。
  • errorHandler:SimpleSaxErrorHandler,把 SAX 的 warning/error/fatalError 转给 Commons Logging。
  • getValidationModeForResource(resource):决定 DTD / XSD / 不校验。
  • isNamespaceAware():命名空间感知,默认 false,只有用了自定义命名空间标签时才有意义。

验证模式的决策逻辑 getValidationModeForResource(第 451-464 行)也值得一讲:

java 复制代码
protected int getValidationModeForResource(Resource resource) {
	int validationModeToUse = getValidationMode();
	if (validationModeToUse != VALIDATION_AUTO) {
		return validationModeToUse;
	}
	int detectedMode = detectValidationMode(resource);
	if (detectedMode != VALIDATION_AUTO) {
		return detectedMode;
	}
	// Hmm, we didn't get a clear indication... Let's assume XSD,
	// since apparently no DTD declaration has been found up until
	// detection stopped (before finding the document's root tag).
	return VALIDATION_XSD;
}

优先级是:用户显式设置 > 自动探测(XmlValidationModeDetector 流式扫描 XML 文本里有没有 DOCTYPE 声明)> 默认按 XSD。VALIDATION_AUTO 是字段 validationMode 的初始值(第 114 行 private int validationMode = VALIDATION_AUTO;)。detectValidationMode(第 473-500 行)要求 Resource 能重复打开流------如果传入的是已经打开过的流,它会直接抛异常并提示"要么传可重复读取的 Resource,要么显式指定 validationMode"。

3.3 registerBeanDefinitions:把 Document 交给文档阅读器

DOM 树就绪后,进入 registerBeanDefinitions(Document, Resource)(第 515-520 行):

java 复制代码
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

三个动作:① createBeanDefinitionDocumentReader() 通过 BeanUtils.instantiateClass(this.documentReaderClass) 反射实例化 documentReaderClass------该字段默认是 DefaultBeanDefinitionDocumentReader.class(第 118-119 行),允许子类替换;② 记录注册前后 registry 中 bean 定义数量,差值即"本文件贡献的 bean 数",这就是启动日志里 "Loaded N bean definitions from ..." 的 N;③ 组装 XmlReaderContext 传过去------createReaderContext(第 535-538 行)把 problemReporter、eventListener、sourceExtractor、reader 自身和 namespaceHandlerResolver 打包进上下文对象,后续整个文档解析过程都通过这个上下文拿到这些协作组件。

至此 XML 阶段完成了"文本 → DOM → 委托给 DefaultBeanDefinitionDocumentReader"的过渡。下一节我们钻进文档阅读器,看 DOM 树是怎么被逐节点翻译成 BeanDefinition 的。

3.4 DefaultBeanDefinitionDocumentReader:DOM 树的遍历中枢

DefaultBeanDefinitionDocumentReader(spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java)是 XML 语义翻译的总调度。入口 registerBeanDefinitions(第 92-95 行)极简:

java 复制代码
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	doRegisterBeanDefinitions(doc.getDocumentElement());
}

拿到根元素 <beans> 后进入 doRegisterBeanDefinitions(第 118-151 行),这个方法处理四件事:delegate 的父子栈、profile 过滤、预处理/后处理钩子、递归嵌套 <beans>。

java 复制代码
protected void doRegisterBeanDefinitions(Element root) {
	// Any nested <beans> elements will cause recursion in this method. In
	// order to propagate and preserve <beans> default-* attributes correctly,
	// keep track of the current (parent) delegate, which may be null. Create
	// the new (child) delegate with a reference to the parent for fallback purposes,
	// then ultimately reset this.delegate back to its original (parent) reference.
	// this behavior emulates a stack of delegates without actually necessitating one.
	BeanDefinitionParserDelegate parent = this.delegate;
	BeanDefinitionParserDelegate current = createDelegate(getReaderContext(), root, parent);
	this.delegate = current;

	if (current.isDefaultNamespace(root)) {
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			// We cannot use Profiles.of(...) since profile expressions are not supported
			// in XML config. See SPR-12458 for details.
			if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}

	preProcessXml(root);
	parseBeanDefinitions(root, current);
	postProcessXml(root);

	this.delegate = parent;
}

逐段解读:

  1. delegate 的"伪栈" 。this.delegate 是文档阅读器的一个实例字段,代表"当前层级的默认值上下文"。XML 允许嵌套 <beans>(一个 <beans> 里再写一个 <beans>),嵌套层可以声明自己的 default-lazy-init、default-autowire 等 default-* 属性。方法先把旧 delegate 存进局部变量 parent,新建一个子 delegate 并在 initDefaults(root, parent) 里把子层未显式指定的默认值回退继承自父层 (详见 3.5);遍历完后 this.delegate = parent 恢复现场。注释明说:这是用"保存-恢复"模拟栈,而不是真的维护一个 Deque。
  2. profile 过滤 。根元素若带 profile="dev,qa",则用环境 acceptsProfiles 判断当前激活 profile 是否命中;不命中直接 return,整个文件跳过 。注意注释里的限定:XML 的 profile 只支持逗号分隔列表,不支持 Profiles.of() 那种表达式(SPR-12458)。
  3. 预处理/后处理是空钩子 。preProcessXml(root) / postProcessXml(root)(第 330 行附近)默认空实现,留给子类扩展------这是少数几个"如果你想改写整个 XML 预处理流程"的公开扩展点。
  4. 真正遍历在 parseBeanDefinitions(root, current)。

3.5 parseBeanDefinitions:默认命名空间与自定义命名空间的分派

parseBeanDefinitions(第 166-184 行)是 XML 标签分派的核心,只有十几行但信息量极大:

java 复制代码
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element ele) {
				if (delegate.isDefaultNamespace(ele)) {
					parseDefaultElement(ele, delegate);
				}
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

分派规则就一条:按元素的命名空间 URI 路由 。isDefaultNamespace(namespaceUri) 的实现(BeanDefinitionParserDelegate 第 1511-1513 行)是:

java 复制代码
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
	return !StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
}

即:命名空间为空(老式无命名空间写法)或等于 http://www.springframework.org/schema/beans 的,走默认通道;其余一律走 parseCustomElement。所以 <bean>、<import>、<alias>、<beans> 走默认通道,而 <context:component-scan>、<aop:config>、<tx:annotation-driven> 这类带前缀的标签因为命名空间 URI 是 http://www.springframework.org/schema/context 等,全部落入自定义通道------这正是命名空间机制存在的意义:spring-beans 只硬编码 beans 命名空间的语义,其余一切命名空间都是可插拔插件。

默认通道的分派 parseDefaultElement(第 186-200 行)是朴素的标签名 if-else:

java 复制代码
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

<import> 触发递归加载另一个资源文件(importBeanDefinitionResource,第 206-269 行:先 environment.resolveRequiredPlaceholders 解析 ${...} 占位符,再判断绝对/相对 location,相对路径用 getResource().createRelative(location) 基于当前文件解析,最后回调 reader.loadBeanDefinitions(location, actualResources)------绕回 3.2 的入口,形成递归);<alias> 直接 registry.registerAlias(name, alias);嵌套 <beans> 则 doRegisterBeanDefinitions(ele) 递归------这就是 3.4 里 delegate 伪栈要处理的场景。

<bean> 标签的处理 processBeanDefinition(第 302-317 行)是默认通道的重头戏:

java 复制代码
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the final decorated instance.
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			getReaderContext().error("Failed to register bean definition with name '" +
					bdHolder.getBeanName() + "'", ele, ex);
		}
		// Send registration event.
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}

四步:① parseBeanDefinitionElement 把 <bean> 元素翻译成 BeanDefinitionHolder(bean 定义 + 名字 + 别名);② decorateBeanDefinitionIfRequired------若该 <bean> 上挂了自定义命名空间的属性或子元素 (例如 <bean id="foo" p:name="bar"/> 里的 p: 命名空间属性),交给对应 BeanDefinitionDecorator 装饰;③ BeanDefinitionReaderUtils.registerBeanDefinition(spring-beans/.../support/BeanDefinitionReaderUtils.java 第 158-173 行)先 registry.registerBeanDefinition(beanName, bd),再逐条 registerAlias;④ 发注册事件给 ReaderEventListener(默认是空实现,工具链/IDE 可挂钩)。

至此,一个 <bean> 从 DOM 节点变成了 registry 里的一条记录。下一节钻进翻译官 BeanDefinitionParserDelegate,看属性是如何逐个落到 AbstractBeanDefinition 字段上的。

3.6 BeanDefinitionParserDelegate:<bean> 元素的翻译官

BeanDefinitionParserDelegate(spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java)是默认命名空间下所有元素的翻译官。它的两个 parseBeanDefinitionElement 重载构成入口:无参版本只负责"起名",三参版本负责"翻译内容"。

3.6.1 名字与别名的确定

第一个重载 parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)(第 410-469 行节选):

java 复制代码
public @Nullable BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
	String id = ele.getAttribute(ID_ATTRIBUTE);
	String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

	List<String> aliases = new ArrayList<>();
	if (StringUtils.hasLength(nameAttr)) {
		String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		aliases.addAll(Arrays.asList(nameArr));
	}

	String beanName = id;
	if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
		beanName = aliases.remove(0);
		if (logger.isTraceEnabled()) {
			logger.trace("No XML 'id' specified - using '" + beanName +
					"' as bean name and " + aliases + " as aliases");
		}
	}

	if (containingBean == null) {
		checkNameUniqueness(beanName, aliases, ele);
	}

	AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
	if (beanDefinition != null) {
		if (!StringUtils.hasText(beanName)) {
			try {
				if (containingBean != null) {
					beanName = BeanDefinitionReaderUtils.generateBeanName(
							beanDefinition, this.readerContext.getRegistry(), true);
				}
				else {
					beanName = this.readerContext.generateBeanName(beanDefinition);
					// Register an alias for the plain bean class name, if still possible,
					// if the generator returned the class name plus a suffix.
					String beanClassName = beanDefinition.getBeanClassName();
					if (beanClassName != null &&
							beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
							!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
						aliases.add(beanClassName);
					}
				}
			}
			// ......异常分支略
		}
		String[] aliasesArray = StringUtils.toStringArray(aliases);
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
	}

	return null;
}

命名规则逐段拆解:

  1. id 优先,name 其次,自动生成兜底 。<bean id="a" name="b,c"/> 中,id="a" 是正式 bean 名,name 属性按分隔符(,; 与空白,MULTI_VALUE_ATTRIBUTE_DELIMITERS)切成别名列表。没有 id 时,别名列表第一个被"转正"为 bean 名,剩下的仍是别名------这就是 name 属性历来承担"多别名"语义的实现。
  2. 同一 <beans> 层级内查重 。checkNameUniqueness(第 475-490 行)维护一个 usedNames 集合,若 beanName 或任一别名在本层已出现,直接 error------注意它只检查当前 delegate 层级 ,这就是嵌套 <beans> 可以复用名字的原因。
  3. 匿名 bean 的命名 。id/name 都没有时,顶层 bean 走 readerContext.generateBeanName(即 BeanDefinitionReaderUtils.generateBeanName 的全限定类名 + # + 自增序号风格,如 com.foo.User#0);内部 bean(containingBean != null,即 <property> 里嵌套的 <bean>)走另一种生成策略。随后还有一个向后兼容细节:如果生成的名字是"类名 + 后缀"且这个纯类名尚未被占用,就把纯类名注册成别名------这是 Spring 1.2/2.0 时代匿名 bean 按类名引用的兼容策略。
3.6.2 属性到字段:parseBeanDefinitionAttributes

真正翻译属性的是三参重载 parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)(第 496-543 行),它先 push(new BeanEntry(beanName)) 到 parseState(错误定位栈),然后依次:

java 复制代码
public @Nullable AbstractBeanDefinition parseBeanDefinitionElement(
		Element ele, String beanName, @Nullable BeanDefinition containingBean) {

	this.parseState.push(new BeanEntry(beanName));

	String className = null;
	if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
		className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
	}
	String parent = null;
	if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
		parent = ele.getAttribute(PARENT_ATTRIBUTE);
	}

	try {
		AbstractBeanDefinition bd = createBeanDefinition(className, parent);

		parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
		bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

		parseMetaElements(ele, bd);
		parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
		parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

		parseConstructorArgElements(ele, bd);
		parsePropertyElements(ele, bd);
		parseQualifierElements(ele, bd);

		bd.setResource(this.readerContext.getResource());
		bd.setSource(extractSource(ele));

		return bd;
	}
	catch (ClassNotFoundException ex) {
		error("Bean class [" + className + "] not found", ele, ex);
	}
	// ......其余 catch 分支略
	finally {
		this.parseState.pop();
	}

	return null;
}

注意 createBeanDefinition(className, parent)(第 635-640 行)只是把类名字符串包成 GenericBeanDefinition,不加载类 ------BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, classLoader) 里那个 classLoader 传进来也只是备而不用。这再次印证本章反复强调的事实:解析阶段只处理字符串与 DOM,类的加载发生在容器真正需要创建 bean 时。

紧接着这个方法的子元素处理顺序也值得记住:description → meta → lookup-replaced(方法覆写)→ constructor-arg → property → qualifier。每一类子元素的解析都是独立方法,例如 parsePropertyElements(第 703-711 行)就是遍历子节点、筛出 <property> 调 parsePropertyElement------后者把 name/value/ref/子 <bean>/<list> 等翻译成 PropertyValue 加入 bd.getPropertyValues()。

属性级翻译 parseBeanDefinitionAttributes(第 552-626 行)是一张完整的"XML 属性 → BeanDefinition 字段"映射表,逐段读:

java 复制代码
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
		@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

	if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
		error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
	}
	else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
		bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
	}
	else if (containingBean != null) {
		// Take default from containing bean in case of an inner bean definition.
		bd.setScope(containingBean.getScope());
	}

	if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
		bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
	}

	String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
	if (isDefaultValue(lazyInit)) {
		lazyInit = this.defaults.getLazyInit();
	}
	bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

	String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
	bd.setAutowireMode(getAutowireMode(autowire));
	// ......depends-on / autowire-candidate / primary / init-method / destroy-method /
	// factory-method / factory-bean 分支略,模式同上
	return bd;
}

几个关键设计点:

  1. 旧版 singleton 属性直接报错 。Spring 2.0 起改用 scope,这里碰到 1.x 时代的 <bean singleton="true"> 不是兼容而是 error 提示升级------历史包袱处理得很干脆。
  2. 内部 bean 继承外部 scope 。匿名内部 <bean>(出现在 <property> 里)没写 scope 时,bd.setScope(containingBean.getScope())------内部 bean 跟着宿主走。
  3. default-* 默认值的回退机制 。这是理解 <beans default-lazy-init="true"> 全局默认如何生效的钥匙:bean 元素上没写 lazy-init(isDefaultValue 判断空串/default)时,从 this.defaults(BeanDefinitionDefaults 对象,由 initDefaults(root, parentDelegate) 从当前 <beans> 标签的 default-lazy-init 读入,未指定则向父 delegate 要)取默认值。initDefaults 在 BeanDefinitionParserDelegate 第 306 行附近,配合 3.4 的 delegate 伪栈,形成了"嵌套 beans 默认值逐层继承"的完整机制。
  4. autowire 模式字符串到常量的映射 。getAutowireMode(第 665-685 行)把 byName/byType/constructor/autodetect 映射为 AbstractBeanDefinition.AUTOWIRE_BY_NAME 等整型常量;未指定则回退到 defaults.getAutowire(),最终 AUTOWIRE_NO。

其余属性同构:depends-on 按分隔符切字符串数组;primary/abstract/lazy-init 一律用 TRUE_VALUE.equals(...) 严格匹配 "true"(其他值一律按 false 处理,不会因为你写了 TRUE 或 yes 就上当);init-method/destroy-method 若 bean 未显式写,则用 <beans default-init-method> 的值并 setEnforceInitMethod(false)------这个 enforce 标志位的含义是"用户显式指定过,必须严格生效",为后续合并父子 bean 定义时区分"默认值"与"显式值"提供依据。

<qualifier> 子元素走 parseQualifierElement(第 861 行起),把 value/type 收集成 AutowireCandidateQualifier,供按限定符注入时使用------它解决的是"同一接口有多个实现,注入时怎么点名"的问题。

3.7 自定义命名空间:parseCustomElement 与 NamespaceHandler 机制

3.5 留下了一个悬念:非默认命名空间的标签(如 <context:component-scan/>)是怎么被解析的?答案在 BeanDefinitionParserDelegate.parseCustomElement(第 1369-1380 行):

java 复制代码
public @Nullable BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

三步:① 从 DOM 节点取 namespaceURI(即你在 XML 头部 xmlns:context="http://www.springframework.org/schema/context" 声明的那个字符串);② 用 NamespaceHandlerResolver 把 URI 解析成一个 NamespaceHandler 实例;③ 调 handler.parse(ele, parserContext) 把解析工作整个交出去。

找不到 handler 时的报错就是这句:"Unable to locate Spring NamespaceHandler for XML schema namespace ..."------后文"常见问题"会专门讲它的根因。

3.7.1 DefaultNamespaceHandlerResolver:spring.handlers 文件即注册表

NamespaceHandlerResolver 的默认实现是 DefaultNamespaceHandlerResolver(spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java)。它的核心常量(第 55 行):

java 复制代码
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

也就是说,"哪个命名空间 URI 由哪个类处理"这个映射关系,不是硬编码在 Java 里,而是放在各 jar 包的 META-INF/spring.handlers 属性文件里。spring-context 模块的该文件(spring-context/src/main/resources/META-INF/spring.handlers)内容为:

properties 复制代码
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

每行是 URI=处理器全限定类名,注意 URI 里的冒号被反斜杠转义(properties 文件规范)。resolve(String namespaceUri)(第 114-145 行)的逻辑:

java 复制代码
@Override
public @Nullable NamespaceHandler resolve(String namespaceUri) {
	Map<String, Object> handlerMappings = getHandlerMappings();
	Object handlerOrClassName = handlerMappings.get(namespaceUri);
	if (handlerOrClassName == null) {
		return null;
	}
	else if (handlerOrClassName instanceof NamespaceHandler namespaceHandler) {
		return namespaceHandler;
	}
	else {
		String className = (String) handlerOrClassName;
		try {
			Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
			if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
				throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
						"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
			}
			NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
			namespaceHandler.init();
			handlerMappings.put(namespaceUri, namespaceHandler);
			return namespaceHandler;
		}
		// ......ClassNotFoundException / LinkageError 分支略
	}
}

这里有一个精妙的懒加载 + 缓存 设计:handlerMappings 这个 Map 的 value 初始存的是类名字符串 ,第一次 resolve 命中某个 URI 时才反射实例化、调 init(),然后把 Map 里那条目替换成实例 (handlerMappings.put(namespaceUri, namespaceHandler))。第二次再 resolve 同一个 URI,instanceof NamespaceHandler 命中,直接返回实例。也就是说:命名空间处理器是按需创建、单例缓存的------你用不上 task: 标签,TaskNamespaceHandler 就永远不会被类加载。

getHandlerMappings()(第 150-177 行)用双重检查锁加载 META-INF/spring.handlers:PropertiesLoaderUtils.loadAllProperties 会扫描 classpath 下所有 jar 的同名文件并合并(这就是为什么第三方 jar 只要带上自己的 spring.handlers 就能向 Spring 注册命名空间),加载结果存进 volatile 的 handlerMappings 字段。

3.7.2 NamespaceHandlerSupport:把元素名分派给 Parser

拿到 handler 实例后,parseCustomElement 调的是 handler.parse(...)。标准实现都继承 NamespaceHandlerSupport(spring-beans/.../xml/NamespaceHandlerSupport.java),它内部维护三张表(第 51-63 行):

java 复制代码
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap<>();

parse 方法本身(第 71-74 行)只是按元素 localName 查表:

java 复制代码
@Override
public @Nullable BeanDefinition parse(Element element, ParserContext parserContext) {
	BeanDefinitionParser parser = findParserForElement(element, parserContext);
	return (parser != null ? parser.parse(element, parserContext) : null);
}

findParserForElement(第 80-88 行)从 parserContext.getDelegate().getLocalName(element) 取 localName(去掉前缀的部分,如 component-scan),去 parsers 表查;查不到就 fatal 报错"Cannot locate BeanDefinitionParser for element xxx"。

注册动作发生在 handler 的 init() 方法里。以 context 命名空间为例(spring-context/src/main/java/org/springframework/context/config/ContextNamespaceHandler.java 第 31-45 行):

java 复制代码
public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}
}

这张表把 <context:xxx> 的每个标签映射到一个具体的 BeanDefinitionParser 实现。init() 何时被调?回顾 3.7.1:DefaultNamespaceHandlerResolver.resolve 在第一次实例化 handler 后立刻调 namespaceHandler.init()------所以 <context:component-scan/> 第一次出现时,整个 ContextNamespaceHandler 的注册表才在这一刻构建完成。

3.7.3 BeanDefinitionDecorator:装饰而非新建

除了"整个元素翻译成新 bean",命名空间机制还支持"装饰已有 bean"。3.5 节 processBeanDefinition 里的 decorateBeanDefinitionIfRequired(BeanDefinitionParserDelegate 第 1399-1420 行)做两件事:先遍历该 <bean> 的属性 ,再遍历其子元素 ,对每个非默认命名空间的节点调 decorateIfRequired(第 1430-1454 行):

java 复制代码
public BeanDefinitionHolder decorateIfRequired(
		Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

	String namespaceUri = getNamespaceURI(node);
	if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler != null) {
			BeanDefinitionHolder decorated =
					handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
			if (decorated != null) {
				return decorated;
			}
		}
		else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
		}
		// ......
	}
	return originalDef;
}

handler.decorate 在 NamespaceHandlerSupport 里按节点类型查表(元素查 decorators 表,属性查 attributeDecorators 表,第 107-125 行)。经典应用是 p: 命名空间(<bean p:name="x"/>)和 util:、c: 命名空间里那些"挂在 bean 上"的用法------它们不注册新 bean,只是往已有 BeanDefinition 里补充属性值或构造器参数。

3.7.4 XSD 的本地解析:spring.schemas

最后补一个与 handler 平行的机制。XML 头部要写 xsi:schemaLocation="http://www.springframework.org/schema/beans https://.../spring-beans.xsd",正常 XML 解析器会按这个 URL 去联网下载 schema------Spring 不允许这种依赖。PluggableSchemaResolver(spring-beans/.../xml/PluggableSchemaResolver.java)在 META-INF/spring.schemas 属性文件里维护"schema URL → jar 内本地 .xsd 路径"的映射(常量 DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas",第 64 行),解析 XSD 时由它接管 resolveEntity,把远程 URL 重定向到 classpath 资源。这套机制和 spring.handlers 是同构的:都是 classpath 扫描 + properties 映射 + 懒加载缓存。自定义命名空间要能离线工作,必须同时提供自己的 .xsd 文件并在 spring.schemas 里登记。

3.8 从 XML 标签进入扫描:ComponentScanBeanDefinitionParser

XML 这条线和注解扫描那条线在 <context:component-scan/> 处会师。3.7 已看到 ContextNamespaceHandler 把 component-scan 标签注册给 ComponentScanBeanDefinitionParser(spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java)。它的 parse 方法(第 82-94 行)只有三步:

java 复制代码
@Override
public @Nullable BeanDefinition parse(Element element, ParserContext parserContext) {
	String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
	basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
	String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

	// Actually scan for bean definitions and register them.
	ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
	Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
	registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

	return null;
}

注意最后 return null------这个 parser 自己不注册任何 bean 定义,注册动作由 scanner 完成。它只负责:解析 base-package 属性(支持 ${...} 占位符、支持逗号/分号/空白分隔多包),配置一个扫描器,调 doScan,最后 registerComponents(第 139-163 行)里做两件配套事:把扫描到的 bean 包装成 CompositeComponentDefinition 发事件;以及若 annotation-config 属性未显式关闭(默认 true),调 AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source)------这就是 <context:component-scan/> 会顺带注册 ConfigurationClassPostProcessor 等一整套注解处理器的原因 (等价于 <context:annotation-config/>,所以你用了 component-scan 就不必再写 annotation-config)。

configureScanner(第 96-132 行)从 XML 属性装配扫描器:use-default-filters、resource-pattern、name-generator、scope-resolver、scoped-proxy、<include-filter>/<exclude-filter>。其中 createTypeFilter(第 231-260 行)把 filter 的 type 属性翻译成具体 TypeFilter 实现,是一个小型策略表:

java 复制代码
switch (filterType) {
	case "annotation" -> {
		return new AnnotationTypeFilter((Class<Annotation>) ClassUtils.forName(expression, classLoader));
	}
	case "assignable" -> {
		return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
	}
	case "aspectj" -> {
		return new AspectJTypeFilter(expression, classLoader);
	}
	case "regex" -> {
		return new RegexPatternTypeFilter(Pattern.compile(expression));
	}
	case "custom" -> {
		Class<?> filterClass = ClassUtils.forName(expression, classLoader);
		if (!TypeFilter.class.isAssignableFrom(filterClass)) {
			throw new IllegalArgumentException(
					"Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
		}
		return (TypeFilter) BeanUtils.instantiateClass(filterClass);
	}
	default -> throw new IllegalArgumentException("Unsupported filter type: " + filterType);
}

五种过滤类型各对应一个 TypeFilter 实现(都在 spring-core 的 org.springframework.core.type.filter 包):按注解、按可赋值类型、按 AspectJ 切点表达式、按类名正则、按用户自定义。这套 TypeFilter 抽象同时被注解方式的 @ComponentScan(includeFilters=...) 复用(见 3.11),是 XML 与注解两条配置路径共享的判定逻辑。

3.9 ClassPathScanningCandidateComponentProvider:ASM 驱动的类查找

ClassPathBeanDefinitionScanner(spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java)继承 ClassPathScanningCandidateComponentProvider(同包)。"找候选类"的核心逻辑全在父类。

3.9.1 默认过滤器:为什么扫到的是 @Component 族

registerDefaultFilters(父类第 216-227 行):

java 复制代码
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
	try {
		this.includeFilters.add(new AnnotationTypeFilter(
				((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Named", cl)), false));
		logger.trace("JSR-330 'jakarta.inject.Named' annotation found and supported for component scanning");
	}
	catch (ClassNotFoundException ex) {
		// JSR-330 API (as included in Jakarta EE) not available - simply skip.
	}
}

默认只加一个 include 过滤器:AnnotationTypeFilter(Component.class)。那 @Service、@Controller、@Repository 为什么也能被扫到?答案在 AnnotationTypeFilter 的匹配行为里:它是元注解传递 的------@Service 本身被 @Component 标注,扫描器判定一个类是否匹配时会沿注解层次向上找,发现类上的注解(含组合注解)带着 @Component,就算匹配。这就是"@Component 派生性"的源码本质:@Service/@Repository/@Controller/@Configuration 都是用 @Component 做元注解的组合注解。

第二个过滤器是 JSR-330 的 jakarta.inject.Named,用 ClassUtils.forName 探测------classpath 上有这个 API 就加,没有(ClassNotFoundException)就静默跳过。这是典型的"可选依赖"写法。

3.9.2 查找主路径:findCandidateComponents

findCandidateComponents(String basePackage)(第 312-322 行)是扫描总入口,它先判断"能不能走索引捷径":

java 复制代码
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
		if (this.componentsIndex.hasScannedPackage(basePackage)) {
			return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
		}
		else {
			this.componentsIndex.registerScan(basePackage);
		}
	}
	return scanCandidateComponents(basePackage);
}

componentsIndex 是 CandidateComponentsIndex(spring-core 提供的运行时索引)。存在索引、且所有 include 过滤器都被索引支持(indexSupportsIncludeFilters:过滤器只能是 AnnotationTypeFilter 且注解本身被 @Indexed 标注,或 AssignableTypeFilter 且目标类被 @Indexed 标注------见第 347-357 行)时,走 addCandidateComponentsFromIndex 捷径:直接从索引查出"哪些类标了 @Component",省去逐 class 文件 ASM 读取。这条路对应 @Indexed 机制------构建期通过 spring-context-indexer 生成 META-INF/spring.components 索引文件。需要说明:生成索引的 CandidateComponentsIndexer 在 7.0.9 中已 @Deprecated(since=6.1, forRemoval=true),但运行时消费索引的 CandidateComponentsIndex 类仍在,这套捷径路径依然有效。

不满足索引条件就走经典路径 scanCandidateComponents(第 446-512 行):

java 复制代码
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	try {
		String packageSearchPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPattern);
		// ......
		for (Resource resource : resources) {
			String filename = resource.getFilename();
			if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
				// Ignore CGLIB-generated classes in the classpath
				continue;
			}
			try {
				MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
				if (isCandidateComponent(metadataReader)) {
					ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
					sbd.setSource(resource);
					if (isCandidateComponent(sbd)) {
						candidates.add(sbd);
					}
				}
			}
			catch (FileNotFoundException ex) {
				// ......忽略
			}
			catch (ClassFormatException ex) {
				// ......根据系统属性 spring.classformat.ignore 决定忽略或抛出
			}
			// ......
		}
	}
	// ......
	return candidates;
}

逐段解读:

  1. 包名转资源模式 。resolveBasePackage(第 523-525 行)先解析占位符,再把 com.example 转成 com/example,拼成 classpath*:com/example/**/*.class(resourcePattern 默认 **/*.class)。注意前缀是 classpath*: 而不是 classpath:------要扫描 jar 包内和多模块下所有同名包路径。
  2. 资源解析 。PathMatchingResourcePatternResolver.getResources 把这个模式展开成一堆 Resource(每个 .class 文件一个)。
  3. 跳过 CGLIB 类 。文件名含 $$(CGLIB_CLASS_SEPARATOR)的是运行时代理类,直接跳过------避免把 AppConfig$$EnhancerByCGLIB 当成候选组件再扫一遍。
  4. ASM 读取,不加载类 。关键在 getMetadataReaderFactory().getMetadataReader(resource):返回一个 MetadataReader,它内部用 ASM 读 class 字节码,只解析类结构与注解,不触发类加载 。默认工厂是 CachingMetadataReaderFactory(在 setResourceLoader 时创建,第 267 行),对同一个 Resource 的 MetadataReader 做缓存------扫描时可能反复读取一个类的注解(多个过滤器都会读),缓存避免重复解析字节码。
  5. 两层候选判定 。第一层 isCandidateComponent(metadataReader) 用 TypeFilter 粗筛(注解匹配);第二层 isCandidateComponent(sbd) 用 AnnotatedBeanDefinition 元数据精筛(是不是具体类)。两层都过,才 new ScannedGenericBeanDefinition(metadataReader)------注意构造器直接吃 MetadataReader,BeanDefinition 内部存的是 ASM 读到的 AnnotationMetadata,依然没有加载类。
3.9.3 两层候选判定的细节

第一层 isCandidateComponent(MetadataReader)(第 533-546 行):

java 复制代码
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
	for (TypeFilter filter : this.excludeFilters) {
		if (filter.match(metadataReader, getMetadataReaderFactory())) {
			return false;
		}
	}
	for (TypeFilter filter : this.includeFilters) {
		if (filter.match(metadataReader, getMetadataReaderFactory())) {
			registerCandidateTypeForIncludeFilter(metadataReader.getClassMetadata().getClassName(), filter);
			return isConditionMatch(metadataReader);
		}
	}
	return false;
}

顺序是:先过 exclude(命中即淘汰),再过 include(任一命中即入选)。入选后还 registerCandidateTypeForIncludeFilter(把这个类登记进运行时索引,配合 3.9.2 的索引捷径)与 isConditionMatch(第 554-560 行)------后者调 ConditionEvaluator.shouldSkip 评估类上的 @Conditional(含 @Profile),不满足条件同样淘汰。这就是 @Conditional 在组件扫描阶段生效的位置。

第二层 isCandidateComponent(AnnotatedBeanDefinition)(第 571-575 行):

java 复制代码
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
	AnnotationMetadata metadata = beanDefinition.getMetadata();
	return (metadata.isIndependent() && (metadata.isConcrete() ||
			(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

两个条件:① isIndependent()------类不依赖外部类实例(不是内部成员类,即不是"非 static 的嵌套类");② isConcrete() 是具体类,或者 虽然是抽象类但带 @Lookup 注解的方法------后者是抽象 bean 的特殊通道:抽象类配合 @Lookup 方法由容器在子类里实现查找方法。接口、注解、枚举、非 static 内部类都在这一层被淘汰。

3.10 doScan:候选类的注册流水线

父类负责"找候选",子类 ClassPathBeanDefinitionScanner 负责"把候选注册成 bean"。核心方法 doScan(String... basePackages)(spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java 第 275-300 行):

java 复制代码
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition abstractBeanDefinition) {
				postProcessBeanDefinition(abstractBeanDefinition, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations(annotatedBeanDefinition);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

对每个候选类依次做五件事:

  1. 解析作用域 。scopeMetadataResolver(默认 AnnotationScopeMetadataResolver)读类上的 @Scope,得到 scope 名与 proxyMode(@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 之类),candidate.setScope(...) 落到 BeanDefinition。
  2. 生成 bean 名 。beanNameGenerator 默认 AnnotationBeanNameGenerator:取类名首字母小写(com.example.UserService → userService);若类名前两个字母都大写(如 URLService),按 Java Bean 规范保留原名。FullyQualifiedAnnotationBeanNameGenerator 则用全限定类名作为 bean 名,用于避免重名。
  3. 后处理默认值 。postProcessBeanDefinition(第 308-313 行)把扫描器上配置的 BeanDefinitionDefaults(来自 <beans default-lazy-init> 或 @ComponentScan 的 lazyInit=true)applyDefaults 到 BeanDefinition;再按 autowireCandidatePatterns 决定 autowireCandidate。
  4. 提取通用注解 。AnnotationConfigUtils.processCommonDefinitionAnnotations 把类上的 @Lazy/@Primary/@DependsOn/@Role/@Description/@Scope 逐一落到 BeanDefinition 对应字段------这和 AnnotatedBeanDefinitionReader 用的是同一个工具方法,XML 与注解两条路在这里汇合。
  5. 查重后注册 。checkCandidate 通过才 applyScopedProxyMode(必要时把 BeanDefinition 包装成 scoped proxy 的代理定义),最后 registerBeanDefinition → BeanDefinitionReaderUtils.registerBeanDefinition(与 XML 路径同一个工具方法,见 3.5.2)。

checkCandidate(第 338-364 行)是"同名 bean 谁赢"问题在扫描侧的答案:

java 复制代码
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
	if (!this.registry.containsBeanDefinition(beanName)) {
		return true;
	}

	BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
	BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
	if (originatingDef != null) {
		existingDef = originatingDef;
	}

	// Explicitly registered overriding bean?
	if (!(existingDef instanceof ScannedGenericBeanDefinition) &&
			(this.registry.isBeanDefinitionOverridable(beanName) || ObjectUtils.nullSafeEquals(
					beanDefinition.getBeanClassName(), existingDef.getBeanClassName()))) {
		return false;
	}

	// Scanned same file or equivalent class twice?
	if (isCompatible(beanDefinition, existingDef)) {
		return false;
	}

	throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
			"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
			"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}

三层逻辑:① registry 里没有同名定义,直接放行;② 已有定义不是扫描出来的 (是 XML 显式注册或 @Bean 注册的),且(容器允许覆盖 或 类名相同),则新扫描的这个静默跳过 (返回 false)------即"显式注册优先于扫描",且从 6.1 开始即使全局禁用覆盖,只要类名相同扫描结果也会被显式定义覆盖(对应 ConfigurationClassBeanDefinitionReader 里对 ScannedGenericBeanDefinition 的处理,见 3.15);③ 已有定义也是扫描出来的,判断是否"同一份东西被扫了两遍"(isCompatible:同源 Resource 或 BeanDefinition 相等,第 377-380 行)------兼容则跳过,不兼容才抛 ConflictingBeanDefinitionException。这就是"两个不同的类被扫成同一个 bean 名"时报错的出处。

3.11 ComponentScanAnnotationParser:从 @ComponentScan 到 doScan

注解方式的 @ComponentScan 不是由 ClassPathBeanDefinitionScanner 直接读的------ConfigurationClassParser 在解析配置类时发现 @ComponentScan,交给 ComponentScanAnnotationParser(spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java)。parse 方法(第 68-129 行)把注解属性翻译成扫描器配置:

java 复制代码
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
	ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
			componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

	Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
	boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
	scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
			BeanUtils.instantiateClass(generatorClass));

	ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
	if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
		scanner.setScopedProxyMode(scopedProxyMode);
	}
	else {
		Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
		scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
	}

	scanner.setResourcePattern(componentScan.getString("resourcePattern"));

	for (AnnotationAttributes includeFilterAttributes : componentScan.getAnnotationArray("includeFilters")) {
		List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilterAttributes, this.environment,
				this.resourceLoader, this.registry);
		for (TypeFilter typeFilter : typeFilters) {
			scanner.addIncludeFilter(typeFilter);
		}
	}
	for (AnnotationAttributes excludeFilterAttributes : componentScan.getAnnotationArray("excludeFilters")) {
		List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilterAttributes, this.environment,
			this.resourceLoader, this.registry);
		for (TypeFilter typeFilter : typeFilters) {
			scanner.addExcludeFilter(typeFilter);
		}
	}

	boolean lazyInit = componentScan.getBoolean("lazyInit");
	if (lazyInit) {
		scanner.getBeanDefinitionDefaults().setLazyInit(true);
	}

	Set<String> basePackages = new LinkedHashSet<>();
	String[] basePackagesArray = componentScan.getStringArray("basePackages");
	for (String pkg : basePackagesArray) {
		String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		Collections.addAll(basePackages, tokenized);
	}
	for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
		basePackages.add(ClassUtils.getPackageName(clazz));
	}
	if (basePackages.isEmpty()) {
		basePackages.add(ClassUtils.getPackageName(declaringClass));
	}

	scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
		@Override
		protected boolean matchClassName(String className) {
			return declaringClass.equals(className);
		}
	});
	return scanner.doScan(StringUtils.toStringArray(basePackages));
}

几个细节:

  • useDefaultFilters :@ComponentScan(useDefaultFilters = false) 时扫描器不注册默认的 @Component include 过滤器------此时必须自己配 includeFilters,这就是为什么很多"只扫特定注解"的写法会同时关掉默认过滤器。
  • basePackageClasses 的语义 :注解里写 basePackageClasses = UserService.class,不是扫这个类本身,而是 ClassUtils.getPackageName(clazz) 取该类所在包作为扫描起点------这是比写字符串包名更抗重构的写法。
  • 兜底包 :一个包都没写时,用 declaringClass(声明 @ComponentScan 的那个配置类)所在包------这就是为什么 @SpringBootApplication 放在根包就能扫全应用。
  • 排除自己。最后匿名加了一个 excludeFilter:按类名精确匹配 declaringClass 本身,把配置类自己排除出扫描结果------避免它既被注册为配置类、又被扫描器重复注册一遍。
  • 过滤器通过 TypeFilterUtils.createTypeFiltersFor 构建,与 XML 的 createTypeFilter(3.8.1)对应同一种 TypeFilter 策略族。

至此,@Component 扫描这条线闭环:@ComponentScan 属性 → ComponentScanAnnotationParser → ClassPathBeanDefinitionScanner.doScan → 父类 findCandidateComponents(ASM + TypeFilter + @Conditional)→ ScannedGenericBeanDefinition → registry。XML 侧的 <context:component-scan> 最终也走到同一个 doScan。

3.12 ConfigurationClassPostProcessor:JavaConfig 世界的入口

注解配置体系的总调度是 ConfigurationClassPostProcessor(spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java)。先看它的类声明(第 152 行):

java 复制代码
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware

关键点:它实现的是 BeanDefinitionRegistryPostProcessor 而不是普通 BeanFactoryPostProcessor。这决定了它在容器启动时序中的位置------refresh() 的 invokeBeanFactoryPostProcessors 阶段会先执行所有 BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry,此时容器里的 bean 定义已经全部收集完毕(XML 加载完、注解 register/scan 完),但还没有任何单例 bean 被创建。JavaConfig 解析就插在这个窗口期:读取现有的 BeanDefinition 集合,把其中的配置类解析成更多 BeanDefinition,再注册回去。

3.12.1 postProcessBeanDefinitionRegistry:防重与委托

postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)(第 304-317 行)很短:

java 复制代码
@Override
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);
}

两个 Set<Integer> 记录"已经处理过的 registry 身份哈希",防止同一个 post-processor 被重复执行(正常流程不会,这是防御性设计)。真正的工作在 processConfigBeanDefinitions。

3.12.2 processConfigBeanDefinitions:两轮迭代解析

这个方法(第 389-490 行节选)是 JavaConfig 体系的主算法,分四步:

第一步:筛配置类候选(第 390-408 行):

java 复制代码
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	if (configCandidates.isEmpty()) {
		return;
	}

遍历 registry 里所有 bean 定义:带 CONFIGURATION_CLASS_ATTRIBUTE 属性的说明已经处理过,跳过;否则用 ConfigurationClassUtils.checkConfigurationClassCandidate 判断它是不是一个"配置类候选"(full 或 lite,见 3.13)。一个都没有就直接返回------这就是纯 XML 项目里这套 post-processor 几乎零开销退出的原因。

第二步:排序 (第 411-415 行):按 @Order 升序。@Order 影响多个配置类之间的处理顺序,尤其影响 @Bean 同名时谁覆盖谁。

第三步:parse → load 循环(第 444-489 行节选):

java 复制代码
	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = CollectionUtils.newHashSet(configCandidates.size());
	do {
		StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
		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());
		}
		this.reader.loadBeanDefinitions(configClasses);
		for (ConfigurationClass configClass : configClasses) {
			this.beanRegistrars.addAll(configClass.getBeanRegistrars());
		}
		alreadyParsed.addAll(configClasses);
		processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

		candidates.clear();
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			// ......新注册进来的 bean 定义里,若还有未处理的配置类,加入下一轮 candidates
		}
		candidateNames = newCandidateNames;
	}
	while (!candidates.isEmpty());

do...while 循环是关键设计:一轮 parse + load 之后,registry 里可能新增了 bean 定义(比如 @Import 进来的配置类、@Bean 方法产出的类),这些新定义中若还有"配置类候选",就进入下一轮继续解析。直到没有新配置类为止。为什么要循环?因为 @Import(AnotherConfig.class) 引入的类、@ComponentScan 扫出来的 @Configuration 类,都是在解析过程中动态产生的新配置类,单轮无法穷尽。

注意两阶段分离:parser.parse(candidates) 只把配置类解析成 ConfigurationClass 内存模型(不注册 bean);this.reader.loadBeanDefinitions(configClasses) 才把模型落成 BeanDefinition。parser.validate() 在两者之间做合法性校验(比如 full 配置类不能是 final、@Bean 方法不能是 final/private------见 ConfigurationClass.validate)。

3.12.3 postProcessBeanFactory:CGLIB 增强

第二个回调 postProcessBeanFactory(第 324-339 行):

java 复制代码
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	int factoryId = System.identityHashCode(beanFactory);
	if (this.factoriesPostProcessed.contains(factoryId)) {
		throw new IllegalStateException(
				"postProcessBeanFactory already called on this post-processor against " + beanFactory);
	}
	this.factoriesPostProcessed.add(factoryId);
	if (!this.registriesPostProcessed.contains(factoryId)) {
		// BeanDefinitionRegistryPostProcessor hook apparently not supported...
		// Simply call processConfigurationClasses lazily at this point then.
		processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
	}

	enhanceConfigurationClasses(beanFactory);
	beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

parse 阶段在 postProcessBeanDefinitionRegistry 已完成,这里做两件收尾:enhanceConfigurationClasses 把 full 模式的 @Configuration 类替换成 CGLIB 子类(见 3.17);注册一个 ImportAwareBeanPostProcessor,让实现了 ImportAware 的 bean 能拿到 @Import 它的那个配置类的注解元数据。

3.13 full 与 lite:ConfigurationClassUtils 的判定

一个类凭什么算"配置类"?ConfigurationClassUtils.checkConfigurationClassCandidate(spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java 第 106-165 行)给出答案。先看判定前置(第 109-144 行):

java 复制代码
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
	return false;
}

AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition annotatedBd &&
		className.equals(annotatedBd.getMetadata().getClassName())) {
	// Can reuse the pre-parsed metadata from the given BeanDefinition...
	metadata = annotatedBd.getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition abstractBd && abstractBd.hasBeanClass()) {
	// Check already loaded Class if present...
	// since we possibly can't even load the class file for this Class.
	Class<?> beanClass = abstractBd.getBeanClass();
	if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
			BeanPostProcessor.class.isAssignableFrom(beanClass) ||
			AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
			EventListenerFactory.class.isAssignableFrom(beanClass)) {
		return false;
	}
	metadata = AnnotationMetadata.introspect(beanClass);
}
else {
	try {
		MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
		metadata = metadataReader.getAnnotationMetadata();
	}
	catch (IOException ex) {
		// ......找不到 class 文件则返回 false
	}
}

三种元数据获取路径:① 已经是 AnnotatedBeanDefinition(扫描出来的/注解注册的),直接复用它自带的 ASM 元数据,不重复解析 ;② 已加载到 Class 的,直接内省,但如果它本身是 BeanFactoryPostProcessor/BeanPostProcessor/AopInfrastructureBean/EventListenerFactory,直接排除------这些基础设施组件不参与配置类解析,避免循环依赖;③ 其余走 MetadataReaderFactory 的 ASM 读取。

然后是 full/lite 判定(第 146-156 行):

java 复制代码
Map<String, @Nullable Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
	beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (config != null || Boolean.TRUE.equals(beanDef.getAttribute(CANDIDATE_ATTRIBUTE)) ||
		isConfigurationCandidate(metadata)) {
	beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
	return false;
}

规则清晰:

  • 类上有 @Configuration 注解,且 proxyBeanMethods 没有显式设为 false → full("full")。这是需要 CGLIB 增强的类。
  • 否则:有 @Configuration(即 proxyBeanMethods=false)、或是被 AnnotatedBeanDefinitionReader 直接注册的(带 CANDIDATE_ATTRIBUTE)、或 isConfigurationCandidate(metadata) 为真 → lite。
  • 都不是 → 不是配置类,本方法返回 false,这个 bean 定义不参与 JavaConfig 解析。

isConfigurationCandidate(第 174-189 行)定义了 lite 候选的另外两种来源:不是接口,且类上带 @Component/@ComponentScan/@Import/@ImportResource 任一(candidateIndicators 集合),或类里有 @Bean 方法(hasBeanMethods)。这就是为什么一个普通 @Component 类里写了 @Bean 方法也会被 JavaConfig 体系处理(lite 模式)。

判定结果存为 BeanDefinition 的一个 attribute(CONFIGURATION_CLASS_ATTRIBUTE = "full" 或 "lite",第 55-57 行),后续 enhanceConfigurationClasses 只挑 "full" 的类做 CGLIB 增强------lite 模式不增强,这就是 proxyBeanMethods=false 的全部代价语义。

3.14 ConfigurationClassParser:把配置类加工成 ConfigurationClass 图

ConfigurationClassParser(spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java)是 JavaConfig 的"语法分析器"。它不注册 bean,只产出 ConfigurationClass 模型集合。

3.14.1 parse:入口与降级

parse(Set<BeanDefinitionHolder> configCandidates)(第 168-201 行):

java 复制代码
public void parse(Set<BeanDefinitionHolder> configCandidates) {
	for (BeanDefinitionHolder holder : configCandidates) {
		BeanDefinition bd = holder.getBeanDefinition();
		try {
			ConfigurationClass configClass;
			if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
				configClass = parse(annotatedBeanDef, holder.getBeanName());
			}
			else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
				configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
			}
			else {
				configClass = parse(bd.getBeanClassName(), holder.getBeanName());
			}

			// Downgrade to lite (no enhancement) in case of no instance-level @Bean methods.
			if (!configClass.getMetadata().isAbstract() && !configClass.hasNonStaticBeanMethods() &&
					ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
							bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
				bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
						ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
			}
		}
		// ......异常包装略
	}

	this.deferredImportSelectorHandler.process();
}

逐段:① 按 BeanDefinition 的三种形态(带元数据的、已加载 Class 的、只有类名的)选对应的 parse 重载,最终都汇聚到 processConfigurationClass;② 7.0.9 的一个重要优化 :full 类若没有任何非 static 的 @Bean 方法(hasNonStaticBeanMethods() 为 false),直接降级为 lite------因为 CGLIB 增强要拦截的就是"实例 @Bean 方法之间的互调",没有互调就没必要增强,白白损失性能;③ 所有普通配置类处理完后,最后一行 this.deferredImportSelectorHandler.process()------DeferredImportSelector 统一延后到此时才执行 ,这是 @EnableXxx 组合注解模式的关键(见 3.16)。

3.14.2 processConfigurationClass:去重与父类递归

processConfigurationClass(第 248-295 行)先做条件检查与去重:

java 复制代码
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) {
	if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
		return;
	}

	ConfigurationClass existingClass = this.configurationClasses.get(configClass);
	if (existingClass != null) {
		if (configClass.isImported()) {
			if (existingClass.isImported()) {
				existingClass.mergeImportedBy(configClass);
			}
			// Otherwise ignore new imported config class; existing non-imported class overrides it.
			return;
		}
		else if (configClass.isScanned()) {
			// ......被扫描出来的同类型类若已有 import 记录,则移除其 registry 定义
			return;
		}
		else {
			// Explicit bean definition found, probably replacing an import.
			// Let's remove the old one and go with the new one.
			this.configurationClasses.remove(configClass);
			removeKnownSuperclass(configClass.getMetadata().getClassName(), false);
		}
	}

	// Recursively process the configuration class and its superclass hierarchy.
	SourceClass sourceClass = null;
	try {
		sourceClass = asSourceClass(configClass, filter);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);
	}
	// ......
	this.configurationClasses.put(configClass, configClass);
}

要点:① 解析阶段的 @Conditional(ConfigurationPhase.PARSE_CONFIGURATION)在这里判定,不满足就整个跳过这个配置类;② 同一个配置类被多次发现(既被 @Import 又被扫描到)时的合并/覆盖规则------imported 之间合并 importedBy 关系,显式定义覆盖 import;③ 父类递归 :doProcessConfigurationClass 返回非 null 就继续处理父类,直到返回 null(没有父类了)。这个 do...while 配合 doProcessConfigurationClass 末尾的"返回父类"实现了配置类继承链的逐层处理。

3.14.3 doProcessConfigurationClass:一个配置类的完整加工清单

这是本章最核心的方法(第 305-405 行),按固定顺序处理六类信息。逐段讲:

① 嵌套成员类(第 309-312 行):

java 复制代码
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
	// Recursively process any member (nested) classes first
	processMemberClasses(configClass, sourceClass, filter);
}

只有当配置类本身带 @Component(@Configuration 是其组合注解)时才处理嵌套类。processMemberClasses(第 410 行起)找出所有嵌套类中"是配置候选"的,递归解析------这就是为什么 @Configuration 类里可以写嵌套的 @Configuration 类。

② @PropertySource (第 315-325 行):收集 @PropertySource/@PropertySources,交给 PropertySourceRegistry.processPropertySource 处理------把 properties 文件加载进 Environment。注意它只是登记 descriptor,真正加载在后续进行。

③ @ComponentScan(第 328-361 行):

java 复制代码
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
		sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class,
		MergedAnnotation::isDirectlyPresent);

// Fall back to searching for @ComponentScan meta-annotations (which indirectly
// includes locally declared composed annotations).
if (componentScans.isEmpty()) {
	componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(),
			ComponentScan.class, ComponentScans.class, MergedAnnotation::isMetaPresent);
}

if (!componentScans.isEmpty()) {
	List<Condition> registerBeanConditions = collectRegisterBeanConditions(configClass);
	if (!registerBeanConditions.isEmpty()) {
		throw new ApplicationContextException(
				"Component scan for configuration class [%s] could not be used with conditions in REGISTER_BEAN phase: %s"
						.formatted(configClass.getMetadata().getClassName(), registerBeanConditions));
	}
	for (AnnotationAttributes componentScan : componentScans) {
		// The config class is annotated with @ComponentScan -> perform the scan immediately
		Set<BeanDefinitionHolder> scannedBeanDefinitions =
				this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
		// Check the set of scanned definitions for any further config classes and parse recursively if needed
		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());
			}
		}
	}
}

两层查找:先找直接声明 的 @ComponentScan(含 @ComponentScans 容器),找不到再退而求其次找元注解 形式(@SpringBootApplication 上的 @ComponentScan 就是通过 @SpringBootApplication 的元注解链被发现的)。然后调 3.11 的 ComponentScanAnnotationParser.parse 立刻执行扫描;扫出来的 bean 定义里若还有配置类候选,递归 parse ------这就是扫描与配置解析互相驱动的闭环。还有一条 7.0 新增的硬性约束:扫描所在配置类若带 REGISTER_BEAN 阶段的 @Conditional,直接抛异常------因为扫描结果在 parse 阶段就注册进 registry 了,条件评估时机对不上。

④ @Import(第 364 行):

java 复制代码
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

getImports 收集类上及元注解链上所有 @Import 的 value,交给 processImports------下一节专门讲它的三分支。

⑤ @ImportResource(第 367-376 行):

java 复制代码
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);
	}
}

注意这里只是登记 (configClass.addImportedResource),并不立刻加载 XML。真正的 XML 读取延迟到 ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources(见 3.15),由登记的 readerClass(默认 XmlBeanDefinitionReader)去加载。这是"先建模型、后落定义"两阶段结构的又一例证。

⑥ @Bean 方法 + 接口默认方法 + 父类(第 379-404 行):

java 复制代码
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
	if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) {
		continue;
	}
	configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

// Process default methods on interfaces
processInterfaces(configClass, sourceClass);

// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
	String superclass = sourceClass.getMetadata().getSuperClassName();
	if (superclass != null && !superclass.startsWith("java")) {
		boolean superclassKnown = this.knownSuperclasses.containsKey(superclass);
		this.knownSuperclasses.add(superclass, configClass);
		if (!superclassKnown) {
			// Superclass found, return its annotation metadata and recurse
			return sourceClass.getSuperClass();
		}
	}
}

// No superclass -> processing is complete
return null;

@Bean 方法收集成 BeanMethod 对象挂到 configClass 上(同样只是登记,不注册);对 Kotlin 的 @JvmStatic 做了特判跳过;processInterfaces 处理配置类实现接口上的 default 方法 (接口 default 方法上的 @Bean 也会被收集);最后若非 java.包 开头的父类且尚未处理过,return sourceClass.getSuperClass()------processConfigurationClass 的 do...while 会拿着这个返回值再次调用本方法,逐层向上处理父类。knownSuperclasses 记录"哪个配置类依赖哪个父类",用于父类被多个子类共享时避免重复解析。

这六步跑完,一个 ConfigurationClass 对象就装满了:嵌套类、PropertySource、ComponentScan 结果、imports、importedResources、beanMethods。下一步是 processImports 的三分支处理。

3.15 processImports:@Import 的三条岔路

@Import 是 Spring 容器扩展机制的总钥匙。processImports(ConfigurationClassParser 第 581-652 行)按被导入类的类型分四个分支:

java 复制代码
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
		Collection<SourceClass> importCandidates, Predicate<String> filter, 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) {
				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) {
						filter = filter.or(selectorFilter);
					}
					if (selector instanceof DeferredImportSelector deferredImportSelector) {
						this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);
					}
					else {
						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, filter);
						processImports(configClass, currentSourceClass, importSourceClasses, filter, false);
					}
				}
				else if (candidate.isAssignable(BeanRegistrar.class)) {
					// 7.0 新增:BeanRegistrar 分支
					Class<?> candidateClass = candidate.loadClass();
					BeanRegistrar registrar = (BeanRegistrar) BeanUtils.instantiateClass(candidateClass);
					AnnotationMetadata metadata = currentSourceClass.getMetadata();
					if (registrar instanceof ImportAware importAware) {
						importAware.setImportMetadata(metadata);
					}
					configClass.addBeanRegistrar(metadata.getClassName(), registrar);
				}
				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 {
					// 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), filter);
				}
			}
		}
		// ......异常包装与 finally pop 略
	}
}

四条分支对应 @Import(XXX.class) 中 XXX 的四种身份:

  1. 普通类 (else 分支):被导入的类本身当作一个配置类来解析(processConfigurationClass),并记录 importedBy 关系。最常见的 @Import(SomeConfig.class) 走这里。
  2. ImportSelector (非 Deferred):实例化选择器,调 selectImports(元数据) 拿到一组类名,递归 processImports 处理这些类名------选择器只是"决定导入谁"的工厂,实际导入动作还是这套递归。
  3. DeferredImportSelector :不立即处理,扔进 deferredImportSelectorHandler,等所有配置类 parse 完后统一执行(3.16 详述)。
  4. ImportBeanDefinitionRegistrar :不解析成配置类,而是登记进 configClass.addImportBeanDefinitionRegistrar(...),等 reader 阶段回调它的 registerBeanDefinitions 方法,由它直接操作 registry注册任意 bean 定义。
  5. 另外 7.0 新增了 BeanRegistrar 分支(比 ImportBeanDefinitionRegistrar 更现代的编程模型,见 org.springframework.beans.factory.BeanRegistrar),同样登记后延迟回调。

importStack 是一个 ImportStack(继承 LinkedHashMap<ConfigurationClass, ConfigurationClass>),记录 import 的嵌套链;isChainedImportOnStack 检测循环 import------A import B、B import A 这种环在解析阶段就报 CircularImportProblem。

注意 ParserStrategyUtils.instantiateClass(..., environment, resourceLoader, registry):实例化选择器/registrar 时会自动回调它们可能实现的 EnvironmentAware/ResourceLoaderAware/BeanFactoryAware 等接口------这就是为什么写 ImportSelector 时可以直接注入 Environment。

3.16 DeferredImportSelector:@EnableXxx 模式的延迟导入

@EnableTransactionManagement、@EnableAsync 这类注解的本体就是 @Import(SomeDeferredImportSelector.class)。为什么用 Deferred 而不是普通 ImportSelector?看 3.14.1 结尾:所有配置类 parse 完后才 this.deferredImportSelectorHandler.process()。DeferredImportSelectorHandler(第 801 行起)内部维护一个 deferredImportSelectors 列表,handle 方法(第 813 行)先把 holder 存起来:

java 复制代码
void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
	DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
	if (this.deferredImportSelectors == null) {
		DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
		handler.register(holder);
		handler.processGroupImports();
	}
	else {
		this.deferredImportSelectors.add(holder);
	}
}

process() 时(第 826-841 行附近):把列表置 null,按 @Order 排序(第 114-115 行的 DEFERRED_IMPORT_COMPARATOR),再交给 DeferredImportSelectorGroupingHandler------后者按 selector 声明的 getImportGroup() 分组(同组的 selector 共享一个 Group,可在 Group.process 里协同决定导入顺序,典型如 Spring Boot 的自动配置分组),最后逐个调 selector 的 selectImports 并递归 processImports。

延迟的意义 :DeferredImportSelector 的 selectImports 在所有用户配置类都解析完之后才执行,此时它看到的 registry 已经包含了用户自己定义的所有 bean。这就是自动配置类能做"用户没定义才兜底注册"(@ConditionalOnMissingBean 之类)的时序基础------普通 ImportSelector 是边解析边执行,看不到后面才出现的 bean。

3.17 ConfigurationClassBeanDefinitionReader:把模型落成 BeanDefinition

解析阶段产出的 ConfigurationClass 集合,由 ConfigurationClassBeanDefinitionReader(spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java)消费。入口 loadBeanDefinitions(Set<ConfigurationClass>)(第 121-126 行)极薄:

java 复制代码
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

loadBeanDefinitionsForConfigurationClass(第 132-154 行)按顺序处理四类产出:

java 复制代码
if (trackedConditionEvaluator.shouldSkip(configClass)) {
	// ......条件不满足则从 registry 移除该配置类自己的定义并 return
}

if (configClass.isImported()) {
	registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
	loadBeanDefinitionsForBeanMethod(beanMethod);
}

loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromImportBeanDefinitionRegistrars(configClass.getImportBeanDefinitionRegistrars());
loadBeanDefinitionsFromBeanRegistrars(configClass.getBeanRegistrars());

顺序是:先处理 REGISTER_BEAN 阶段的 @Conditional(parse 阶段评估过一次,注册阶段再评估一次------TrackedConditionEvaluator 带缓存);imported 的配置类自己注册成 bean(registerBeanDefinitionForImportedConfigurationClass,第 159-184 行:new AnnotatedGenericBeanDefinition(metadata),走 scope 解析、通用注解处理、注册);然后逐个 @Bean 方法;最后处理 importedResources(这里才真正加载 @ImportResource 指向的 XML ,第 377 行附近:按登记的 readerClass new 一个 reader,调 loadBeanDefinitions(location)------绕回 3.2 的 XML 链路)、importBeanDefinitionRegistrars(第 380 行附近:逐个回调 registrar.registerBeanDefinitions(metadata, registry))。

loadBeanDefinitionsForBeanMethod(第 190-310 行)是把 @Bean 方法翻译成 ConfigurationClassBeanDefinition 的地方,关键片段:

java 复制代码
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
	ConfigurationClass configClass = beanMethod.getConfigurationClass();
	MethodMetadata metadata = beanMethod.getMetadata();
	String methodName = metadata.getMethodName();

	// Do we need to mark the bean as skipped by its condition?
	if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
		configClass.skippedBeanMethods.add(methodName);
		return;
	}
	if (configClass.skippedBeanMethods.contains(methodName)) {
		return;
	}

	AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
	Assert.state(bean != null, "No @Bean annotation attributes");

	// Consider name and any aliases.
	String[] explicitNames = bean.getStringArray("name");
	String beanName = (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) ? explicitNames[0] : null;
	String localBeanName = defaultBeanName(beanName, methodName);
	beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ?
			cbng.deriveBeanName(metadata, beanName) : localBeanName);
	if (explicitNames.length > 0) {
		// Register aliases even when overridden below.
		for (int i = 1; i < explicitNames.length; i++) {
			this.registry.registerAlias(beanName, explicitNames[i]);
		}
	}
	// ......创建 ConfigurationClassBeanDefinition、static/instance 分支、autowireMode、scope、注册
}

关键点:

  1. REGISTER_BEAN 阶段的 @Conditional :方法上的条件注解在这里第二次评估(parse 阶段评过 PARSE_CONFIGURATION 阶段),不满足就记入 skippedBeanMethods 并 return。
  2. 命名 :@Bean(name = {"x","y","z"}) 第一个是 bean 名,其余注册成别名;没写 name 就用方法名(defaultBeanName,第 312-314 行)。
  3. static 与 instance 的区别 (第 234-248 行):static @Bean 方法 beanDef.setBeanClass(...) + setUniqueFactoryMethodName------bean 类直接是方法所在类,不依赖配置类实例;instance 方法 beanDef.setFactoryBeanName(configClass.getBeanName())------bean 的创建要先实例化配置类、再调它的方法。这个区分是 CGLIB 增强能否生效的前提(static 方法不需要代理)。
  4. autowireMode 固定为 AUTOWIRE_CONSTRUCTOR(第 258 行)------@Bean 方法的参数自动按构造器方式解析。
  5. 覆盖检查 (第 225-232、317-354 行):isOverriddenByExistingDefinition 处理同名冲突------同配置类内重载方法保留第一个并标记 setNonUniqueFactoryMethodName;已有定义是 ScannedGenericBeanDefinition 且返回类型相同则移除扫描定义(@Bean 覆盖扫描结果);若 enforceUniqueMethods 或容器禁用覆盖则抛 BeanDefinitionOverrideException。

3.18 full 模式的 CGLIB 增强

为什么 @Configuration 类需要 CGLIB 代理?设想你的配置类里 @Bean A a() { return new A(b()); },b() 是另一个 @Bean 方法。如果直接调用 b(),那只是一次普通 Java 方法调用------new 出来一个全新的 B 对象,绕过了容器,单例语义被破坏。full 模式的解法:把配置类做成 CGLIB 子类,子类里每个 @Bean 方法都被拦截,拦截器先问容器要 bean,而不是真的去 new。

enhanceConfigurationClasses(ConfigurationClassPostProcessor 第 514-586 行):

java 复制代码
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
	StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");
	Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
	for (String beanName : beanFactory.getBeanDefinitionNames()) {
		BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
		Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
		AnnotationMetadata annotationMetadata = null;
		MethodMetadata methodMetadata = null;
		if (beanDef instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
			annotationMetadata = annotatedBeanDefinition.getMetadata();
			methodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
		}
		if ((configClassAttr != null || methodMetadata != null) &&
				(beanDef instanceof AbstractBeanDefinition abd) && !abd.hasBeanClass()) {
			// Configuration class (full or lite) or a configuration-derived @Bean method
			// -> eagerly resolve bean class at this point, unless it's a 'lite' configuration
			// or component class without @Bean methods.
			boolean liteConfigurationCandidateWithoutBeanMethods =
					(ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
						annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
			if (!liteConfigurationCandidateWithoutBeanMethods) {
				try {
					abd.resolveBeanClass(this.beanClassLoader);
				}
				catch (Throwable ex) {
					throw new IllegalStateException(
							"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
				}
			}
		}
		if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
			if (!(beanDef instanceof AbstractBeanDefinition abd)) {
				throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
						beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
			}
			else if (beanFactory.containsSingleton(beanName)) {
				if (logger.isWarnEnabled()) {
					logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
							"' since its singleton instance has been created too early. The typical cause " +
							"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
							"return type: Consider declaring such methods as 'static' and/or marking the " +
							"containing configuration class as 'proxyBeanMethods=false'.");
				}
			}
			else {
				configBeanDefs.put(beanName, abd);
			}
		}
	}
	if (configBeanDefs.isEmpty()) {
		// nothing to enhance -> return immediately
		enhanceConfigClasses.end();
		return;
	}

	ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
	for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
		AbstractBeanDefinition beanDef = entry.getValue();
		// If a @Configuration class gets proxied, always proxy the target class
		beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
		// Set enhanced subclass of the user-specified bean class
		Class<?> configClass = beanDef.getBeanClass();
		Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
		if (configClass != enhancedClass) {
			beanDef.setBeanClass(enhancedClass);
		}
	}
	enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
}

逐段解读:

  1. 先提前解析类 。配置类此时还只有类名(!abd.hasBeanClass()),要 CGLIB 增强必须先拿到 Class 对象,所以这里 resolveBeanClass 显式触发类加载------注意豁免:lite 且没有 @Bean 方法的类不加载(反正不增强)。
  2. 只挑 full 类 。CONFIGURATION_CLASS_FULL.equals(configClassAttr) 才进待增强集合;lite 类直接跳过。
  3. 过早实例化告警 。beanFactory.containsSingleton(beanName) 为 true 说明这个配置类已经被实例化了(典型原因:某个非 static @Bean 方法返回了 BeanDefinitionRegistryPostProcessor,导致容器不得不在增强之前先创建它)------此时无法替换 beanClass,只能打警告日志。这就是"@Bean 方法返回 BeanFactoryPostProcessor 必须声明 static"的源码依据。
  4. 替换 beanClass 。对每个 full 类,enhancer.enhance(configClass, classLoader) 生成 CGLIB 子类,然后 beanDef.setBeanClass(enhancedClass)------容器后续实例化的就是这个子类。同时打 PRESERVE_TARGET_CLASS_ATTRIBUTE,告诉 AOP 不要因为代理链而影响它。

ConfigurationClassEnhancer.enhance(spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java 第 102-140 行):

java 复制代码
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
	if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Ignoring request to enhance %s as it has " +
					"already been enhanced. This usually indicates that more than one " +
					"ConfigurationClassPostProcessor has been registered ...",
					configClass.getName()));
		}
		return configClass;
	}

	try {
		// Use original ClassLoader if config class not locally loaded in overriding class loader
		boolean classLoaderMismatch = (classLoader != null && classLoader != configClass.getClassLoader());
		if (classLoaderMismatch && classLoader instanceof SmartClassLoader smartClassLoader) {
			classLoader = smartClassLoader.getOriginalClassLoader();
			classLoaderMismatch = (classLoader != configClass.getClassLoader());
		}
		// Use original ClassLoader if config class relies on package visibility
		if (classLoaderMismatch && reliesOnPackageVisibility(configClass)) {
			classLoader = configClass.getClassLoader();
			classLoaderMismatch = false;
		}
		Enhancer enhancer = newEnhancer(configClass, classLoader);
		Class<?> enhancedClass = createClass(enhancer, classLoaderMismatch);
		return enhancedClass;
	}
	catch (CodeGenerationException ex) {
		throw new BeanDefinitionStoreException("Could not enhance configuration class [" + configClass.getName() +
				"]. Consider declaring @Configuration(proxyBeanMethods=false) without inter-bean references " +
				"between @Bean methods on the configuration class, avoiding the need for CGLIB enhancement.", ex);
	}
}

两个值得注意的点:① 若类已实现 EnhancedConfiguration 接口(说明已被增强过),直接返回------防重复增强(多个 CCPP 注册时无害但提示你去重);② reliesOnPackageVisibility(第 146-166 行)检查配置类/构造器/@Bean 方法是否用了包级可见性------CGLIB 子类必须能访问父类成员,若跨 ClassLoader 又依赖包私有可见性,就退回用原 ClassLoader 生成子类,否则生成的子类在新 ClassLoader 里根本访问不了父类成员。失败时的异常信息直接建议你改用 proxyBeanMethods=false。

拦截器链在类加载时就固定(第 81-85 行):

java 复制代码
static final Callback[] CALLBACKS = new Callback[] {
		new BeanMethodInterceptor(),
		new BeanFactoryAwareMethodInterceptor(),
		NoOp.INSTANCE
};

BeanMethodInterceptor.intercept(第 351-402 行)是整个机制的心脏:

java 复制代码
public @Nullable Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
			MethodProxy cglibMethodProxy) throws Throwable {

	ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
	String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod, beanFactory);

	// ......scoped-proxy 特判略

	if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
		// The factory is calling the bean method in order to instantiate and register the bean
		// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
		// create the bean instance.
		if (logger.isInfoEnabled() &&
				BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
			logger.info("""
					@Bean method %s.%s is non-static and returns an object assignable to Spring's \
					BeanFactoryPostProcessor interface. ... Add the 'static' modifier to this method ...
					""".formatted(...));
		}
		return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
	}

	return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

判定逻辑是一问:当前是不是容器自己在调这个 @Bean 方法? isCurrentlyInvokedFactoryMethod 查 SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod()------容器创建 bean 时会在 ThreadLocal 里记录"正在调哪个工厂方法"。是 → invokeSuper 执行真实方法体(这是创建 bean 的正经路径);不是(即用户代码在配置类里 b() 这样互调)→ resolveBeanReference(第 404-468 行)转而 beanFactory.getBean(beanName) 从容器拿单例。这就是"互调失效"与"互调走容器"的分水岭:full 模式下 a() 里调 b(),拦截器发现此刻不是容器在创建 b,于是 getBean("b") 返回容器里那个单例;lite 模式没有拦截器,b() 就是裸方法调用,每次 new 新对象。

3.19 AnnotatedBeanDefinitionReader:直接注册类

最后一条元数据路径是最直白的:new AnnotationConfigApplicationContext(AppConfig.class) 时,register(componentClasses) 委托给 AnnotatedBeanDefinitionReader(spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java)。它的 doRegisterBean(第 252-293 行):

java 复制代码
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
		Class<? extends Annotation> @Nullable [] qualifiers, @Nullable Supplier<T> supplier,
		BeanDefinitionCustomizer @Nullable [] customizers) {

	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
	if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
		return;
	}

	abd.setAttribute(ConfigurationClassUtils.CANDIDATE_ATTRIBUTE, Boolean.TRUE);
	abd.setInstanceSupplier(supplier);
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
	String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
	if (qualifiers != null) {
		for (Class<? extends Annotation> qualifier : qualifiers) {
			if (Primary.class == qualifier) {
				abd.setPrimary(true);
			}
			else if (Fallback.class == qualifier) {
				abd.setFallback(true);
			}
			else if (Lazy.class == qualifier) {
				abd.setLazyInit(true);
			}
			else {
				abd.addQualifier(new AutowireCandidateQualifier(qualifier));
			}
		}
	}
	if (customizers != null) {
		for (BeanDefinitionCustomizer customizer : customizers) {
			customizer.customize(abd);
		}
	}

	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

流程:① new AnnotatedGenericBeanDefinition(beanClass)------这里是真的加载了类 (beanClass 是用户直接传入的 Class 对象),元数据通过 AnnotationMetadata.introspect 内省;② 条件不满足直接 return(@Conditional 在手动 register 阶段就生效);③ 打 CANDIDATE_ATTRIBUTE=TRUE 标记------回看 3.13:这个标记让即使没有 @Configuration 的类也能成为 lite 配置类候选;④ scope、beanName(显式 name 或自动生成);⑤ processCommonDefinitionAnnotations 提取 @Lazy/@Primary/@DependsOn/@Role/@Description------和扫描路径共用同一个方法;⑥ 编程式 qualifiers/customizers;⑦ 最后经 applyScopedProxyMode + BeanDefinitionReaderUtils.registerBeanDefinition 注册------与 XML、扫描殊途同归。

4. 运行机制:一次启动中配置解析的完整时序

把前面的源码串成一条时间线。以最典型的 new AnnotationConfigApplicationContext(AppConfig.class) 为例(spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java 第 89-93 行):

java 复制代码
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}

T0 --- 构造器 this()(第 68-71 行):

java 复制代码
public AnnotationConfigApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}

reader 和 scanner 创建时,AnnotatedBeanDefinitionReader 的构造链路会触发 AnnotationConfigUtils.registerAnnotationConfigProcessors(this)------把 ConfigurationClassPostProcessor(bean 名 annotationConfigProcessor,见 AnnotationConfigUtils 第 69、158-161 行)、AutowiredAnnotationBeanPostProcessor 等基础设施 作为普通 BeanDefinition 注册进 registry。注意此刻还没有解析任何用户配置类。

T1 --- register(AppConfig.class) :走 3.19 的 doRegisterBean,把 AppConfig 本身注册成一条 AnnotatedGenericBeanDefinition(带 CANDIDATE_ATTRIBUTE)。此时 registry 里只有:基础设施 post-processor 们 + AppConfig 自己。

T2 --- refresh() → invokeBeanFactoryPostProcessors() :这是配置解析真正发生的阶段。按 PriorityOrdered→Ordered→普通的顺序执行所有 BeanDefinitionRegistryPostProcessor:

  1. ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry 被调用(3.12):遍历 registry,checkConfigurationClassCandidate 认出 AppConfig 是 full/lite 候选 → parser.parse() 加工成 ConfigurationClass 模型(途中若 AppConfig 有 @ComponentScan,立刻触发扫描,registry 里多出一批 ScannedGenericBeanDefinition;若有 @Import,按 3.15 三分支处理)→ reader.loadBeanDefinitions() 把模型落成 ConfigurationClassBeanDefinition(@Bean 方法)+ 可能的 XML 加载(@ImportResource)。do-while 循环直到没有新配置类。
  2. 同一阶段,其他 BeanDefinitionRegistryPostProcessor(如用户自己注册的)也在这里执行。
  3. 随后 postProcessBeanFactory 回调(3.12.3):enhanceConfigurationClasses 把 full 配置类的 beanClass 替换成 CGLIB 子类。

T3 --- 此后的 refresh 步骤 :registerBeanPostProcessors、finishBeanFactoryInitialization(实例化单例)......配置解析阶段彻底结束,容器开始按已注册的 BeanDefinition 创建 bean。

时序图:

scss 复制代码
new AnnotationConfigApplicationContext(AppConfig.class)
  │
  ├─ this()                    注册基础设施 BeanDefinition(含 CCPP)
  ├─ register(AppConfig.class)  AnnotatedBeanDefinitionReader → registry 加 1 条
  └─ refresh()
       └─ invokeBeanFactoryPostProcessors()
            ├─ CCPP.postProcessBeanDefinitionRegistry
            │    ├─ checkConfigurationClassCandidate 筛出 AppConfig
            │    ├─ ConfigurationClassParser.parse
            │    │    ├─ @ComponentScan → ClassPathBeanDefinitionScanner.doScan(ASM 扫包)
            │    │    ├─ @Import → processImports(3 分支 / Deferred 延后)
            │    │    └─ @ImportResource → 登记 XML 位置
            │    └─ ConfigurationClassBeanDefinitionReader.loadBeanDefinitions
            │         ├─ @Bean 方法 → ConfigurationClassBeanDefinition
            │         ├─ imported XML → XmlBeanDefinitionReader 走 XML 链路
            │         └─ ImportBeanDefinitionRegistrar 回调
            └─ CCPP.postProcessBeanFactory
                 └─ enhanceConfigurationClasses → full 类 CGLIB 子类化

XML 与注解混用的覆盖规则(结合 3.10 与 3.17 的源码):

  • @Bean 方法产出的定义可以覆盖 扫描出来的 ScannedGenericBeanDefinition(类名相同时静默移除扫描定义,3.17 isOverriddenByExistingDefinition 第 349-353 行)------即 @ComponentScan 扫到的 UserService 被 @Bean 方法声明的同名同类型 bean 顶替。
  • 反过来,扫描遇到已存在的非扫描 定义(XML 显式注册或 @Bean)且允许覆盖或类名相同,则扫描结果静默跳过(3.10 checkCandidate)。
  • 两个显式 定义(XML vs XML、XML vs @Bean)同名且类型不同:6.1 起默认 beanDefinitionOverridingAllowed=false,直接抛 BeanDefinitionOverrideException;若显式开启覆盖,则后注册的覆盖先注册的------具体顺序取决于 post-processor 执行顺序与注册顺序。
  • XML 里 <import> 与注解 @ImportResource 都是"加载另一份元数据",前者由 DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource 处理,后者在 reader 阶段由登记的 readerClass 加载,最终都汇入同一个 registry,覆盖规则一致。

5. 扩展用法与最佳实践

5.1 自定义 NamespaceHandler 与 XSD 的完整流程

要让你自己的 XML 标签(如 <myns:foo/>)被容器识别,做四件事:

  1. 写一个 .xsd 文件 描述你的标签语法,放在 jar 包内(如 META-INF/myns.xsd)。
  2. 实现 NamespaceHandler :继承 NamespaceHandlerSupport,在 init() 里 registerBeanDefinitionParser("foo", new MyFooParser());Parser 实现 BeanDefinitionParser.parse(Element, ParserContext),内部把元素翻译成 BeanDefinitionHolder 并 parserContext.getRegistry().registerBeanDefinition(...)(可参考 spring-context 里 ComponentScanBeanDefinitionParser 的写法)。
  3. 写 META-INF/spring.handlers :一行 http\://your-uri/myns=com.you.MyNamespaceHandler。DefaultNamespaceHandlerResolver 启动时 loadAllProperties 扫描所有 jar 的该文件合并,你的 URI→类映射就此生效。
  4. 写 META-INF/spring.schemas :一行 http\://your-uri/myns.xsd=META-INF/myns.xsd,让 PluggableSchemaResolver 把远程 schema URL 重定向到本地资源------否则容器会尝试联网下载 XSD。

用户侧用法:XML 头声明 xmlns:myns="http://your-uri/myns" + xsi:schemaLocation,然后 <myns:foo/>。标签出现时走 3.7 的 parseCustomElement → resolve(uri) → handler.parse(...)。

5.2 自定义 ImportBeanDefinitionRegistrar

比 XML 扩展更轻量的注解方式:实现 ImportBeanDefinitionRegistrar,重写 registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry),在里面任意注册 BeanDefinition。再用 @Import(MyRegistrar.class) 把它挂到某个 @EnableXxx 注解上。3.15 已看到:解析到它时不实例化成 bean,而是登记进 ConfigurationClass,在 reader 阶段回调------此时 registry 已包含用户全部配置类,你可以参照 AnnotationConfigUtils.registerAnnotationConfigProcessors 的模式做条件注册。7.0 起新写扩展优先考虑 BeanRegistrar(3.15 第四个分支),它提供更类型安全的 DSL 式注册 API。

5.3 @Conditional 与解析顺序

@Conditional 在两个阶段评估:组件扫描时(ClassPathScanningCandidateComponentProvider.isConditionMatch,3.9.3)与配置类/@Bean 解析时(ConfigurationClassParser 的 PARSE_CONFIGURATION 阶段 + ConfigurationClassBeanDefinitionReader 的 REGISTER_BEAN 阶段)。写条件类时注意:REGISTER_BEAN 阶段的条件出现在 @ComponentScan 所在类上会直接报错(3.14.3 ③),而 DeferredImportSelector 的执行时机(全部用户配置类解析完之后)决定了"自动配置类按用户定义兜底"的可行性。

5.4 性能与线程安全

  • 扫描阶段用 ASM(CachingMetadataReaderFactory 缓存 MetadataReader),不加载类------但 @Conditional/过滤器里若用 MetadataReader.getClassMetadata().getIntrospectedClass() 就会触发加载,写自定义 TypeFilter 时优先用 ASM 元数据字符串比较,保持类隔离。
  • META-INF/spring.handlers/spring.schemas 都是懒加载 + 并发缓存(ConcurrentHashMap + 双重检查锁),容器启动后只读,线程安全。
  • full 模式 CGLIB 增强有开销:每个 full 配置类生成一个子类。配置类多、@Bean 方法之间无互调时,显式 @Configuration(proxyBeanMethods = false)(或直接用 @Component)可跳过增强------3.14.1 的自动降级也会在没有实例 @Bean 方法时生效。

6. 常见问题与坑

Q1:同名 bean 覆盖,6.1 之后报 BeanDefinitionOverrideException? 源码依据:ConfigurationClassBeanDefinitionReader.isOverriddenByExistingDefinition(3.17)与 DefaultListableBeanFactory.registerBeanDefinition 的覆盖开关。两个不同来源的同名 bean(尤其一个来自 XML、一个来自 @Bean)类型又不同时,7.0.9 默认拒绝覆盖。解法:定位来源(异常信息里会带 resourceDescription),统一命名,或在 application.properties 开 spring.main.allow-bean-definition-overriding=true。注意扫描结果与显式定义之间有特殊豁免(3.10/3.17),不受这个开关影响。

Q2:@ComponentScan 与 @Bean 冲突? 扫描器 checkCandidate(3.10)遇到已有非扫描定义时:允许覆盖或类名相同就静默跳过扫描结果;两个扫描来源同名不同类才抛 ConflictingBeanDefinitionException。典型踩坑:两个 @Controller 类默认bean名相同(都叫 userController)------报冲突;解法是显式命名或检查包扫描范围重叠。

7. 本章小结

本章沿三条主线走完了"元数据 → BeanDefinition"的全过程:XML 线(XmlBeanDefinitionReader → DefaultBeanDefinitionDocumentReader → BeanDefinitionParserDelegate → NamespaceHandler 插件机制)、扫描线(ComponentScanAnnotationParser/ComponentScanBeanDefinitionParser → ClassPathBeanDefinitionScanner.doScan → ASM + TypeFilter + @Conditional)、JavaConfig 线(ConfigurationClassPostProcessor → ConfigurationClassParser 两阶段解析 → ConfigurationClassBeanDefinitionReader 落定义 → full 类 CGLIB 增强)。三条线最终都汇入同一个 BeanDefinitionRegistry,并在 invokeBeanFactoryPostProcessors 阶段完成。

相关推荐
Ikalus19881 小时前
一晚上提了两个 AI 补丁:一个词拒,等了 22 小时;一个话拒,等了 15 小时
开源
NGINX开源社区1 小时前
与我们交流:NGINX Gateway Fabric 与 NGINX Ingress Controller 社区会议
java·服务器·前端
李白客1 小时前
信创里,「开源」正在被「可控」取代
开源
鶴哥只手遮天1 小时前
Spring Framework 7.0.9 深度剖析-16-异步与调度
java·后端·开源
高频因子挖掘机1 小时前
500只股票 × 多年日线:数据获取与本地存储如何分工?
后端·github·api
wflynn1 小时前
GitHub 日榜趋势速报 | 2026-10-16
开源·github
张立立1 小时前
取名不想只靠翻字典?我做了个诗词取名工具,还能分析三才五格
后端
十年Java程序媛1 小时前
Java 多线程基础与锁机制|进程线程、synchronized、volatile、Lock 面试全梳理
java·spring boot·面试
弈栈录1 小时前
大模型服务抽象:兼容 OpenAI、通义千问与本地模型
java·后端·架构