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把 DOMDocument交出去,具体怎么遍历子节点由它负责。 - 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();
}
}
}
逐段解读:
- 循环 import 检测 。
resourcesCurrentlyBeingLoaded是一个NamedThreadLocal<Set<EncodedResource>>(类字段区声明)。<import resource="a.xml"/>触发递归加载时,若 a.xml 又 import 了自己(直接或间接成环),currentResources.add(encodedResource)返回 false------Set 不允许重复元素------于是立刻抛出 "Detected cyclic loading"。这就是"循环 import XML 启动报错"的源码出处。finally里加载完成后把当前资源从集合移除,集合空了就清理 ThreadLocal,避免线程池复用时残留脏状态。 - 编码注入 SAX 。打开
InputStream后包成InputSource;若EncodedResource带了显式编码(典型场景:FileSystemXmlApplicationContext或用户手工new EncodedResource(resource, "UTF-8")),就inputSource.setEncoding(...),SAX 解析器据此选择字符集。 - 异常包装 。
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;
}
逐段解读:
- 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。 - profile 过滤 。根元素若带
profile="dev,qa",则用环境acceptsProfiles判断当前激活 profile 是否命中;不命中直接 return,整个文件跳过 。注意注释里的限定:XML 的 profile 只支持逗号分隔列表,不支持Profiles.of()那种表达式(SPR-12458)。 - 预处理/后处理是空钩子 。
preProcessXml(root)/postProcessXml(root)(第 330 行附近)默认空实现,留给子类扩展------这是少数几个"如果你想改写整个 XML 预处理流程"的公开扩展点。 - 真正遍历在
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;
}
命名规则逐段拆解:
id优先,name其次,自动生成兜底 。<bean id="a" name="b,c"/>中,id="a"是正式 bean 名,name属性按分隔符(,;与空白,MULTI_VALUE_ATTRIBUTE_DELIMITERS)切成别名列表。没有id时,别名列表第一个被"转正"为 bean 名,剩下的仍是别名------这就是name属性历来承担"多别名"语义的实现。- 同一
<beans>层级内查重 。checkNameUniqueness(第 475-490 行)维护一个usedNames集合,若 beanName 或任一别名在本层已出现,直接error------注意它只检查当前 delegate 层级 ,这就是嵌套<beans>可以复用名字的原因。 - 匿名 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;
}
几个关键设计点:
- 旧版
singleton属性直接报错 。Spring 2.0 起改用scope,这里碰到 1.x 时代的<bean singleton="true">不是兼容而是error提示升级------历史包袱处理得很干脆。 - 内部 bean 继承外部 scope 。匿名内部
<bean>(出现在<property>里)没写 scope 时,bd.setScope(containingBean.getScope())------内部 bean 跟着宿主走。 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 默认值逐层继承"的完整机制。- 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;
}
逐段解读:
- 包名转资源模式 。
resolveBasePackage(第 523-525 行)先解析占位符,再把com.example转成com/example,拼成classpath*:com/example/**/*.class(resourcePattern默认**/*.class)。注意前缀是classpath*:而不是classpath:------要扫描 jar 包内和多模块下所有同名包路径。 - 资源解析 。
PathMatchingResourcePatternResolver.getResources把这个模式展开成一堆Resource(每个 .class 文件一个)。 - 跳过 CGLIB 类 。文件名含
$$(CGLIB_CLASS_SEPARATOR)的是运行时代理类,直接跳过------避免把AppConfig$$EnhancerByCGLIB当成候选组件再扫一遍。 - ASM 读取,不加载类 。关键在
getMetadataReaderFactory().getMetadataReader(resource):返回一个MetadataReader,它内部用 ASM 读 class 字节码,只解析类结构与注解,不触发类加载 。默认工厂是CachingMetadataReaderFactory(在setResourceLoader时创建,第 267 行),对同一个 Resource 的 MetadataReader 做缓存------扫描时可能反复读取一个类的注解(多个过滤器都会读),缓存避免重复解析字节码。 - 两层候选判定 。第一层
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;
}
对每个候选类依次做五件事:
- 解析作用域 。
scopeMetadataResolver(默认AnnotationScopeMetadataResolver)读类上的@Scope,得到 scope 名与 proxyMode(@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)之类),candidate.setScope(...)落到 BeanDefinition。 - 生成 bean 名 。
beanNameGenerator默认AnnotationBeanNameGenerator:取类名首字母小写(com.example.UserService→userService);若类名前两个字母都大写(如URLService),按 Java Bean 规范保留原名。FullyQualifiedAnnotationBeanNameGenerator则用全限定类名作为 bean 名,用于避免重名。 - 后处理默认值 。
postProcessBeanDefinition(第 308-313 行)把扫描器上配置的BeanDefinitionDefaults(来自<beans default-lazy-init>或@ComponentScan的lazyInit=true)applyDefaults到 BeanDefinition;再按autowireCandidatePatterns决定autowireCandidate。 - 提取通用注解 。
AnnotationConfigUtils.processCommonDefinitionAnnotations把类上的@Lazy/@Primary/@DependsOn/@Role/@Description/@Scope逐一落到 BeanDefinition 对应字段------这和AnnotatedBeanDefinitionReader用的是同一个工具方法,XML 与注解两条路在这里汇合。 - 查重后注册 。
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)时扫描器不注册默认的@Componentinclude 过滤器------此时必须自己配 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 的四种身份:
- 普通类 (else 分支):被导入的类本身当作一个配置类来解析(
processConfigurationClass),并记录 importedBy 关系。最常见的@Import(SomeConfig.class)走这里。 - ImportSelector (非 Deferred):实例化选择器,调
selectImports(元数据)拿到一组类名,递归processImports处理这些类名------选择器只是"决定导入谁"的工厂,实际导入动作还是这套递归。 - DeferredImportSelector :不立即处理,扔进
deferredImportSelectorHandler,等所有配置类 parse 完后统一执行(3.16 详述)。 - ImportBeanDefinitionRegistrar :不解析成配置类,而是登记进
configClass.addImportBeanDefinitionRegistrar(...),等 reader 阶段回调它的registerBeanDefinitions方法,由它直接操作 registry注册任意 bean 定义。 - 另外 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、注册
}
关键点:
- REGISTER_BEAN 阶段的
@Conditional:方法上的条件注解在这里第二次评估(parse 阶段评过 PARSE_CONFIGURATION 阶段),不满足就记入skippedBeanMethods并 return。 - 命名 :
@Bean(name = {"x","y","z"})第一个是 bean 名,其余注册成别名;没写 name 就用方法名(defaultBeanName,第 312-314 行)。 - static 与 instance 的区别 (第 234-248 行):static @Bean 方法
beanDef.setBeanClass(...)+setUniqueFactoryMethodName------bean 类直接是方法所在类,不依赖配置类实例;instance 方法beanDef.setFactoryBeanName(configClass.getBeanName())------bean 的创建要先实例化配置类、再调它的方法。这个区分是 CGLIB 增强能否生效的前提(static 方法不需要代理)。 - autowireMode 固定为
AUTOWIRE_CONSTRUCTOR(第 258 行)------@Bean 方法的参数自动按构造器方式解析。 - 覆盖检查 (第 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();
}
逐段解读:
- 先提前解析类 。配置类此时还只有类名(
!abd.hasBeanClass()),要 CGLIB 增强必须先拿到Class对象,所以这里resolveBeanClass显式触发类加载------注意豁免:lite 且没有 @Bean 方法的类不加载(反正不增强)。 - 只挑 full 类 。
CONFIGURATION_CLASS_FULL.equals(configClassAttr)才进待增强集合;lite 类直接跳过。 - 过早实例化告警 。
beanFactory.containsSingleton(beanName)为 true 说明这个配置类已经被实例化了(典型原因:某个非 static @Bean 方法返回了BeanDefinitionRegistryPostProcessor,导致容器不得不在增强之前先创建它)------此时无法替换 beanClass,只能打警告日志。这就是"@Bean 方法返回 BeanFactoryPostProcessor 必须声明 static"的源码依据。 - 替换 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:
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 循环直到没有新配置类。- 同一阶段,其他
BeanDefinitionRegistryPostProcessor(如用户自己注册的)也在这里执行。 - 随后
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.17isOverriddenByExistingDefinition第 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/>)被容器识别,做四件事:
- 写一个
.xsd文件 描述你的标签语法,放在 jar 包内(如META-INF/myns.xsd)。 - 实现 NamespaceHandler :继承
NamespaceHandlerSupport,在init()里registerBeanDefinitionParser("foo", new MyFooParser());Parser 实现BeanDefinitionParser.parse(Element, ParserContext),内部把元素翻译成BeanDefinitionHolder并parserContext.getRegistry().registerBeanDefinition(...)(可参考 spring-context 里ComponentScanBeanDefinitionParser的写法)。 - 写
META-INF/spring.handlers:一行http\://your-uri/myns=com.you.MyNamespaceHandler。DefaultNamespaceHandlerResolver启动时loadAllProperties扫描所有 jar 的该文件合并,你的 URI→类映射就此生效。 - 写
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 阶段完成。