FEATURED · 精选文章

Spring 组件扫描(Component Scan)源码深度解析:从 `@ComponentScan` 到 BeanDefinition 注册的全链路

发布时间 / 2026/9/13 11:58:04
来源 / 创域科博编辑部
栏目 / 资讯中心
Spring 组件扫描(Component Scan)源码深度解析:从 `@ComponentScan` 到 BeanDefinition 注册的全链路 Spring 组件扫描Component Scan源码深度解析从ComponentScan到 BeanDefinition 注册的全链路【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunterSpring 的注解驱动 IoC 容器能够自动发现被Component、Service、Repository、Controller等注解标注的类并将它们注册为 Bean这一能力的核心就是组件扫描Component Scan。本文基于本仓库对 Spring 框架源码的阅读笔记 Spring-scan.md从AnnotationConfigApplicationContext与 XML 的context:component-scan两种使用方式入手逐层拆解ComponentScanBeanDefinitionParser.parse→ClassPathBeanDefinitionScanner.scan/doScan→ClassPathScanningCandidateComponentProvider.findCandidateComponents→ 作用域解析 → Bean 命名 → 重复检查 → 代理处理 → 注册的完整调用链。读完本文你将能从源码层面回答Spring 是如何把注解类变成 BeanDefinition 的这一经典问题并掌握扫描过程中的每一个关键扩展点。一、两种注解扫描的入口方式在 Spring 中启用注解形式的组件扫描有且仅有两种主流方式纯注解方式通过AnnotationConfigApplicationContext构造参数传入扫描包路径。AnnotationConfigApplicationContext aac new AnnotationConfigApplicationContext(com.huifer.source.spring.ann);XML 方式在 Spring 配置文件中使用context:component-scan标签通过base-package属性指定扫描包。context:component-scan base-packagecom.huifer.source.spring.ann /context:component-scan两种方式的殊途同归之处在于它们最终都会走进同一个核心扫描器ClassPathBeanDefinitionScanner。下面分别追踪它们的入口。二、入口一AnnotationConfigApplicationContext 的 scan直接查看AnnotationConfigApplicationContext的构造方法目标入口一目了然public AnnotationConfigApplicationContext(String... basePackages) { this(); // 扫描包 scan(basePackages); refresh(); }其中scan方法内部持有ClassPathBeanDefinitionScanner类型的成员变量scannerprivate final ClassPathBeanDefinitionScanner scanner; Override public void scan(String... basePackages) { Assert.notEmpty(basePackages, At least one base package must be specified); this.scanner.scan(basePackages); }注意这里有一个断言basePackages不能为空否则直接抛出IllegalArgumentException。这提醒我们在实际使用时必须保证传入有效的包路径。三、入口二XML 标签 context:component-scan 的解析对于 XML 方式Spring 解析自定义命名空间标签的机制是命名空间冒号前的字符串 NamespaceHandler。全文搜索component-scan最终定位到org.springframework.context.config.ContextNamespaceHandlerpublic 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()); } }可以看到component-scan标签被注册到了ComponentScanBeanDefinitionParser。该类实现BeanDefinitionParser接口继承关系如图3.1 ComponentScanBeanDefinitionParser.parse 方法解析的核心在parse方法Override Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { // 获取 base-package 属性值 String basePackage element.getAttribute(BASE_PACKAGE_ATTRIBUTE); // 处理 ${} basePackage parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage); // 分隔符,;\t\n切分 String[] basePackages StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them. // 扫描对象创建 ClassPathBeanDefinitionScanner scanner configureScanner(parserContext, element); // 执行扫描方法 SetBeanDefinitionHolder beanDefinitions scanner.doScan(basePackages); // 注册组件,触发监听 registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; }这段代码揭示了parse的三个关键动作占位符解析base-package属性值支持${...}占位符由Environment.resolvePlaceholders处理因此可以在属性文件或系统属性中定义扫描包路径。多包切分StringUtils.tokenizeToStringArray使用ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS即逗号,、分号;、制表符\t、换行符\n作为分隔符意味着base-package可以一次配置多个包路径例如base-packagecom.foo,com.bar。委托扫描真正干活的是configureScanner创建出的ClassPathBeanDefinitionScanner实例其doScan(basePackages)返回一组BeanDefinitionHolder最后通过registerComponents注册组件并触发容器事件。四、核心扫描器ClassPathBeanDefinitionScannerAnnotationConfigApplicationContext的scan最终也委托给ClassPathBeanDefinitionScanner.scanpublic int scan(String... basePackages) { // 获取bean数量 int beanCountAtScanStart this.registry.getBeanDefinitionCount(); // 执行扫描 doScan(basePackages); // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }这里的doScan与 XML 解析路径中的doScan是同一个方法两条入口在此汇合。scan方法的返回值是本次扫描新增的 BeanDefinition 数量扫描后总数减去扫描前总数这也是为什么scan方法返回int而doScan返回SetBeanDefinitionHolder——一个面向调用方统计一个面向内部处理。4.1 doScan扫描的主循环doScan是整个扫描过程的编排核心protected SetBeanDefinitionHolder doScan(String... basePackages) { Assert.notEmpty(basePackages, At least one base package must be specified); SetBeanDefinitionHolder beanDefinitions new LinkedHashSet(); for (String basePackage : basePackages) { // 寻找组件 SetBeanDefinition candidates findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { // bean 作用域设置 ScopeMetadata scopeMetadata this.scopeMetadataResolver.resolveScopeMetadata(candidate); // 设置生命周期 candidate.setScope(scopeMetadata.getScopeName()); // 创建beanName String beanName this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { // 设置默认属性 具体方法:org.springframework.beans.factory.support.AbstractBeanDefinition.applyDefaults postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { // 读取LazyPrimary 等注解 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // bean的重复检查 if (checkCandidate(beanName, candidate)) { // 创建 BeanDefinitionHolder BeanDefinitionHolder definitionHolder new BeanDefinitionHolder(candidate, beanName); // 代理对象的处理 definitionHolder AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // 放入list中,最后返回用 beanDefinitions.add(definitionHolder); // 注册bean registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }doScan对每个候选类依次完成六步处理接下来逐个剖析findCandidateComponents在包路径下找出候选组件核心扫描逻辑resolveScopeMetadata解析Scope作用域并回填generateBeanName生成 Bean 名称postProcessBeanDefinition应用默认属性processCommonDefinitionAnnotations读取Lazy、Primary、DependsOn等通用注解checkCandidateapplyScopedProxyModeregisterBeanDefinition重复校验、代理处理与注册。4.2 findCandidateComponents候选组件的定位public SetBeanDefinition findCandidateComponents(String basePackage) { // 扫描 if (this.componentsIndex ! null indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } }这里存在一条分支优化如果 classpath 上存在 Spring 5 引入的候选组件索引CandidateComponentsIndex即META-INF/spring.components文件相关实现见 Spring-spring-components.md且当前 include 过滤器支持索引则直接从索引中读取候选类跳过类路径遍历显著提升大型项目的启动速度否则退化为传统的scanCandidateComponents全量扫描。scanCandidateComponents是传统扫描的实现本质是一次 classpath 资源加载 元数据解析 过滤器判定的过程private SetBeanDefinition scanCandidateComponents(String basePackage) { SetBeanDefinition candidates new LinkedHashSet(); try { // 字符串拼接出一个编译后的路径 classpath:// // 这里替换了通配符 String packageSearchPath ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX resolveBasePackage(basePackage) / this.resourcePattern; // 获取资源 Resource[] resources getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled logger.isTraceEnabled(); boolean debugEnabled logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace(Scanning resource); } if (resource.isReadable()) { try { // 获取 MetadataReader MetadataReader metadataReader getMetadataReaderFactory().getMetadataReader(resource); // 判断是否是 Component if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug(Identified candidate component class: resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug(Ignored because not a concrete top-level class: resource); } } } else { if (traceEnabled) { logger.trace(Ignored because not matching any filter: resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( Failed to read candidate component class: resource, ex); } } else { if (traceEnabled) { logger.trace(Ignored because not readable: resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException(I/O failure during classpath scanning, ex); } return candidates; }几个关键细节值得展开搜索路径的构造CLASSPATH_ALL_URL_PREFIX即classpath*:resolveBasePackage(basePackage)会把包名中的.转换为/并替换classpath*:中的通配符最终拼接出类似classpath*:com/huifer/source/spring/ann/**/*.class的路径模式交给PathMatchingResourcePatternResolver加载资源。元数据读取每个.class资源通过MetadataReaderFactory.getMetadataReader(resource)读取为MetadataReader借助 ASM 解析字节码中的注解元数据无需加载类到 JVM因此不会触发类的初始化。两级候选判定先通过isCandidateComponent(metadataReader)依据 include/exclude 过滤器如Component及其派生注解判断元数据是否匹配再通过isCandidateComponent(sbd)判断该类是否为具体的顶层类排除接口、抽象类、内部类等。产出物匹配的类被包装为ScannedGenericBeanDefinition并设置resource与source信息加入候选集合返回。五、作用域解析ScopeMetadataResolverdoScan中对每个候选调用scopeMetadataResolver.resolveScopeMetadata(candidate)默认实现AnnotationScopeMetadataResolver的核心逻辑如下Override public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata new ScopeMetadata(); // 判断是否属于 AnnotatedBeanDefinition if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef (AnnotatedBeanDefinition) definition; AnnotationAttributes attributes AnnotationConfigUtils.attributesFor( annDef.getMetadata(), this.scopeAnnotationType); if (attributes ! null) { // 获取 value 属性值并且设置 metadata.setScopeName(attributes.getString(value)); // 获取 proxyMode 属性值并且设置 ScopedProxyMode proxyMode attributes.getEnum(proxyMode); if (proxyMode ScopedProxyMode.DEFAULT) { proxyMode this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } } return metadata; }它做的事很直接读取类上Scope注解的value作用域名如singleton、prototype、request、session和proxyMode代理模式若proxyMode为DEFAULT则回退到解析器配置的defaultProxyMode。随后doScan中candidate.setScope(scopeMetadata.getScopeName())将该作用域写回 BeanDefinition。Spring 框架自带的测试用例验证了这一行为AnnotationScopeMetadataResolverTests#resolveScopeMetadataShouldReadScopedProxyModeFromAnnotationTest public void resolveScopeMetadataShouldReadScopedProxyModeFromAnnotation() { AnnotatedBeanDefinition bd new AnnotatedGenericBeanDefinition(AnnotatedWithScopedProxy.class); ScopeMetadata scopeMetadata this.scopeMetadataResolver.resolveScopeMetadata(bd); assertNotNull(resolveScopeMetadata(..) must *never* return null., scopeMetadata); assertEquals(request, scopeMetadata.getScopeName()); assertEquals(TARGET_CLASS, scopeMetadata.getScopedProxyMode()); }该测试证明Scope标注的 Bean 能正确解析出request作用域与TARGET_CLASS代理模式。下图是resolveScopeMetadata执行后ScopeMetadata与AnnotationAttributes中属性值的真实状态截图包含value request、proxyMode ScopedProxyMode.TARGET_CLASS、scopeName request六、Bean 名称生成BeanNameGeneratorBean 名称的生成由BeanNameGenerator完成扫描场景下默认使用AnnotationBeanNameGenerator其generateBeanName逻辑为更完整的命名器族谱参见 Spring-BeanNameGenerator.mdOverride public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instanceof AnnotatedBeanDefinition) { // 如果存在bean(value) value存在 String beanName determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { // Explicit bean name found. return beanName; } } // Fallback: generate a unique default bean name. // 创建beanName return buildDefaultBeanName(definition, registry); }6.1 优先使用注解的 valuedetermineBeanNameFromAnnotation遍历类上的所有注解寻找带有value属性的构造型注解Stereotype即Component、Service、Controller、Repository等Nullable protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) { AnnotationMetadata amd annotatedDef.getMetadata(); SetString types amd.getAnnotationTypes(); String beanName null; for (String type : types) { AnnotationAttributes attributes AnnotationConfigUtils.attributesFor(amd, type); if (attributes ! null isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) { // 获取注解的value 属性值 Object value attributes.get(value); if (value instanceof String) { String strVal (String) value; // 判断是否存在值 if (StringUtils.hasLength(strVal)) { if (beanName ! null !strVal.equals(beanName)) { throw new IllegalStateException(Stereotype annotations suggest inconsistent component names: beanName versus strVal ); } // beanName value属性值 beanName strVal; } } } } return beanName; }注意其中的一致性校验如果多个构造型注解声明了不同的value会抛出IllegalStateExceptionStereotype annotations suggest inconsistent component names。例如下面的类指定了显式名称dhcService(value dhc) public class DemoService { }下图展示了determineBeanNameFromAnnotation在调试状态下从AnnotationMetadata中读取注解类型、并通过AnnotationConfigUtils解析value属性的过程截图6.2 兜底策略类名首字母小写当注解没有显式指定value时进入buildDefaultBeanNameprotected String buildDefaultBeanName(BeanDefinition definition) { // 获取bean class name String beanClassName definition.getBeanClassName(); Assert.state(beanClassName ! null, No bean class name set); // 获取短类名, String shortClassName ClassUtils.getShortName(beanClassName); // 第一个字母小写 return Introspector.decapitalize(shortClassName); }即取类的短名称不带包名再通过Introspector.decapitalize将首字母小写BeanConfig→beanConfig。这也正是Spring 默认 Bean 名 类名首字母小写这一常见认知的源码出处。下图是buildDefaultBeanName方法执行时的调试截图可见BeanConfig被转换为beanConfig作为对比XML 场景下的DefaultBeanNameGenerator则使用BeanDefinitionReaderUtils.generateBeanName生成类名 # 十六进制唯一后缀的名称两种命名策略的差异点在于是否面向注解元数据参见 Spring-BeanNameGenerator.md。七、默认属性应用postProcessBeanDefinitionpostProcessBeanDefinition方法本身没有复杂的逻辑直接是各种 set 方法protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) { beanDefinition.applyDefaults(this.beanDefinitionDefaults); if (this.autowireCandidatePatterns ! null) { beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName)); } }其底层调用AbstractBeanDefinition.applyDefaults把扫描器上配置的默认值如是否懒加载、自动装配模式、依赖检查方式、初始化/销毁方法名等批量灌入 BeanDefinitionpublic void applyDefaults(BeanDefinitionDefaults defaults) { setLazyInit(defaults.isLazyInit()); setAutowireMode(defaults.getAutowireMode()); setDependencyCheck(defaults.getDependencyCheck()); setInitMethodName(defaults.getInitMethodName()); setEnforceInitMethod(false); setDestroyMethodName(defaults.getDestroyMethodName()); setEnforceDestroyMethod(false); }八、通用注解处理processCommonDefinitionAnnotations若候选是AnnotatedBeanDefinition还会调用AnnotationConfigUtils.processCommonDefinitionAnnotations读取Lazy、Primary、DependsOn、Role、Description等通用注解public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { processCommonDefinitionAnnotations(abd, abd.getMetadata()); }static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) { // 获取 lazy 注解 AnnotationAttributes lazy attributesFor(metadata, Lazy.class); if (lazy ! null) { abd.setLazyInit(lazy.getBoolean(value)); } else if (abd.getMetadata() ! metadata) { lazy attributesFor(abd.getMetadata(), Lazy.class); if (lazy ! null) { abd.setLazyInit(lazy.getBoolean(value)); } } if (metadata.isAnnotated(Primary.class.getName())) { abd.setPrimary(true); } AnnotationAttributes dependsOn attributesFor(metadata, DependsOn.class); if (dependsOn ! null) { abd.setDependsOn(dependsOn.getStringArray(value)); } AnnotationAttributes role attributesFor(metadata, Role.class); if (role ! null) { abd.setRole(role.getNumber(value).intValue()); } AnnotationAttributes description attributesFor(metadata, Description.class); if (description ! null) { abd.setDescription(description.getString(value)); } }方法思路非常清晰可归纳为两步通过attributesFor获取注解的属性值通过 setter 将属性值写入 BeanDefinition。例如Lazy(value true)会被解析为abd.setLazyInit(true)Primary会被解析为abd.setPrimary(true)DependsOn({a, b})会被解析为abd.setDependsOn(new String[]{a, b})。这些属性在后续依赖注入、初始化排序阶段都会发挥作用。九、重复校验checkCandidate在注册之前Spring 会检查同名的 BeanDefinition 是否已存在防止冲突protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { // 判断当前 beanName 是否在注册表中 if (!this.registry.containsBeanDefinition(beanName)) { return true; } // 从注册表中获取 BeanDefinition existingDef this.registry.getBeanDefinition(beanName); // 当前的bean BeanDefinition originatingDef existingDef.getOriginatingBeanDefinition(); if (originatingDef ! null) { existingDef originatingDef; } 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() ]); }其判定规则注册表中没有该名称 → 直接返回true允许注册注册表中已有该名称 → 获取已存在的定义若存在OriginatingBeanDefinition则取其本源定义通过isCompatible判断是否兼容例如同一个类被扫描器多次扫描到、且是同一来源的定义视为兼容兼容 → 返回false跳过注册不重复注册不兼容 → 抛出ConflictingBeanDefinitionException这就是我们常见的Annotation-specified bean name xxx conflicts with existing, non-compatible bean definition异常的来源。十、作用域代理applyScopedProxyMode扫描到的 Bean 若声明了非默认代理模式还需要为其生成作用域代理static BeanDefinitionHolder applyScopedProxyMode( ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { ScopedProxyMode scopedProxyMode metadata.getScopedProxyMode(); if (scopedProxyMode.equals(ScopedProxyMode.NO)) { return definition; } boolean proxyTargetClass scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); // 创建代理对象 return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); }当Scope(proxyMode ScopedProxyMode.NO)默认值时原样返回不创建代理当proxyMode ScopedProxyMode.INTERFACES时创建基于 JDK 动态代理的接口代理当proxyMode ScopedProxyMode.TARGET_CLASS时创建基于 CGLIB 的目标类代理。这一步的意义在于对于request、session等非单例作用域注入方持有的往往是一个固定 Bean代理可以在每次访问时转发到当前请求/会话对应的真实实例。代理处理完成后BeanDefinitionHolder连同代理信息被加入返回集合并由registerBeanDefinition正式注册进容器至此一次完整的组件扫描结束。十一、小结一条完整的扫描调用链综合全文Spring 注解组件扫描的完整调用链可归纳为AnnotationConfigApplicationContext(basePackages) / context:component-scan │ ▼ ComponentScanBeanDefinitionParser.parseXML 路径 │ 或 AnnotationConfigApplicationContext.scan注解路径 ▼ ClassPathBeanDefinitionScanner.scan / doScan │ ├── findCandidateComponents ──► scanCandidateComponents │ └─ classpath*:包路径/**/*.class 资源加载 │ └─ MetadataReader 元数据解析ASM不加载类 │ └─ isCandidateComponent 过滤器判定Component 及其派生 │ └─ 包装为 ScannedGenericBeanDefinition ├── scopeMetadataResolver.resolveScopeMetadataScope ├── beanNameGenerator.generateBeanName注解 value 优先否则类名首字母小写 ├── postProcessBeanDefinitionapplyDefaults 默认属性 ├── AnnotationConfigUtils.processCommonDefinitionAnnotationsLazy/Primary/DependsOn 等 ├── checkCandidate重复校验冲突抛 ConflictingBeanDefinitionException ├── AnnotationConfigUtils.applyScopedProxyMode作用域代理 └── registerBeanDefinition注册进 BeanDefinitionRegistry关键收获总结如下两条入口、一个核心注解与 XML 两种扫描方式最终都汇聚到ClassPathBeanDefinitionScanner.doScan扫描的本质是资源定位scanCandidateComponents通过classpath*:模式加载.class资源再用 ASM 读取注解元数据全程不触发类加载Spring 5 的索引优化存在META-INF/spring.components候选组件索引时走addCandidateComponentsFromIndex捷径参见 Spring-spring-components.md跳过全量类路径扫描命名规则可定制默认AnnotationBeanNameGenerator遵循注解 value 优先、类名首字母小写兜底也可以替换为FullyQualifiedAnnotationBeanNameGenerator使用全限定类名扫描只是第一步doScan产出的BeanDefinitionHolder最终交由registerBeanDefinition注册再经refresh()进入 IoC 容器的后续实例化流程资源定位与载入的整体脉络可参考 BeanDefinition 的资源定位过程。掌握了这条链路无论是排查Bean 未被扫描到、理解同名 Bean 冲突异常还是自定义BeanNameGenerator、ScopeMetadataResolver等扩展点你都能直接从源码层面定位到对应的方法与职责。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻