免费网站模板带后台,wordpress 修改链接,页面设计怎么弄,微信公众号创建流程ComponentScan
ComponentScan 一、基本信息二、注解描述三、注解源码四、主要功能五、最佳实践六、时序图七、源码分析八、注意事项九、总结 最佳实践总结源码分析总结
一、基本信息
转载自github#xff0c;在此作为个人备份
二、注解描述
ComponentScan 注解#xf…ComponentScan
ComponentScan 一、基本信息二、注解描述三、注解源码四、主要功能五、最佳实践六、时序图七、源码分析八、注意事项九、总结 最佳实践总结源码分析总结
一、基本信息
转载自github在此作为个人备份
二、注解描述
ComponentScan 注解用于自动扫描特定包和其子包中的组件并自动注册为 Spring 容器中的 bean。当我们使用 Spring Boot它默认会扫描主应用程序所在的包以及子包。但是如果我们需要更细粒度的控制或者我们在使用传统的 Spring 而非 Spring Boot那么我们可能会明确地使用 ComponentScan。
三、注解源码
ComponentScan注解是 Spring 框架自 3.1 版本开始引入的一个核心注解用于指导如何扫描组件。与 Configuration 配合使用其功能与 Spring XML 的 context:component-scan 类似。除了允许指定要扫描的包它还提供了多种属性如命名生成器、范围解析器、代理设置等以精细地控制组件的扫描和注册过程。若不指定扫描包它默认从注解声明的位置开始。与此同时Filter 注解定义了类型过滤器特别用于 ComponentScan 中的组件包含和排除设置。它允许基于特定类型、类或模式来筛选组件。
/*** 配置 Configuration 类使用的组件扫描指令。* 提供与 Spring XML 的 context:component-scan 元素相似的支持。** 可以指定 #basePackageClasses 或 #basePackages (或其别名* #value }) 来定义要扫描的特定包。如果没有定义特定的包* 则从声明此注解的类的包开始扫描。** 注意context:component-scan 元素有一个* annotation-config 属性; 但是此注解没有。这是因为* 在几乎所有使用 ComponentScan 的情况下默认的注解配置* 处理例如处理 Autowired 及其朋友们都是预期的。此外* 使用 AnnotationConfigApplicationContext 时总是会注册注解配置处理器* 这意味着在 ComponentScan 级别尝试禁用它们都会被忽略。** 有关使用示例请参见 Configuration Configuration 的 Javadoc。** author Chris Beams* author Juergen Hoeller* author Sam Brannen* since 3.1* see Configuration*/
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.TYPE)
Documented
Repeatable(ComponentScans.class)
public interface ComponentScan {/*** #basePackages 的别名。* 如果不需要其他属性则允许更简洁的注解声明例如ComponentScan(org.my.pkg)* 而不是 ComponentScan(basePackages org.my.pkg)。*/AliasFor(basePackages)String[] value() default {};/*** 扫描带注解的组件的基础包。* #value 是此属性的别名且与此属性互斥。* 使用 #basePackageClasses 作为基于类型安全的替代方法* 来指定要扫描注解的组件的包。将扫描每个指定类的包。*/AliasFor(value)String[] basePackages() default {};/*** 指定要扫描的包的类型安全替代方法。每个指定类的包都会被扫描。* 考虑在每个包中创建一个特殊的无操作标记类或接口* 除了被此属性引用之外没有其他用途。*/Class?[] basePackageClasses() default {};/*** 在Spring容器内为检测到的组件命名的 BeanNameGenerator 类。* BeanNameGenerator 接口的默认值表明处理此 ComponentScan 注解的扫描器* 应使用它的继承的bean命名生成器例如默认的* AnnotationBeanNameGenerator 或在启动时提供给应用上下文的任何自定义实例。*/Class? extends BeanNameGenerator nameGenerator() default BeanNameGenerator.class;/*** 用于解析检测到的组件范围的 ScopeMetadataResolver。*/Class? extends ScopeMetadataResolver scopeResolver() default AnnotationScopeMetadataResolver.class;/*** 指示是否应为检测到的组件生成代理这在以代理风格使用范围时可能是必要的。* 默认值是延迟到执行实际扫描的组件扫描器的默认行为。* 注意设置此属性会覆盖为 #scopeResolver 设置的任何值。*/ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;/*** 控制适用于组件检测的类文件。* 考虑使用 #includeFilters 和 #excludeFilters* 来采用更灵活的方法。*/String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;/*** 指示是否应启用使用 Component Repository, Service, 或 Controller 注解的类的自动检测。*/boolean useDefaultFilters() default true;/*** 指定哪些类型有资格进行组件扫描。* 进一步从 #basePackages 中的所有内容缩小到匹配给定过滤器或过滤器的基包中的所有内容。* 注意这些过滤器将附加到默认过滤器如果指定。即使它与默认过滤器不匹配例如没有使用 Component 注解* 任何匹配给定过滤器的基包下的类型都将被包括。*/Filter[] includeFilters() default {};/*** 指定哪些类型不适合进行组件扫描。*/Filter[] excludeFilters() default {};/*** 指定是否应注册扫描的beans以进行延迟初始化。* 默认值是 false如果需要切换为 true。*/boolean lazyInit() default false;/*** 声明用作 ComponentScan#includeFilters include filter 或 * ComponentScan#excludeFilters exclude filter 的类型过滤器。*/Retention(RetentionPolicy.RUNTIME)Target({})interface Filter {/*** 要使用的过滤器类型。* 默认为 FilterType#ANNOTATION。* see #classes* see #pattern*/FilterType type() default FilterType.ANNOTATION;/*** #classes 的别名。*/AliasFor(classes)Class?[] value() default {};/*** 用作过滤器的类或类。* 根据 #type 属性的配置值以下表格解释了如何解释这些类* ...* 这部分包含了一个表格和其它详细说明由于格式限制需要额外的处理来适应中文文档* ...*/AliasFor(value)Class?[] classes() default {};/*** 用作过滤器的模式或模式作为指定类 #value 的替代。* 如果 #type 设置为 FilterType#ASPECTJ ASPECTJ这是一个 AspectJ 类型模式表达式。* 如果 #type 设置为 FilterType#REGEX REGEX这是一个正则模式用于匹配完全限定的类名。*/String[] pattern() default {};}
}
ScopedProxyMode 是一个枚举定义了不同的作用域代理选项用于决定如何为特定的作用域 bean 创建代理。作用域代理是 Spring 中一个高级特性允许在不同的上下文中共享 bean 实例如请求或会话。此枚举的主要用途是为这些作用域 bean 提供不同的代理机制。
/*** 枚举各种作用域代理选项。** 为了更完整地讨论什么是作用域代理请查看 Spring 参考文档中标题为 作为依赖的作用域 beans 的部分。** author Mark Fisher* since 2.5* see ScopeMetadata*/
public enum ScopedProxyMode {/*** 默认通常等于 #NO除非在组件扫描指令级别配置了不同的默认值。*/DEFAULT,/*** 不创建一个作用域代理。* p当与非单例作用域实例一起使用时这种代理模式通常不太有用如果要作为依赖项使用* 它应该优先使用 #INTERFACES 或 #TARGET_CLASS 代理模式。*/NO,/*** 创建一个JDK动态代理实现目标对象的类所暴露的所有接口。*/INTERFACES,/*** 创建一个基于类的代理使用CGLIB。*/TARGET_CLASS}FilterType 是一个枚举定义了与 ComponentScan 注解结合使用时的不同类型过滤器选项。这些过滤器用于决定在组件扫描过程中哪些组件应被包括或排除。
/*** 与 ComponentScan ComponentScan 结合使用的类型过滤器的枚举。* 该枚举定义了在组件扫描过程中可以用于过滤组件的不同类型。** author Mark Fisher* author Juergen Hoeller* author Chris Beams* since 2.5* see ComponentScan* see ComponentScan#includeFilters()* see ComponentScan#excludeFilters()* see org.springframework.core.type.filter.TypeFilter*/
public enum FilterType {/*** 过滤带有指定注解的候选项。* see org.springframework.core.type.filter.AnnotationTypeFilter*/ANNOTATION,/*** 过滤可以赋值给指定类型的候选项。* see org.springframework.core.type.filter.AssignableTypeFilter*/ASSIGNABLE_TYPE,/*** 过滤与指定的AspectJ类型模式表达式匹配的候选项。* see org.springframework.core.type.filter.AspectJTypeFilter*/ASPECTJ,/*** 过滤与指定的正则表达式模式匹配的候选项。* see org.springframework.core.type.filter.RegexPatternTypeFilter*/REGEX,/*** 使用给定的自定义 org.springframework.core.type.filter.TypeFilter 实现来过滤候选项。*/CUSTOM}四、主要功能 指定扫描的包 通过 basePackages 和 basePackageClasses 属性用户可以明确告诉 Spring 在哪些包中查找带有 Component、Service、Repository 和 Controller 等注解的类。 自动扫描 如果用户没有明确指定要扫描的包则默认从声明 ComponentScan 的类所在的包开始进行扫描。 过滤扫描的组件 通过 includeFilters 和 excludeFilters 属性用户可以更精细地控制哪些组件应被扫描或排除。 其他配置 此注解还提供了其他属性如 nameGenerator为检测到的组件命名、scopeResolver解析组件的范围、scopedProxy是否为组件生成代理等以提供更高级的配置。
五、最佳实践
首先来看看启动类入口上下文环境使用AnnotationConfigApplicationContext此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个MyConfiguration组件类。在初始化上下文后该程序会遍历并打印所有在 Spring 容器中定义的 beans 的名字。
public class ComponentScanApplication {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MyConfiguration.class);for (String beanDefinitionName : context.getBeanDefinitionNames()) {System.out.println(beanName beanDefinitionName);}}
}在MyConfiguration类中Spring 扫描 com.xcs.spring 包及其子包包括所有 SpecialComponent 类型的组件但排除所有 AdminService 类型的组件。
Configuration
ComponentScan(basePackages com.xcs.spring,includeFilters ComponentScan.Filter(type FilterType.ASSIGNABLE_TYPE, classes SpecialComponent.class),excludeFilters ComponentScan.Filter(type FilterType.ASSIGNABLE_TYPE, classes AdminService.class)
)
public class MyConfiguration {}UserRepository 的类位于 com.xcs.spring.repository 包中并用 Repository 注解标记。
package com.xcs.spring.repository;Repository
public class UserRepository {}AdminService 和 UserService它们都位于 com.xcs.spring.service 包中并分别用 Service 注解标记。
package com.xcs.spring.service;Service
public class AdminService {}Service
public class UserService {}SpecialComponent 的类它位于 com.xcs.spring.special 包中没有使用spring中的任何注解标记。
package com.xcs.spring.special;public class SpecialComponent {}运行结果发现UserRepository 将被自动检测并注册为一个 Spring bean因为它位于我们指定的 com.xcs.spring 包路径下。UserService 将被自动检测并注册为一个 Spring bean因为它位于我们指定的 com.xcs.spring 包路径下。但是由于 ComponentScan 配置中使用了 excludeFilters 明确排除了 AdminService所以即使 AdminService 位于 com.xcs.spring 包路径下它也不会被注册为一个 Spring bean。虽然SpecialComponent 类是一个没有任何 Spring 注解的普通 Java 类。但通过使用 ComponentScan 的 includeFilters 和 FilterType.ASSIGNABLE_TYPE我们可以强制 Spring 上下文扫描并注册它为一个 bean即使它没有标记为 Component 或其他 Spring 注解。
beanName org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanName org.springframework.context.annotation.internalAutowiredAnnotationProcessor
beanName org.springframework.context.event.internalEventListenerProcessor
beanName org.springframework.context.event.internalEventListenerFactory
beanName myConfiguration
beanName userRepository
beanName userService
beanName specialComponent六、时序图 #mermaid-svg-XcraVV1qKmIqYCke {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-XcraVV1qKmIqYCke .error-icon{fill:#552222;}#mermaid-svg-XcraVV1qKmIqYCke .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XcraVV1qKmIqYCke .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-XcraVV1qKmIqYCke .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XcraVV1qKmIqYCke .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XcraVV1qKmIqYCke .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XcraVV1qKmIqYCke .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XcraVV1qKmIqYCke .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XcraVV1qKmIqYCke .marker.cross{stroke:#333333;}#mermaid-svg-XcraVV1qKmIqYCke svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XcraVV1qKmIqYCke .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XcraVV1qKmIqYCke text.actortspan{fill:black;stroke:none;}#mermaid-svg-XcraVV1qKmIqYCke .actor-line{stroke:grey;}#mermaid-svg-XcraVV1qKmIqYCke .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XcraVV1qKmIqYCke .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XcraVV1qKmIqYCke #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XcraVV1qKmIqYCke .sequenceNumber{fill:white;}#mermaid-svg-XcraVV1qKmIqYCke #sequencenumber{fill:#333;}#mermaid-svg-XcraVV1qKmIqYCke #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XcraVV1qKmIqYCke .messageText{fill:#333;stroke:#333;}#mermaid-svg-XcraVV1qKmIqYCke .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XcraVV1qKmIqYCke .labelText,#mermaid-svg-XcraVV1qKmIqYCke .labelTexttspan{fill:black;stroke:none;}#mermaid-svg-XcraVV1qKmIqYCke .loopText,#mermaid-svg-XcraVV1qKmIqYCke .loopTexttspan{fill:black;stroke:none;}#mermaid-svg-XcraVV1qKmIqYCke .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XcraVV1qKmIqYCke .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XcraVV1qKmIqYCke .noteText,#mermaid-svg-XcraVV1qKmIqYCke .noteTexttspan{fill:black;stroke:none;}#mermaid-svg-XcraVV1qKmIqYCke .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XcraVV1qKmIqYCke .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XcraVV1qKmIqYCke .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XcraVV1qKmIqYCke .actorPopupMenu{position:absolute;}#mermaid-svg-XcraVV1qKmIqYCke .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XcraVV1qKmIqYCke .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XcraVV1qKmIqYCke .actor-man circle,#mermaid-svg-XcraVV1qKmIqYCke line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XcraVV1qKmIqYCke :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ComponentScanApplication AnnotationConfigApplicationContext AbstractApplicationContext PostProcessorRegistrationDelegate ConfigurationClassPostProcessor ConfigurationClassParser ComponentScanAnnotationParser ClassPathBeanDefinitionScanner ClassPathScanningCandidateComponentProvider BeanDefinitionReaderUtils DefaultListableBeanFactory AnnotationConfigApplicationContext(componentClasses) refresh() invokeBeanFactoryPostProcessors(beanFactory) invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors) invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup) postProcessBeanDefinitionRegistry(registry) processConfigBeanDefinitions(registry) ConfigurationClassParser(...) 返回解析解析器 parser.parse(candidates) parse(metadata, String beanName) processConfigurationClass(configClass,filter) doProcessConfigurationClass(configClass,sourceClass,filter) parse(componentScan,declaringClass) ClassPathBeanDefinitionScanner(registry,useDefaultFilters,environment,resourceLoader) registerDefaultFilters() 返回扫描器 doScan(basePackages) findCandidateComponents(basePackage) scanCandidateComponents(basePackage) 返回BeanDefinition registerBeanDefinition(definitionHolder,registry) registerBeanDefinition(definitionHolder, registry) registerBeanDefinition(beanName,beanDefinition) 返回BeanDefinition ComponentScanApplication AnnotationConfigApplicationContext AbstractApplicationContext PostProcessorRegistrationDelegate ConfigurationClassPostProcessor ConfigurationClassParser ComponentScanAnnotationParser ClassPathBeanDefinitionScanner ClassPathScanningCandidateComponentProvider BeanDefinitionReaderUtils DefaultListableBeanFactory ComponentScan注解时序图 七、源码分析
首先来看看启动类入口上下文环境使用AnnotationConfigApplicationContext此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个MyConfiguration组件类。在初始化上下文后该程序会遍历并打印所有在 Spring 容器中定义的 beans 的名字。
public class ComponentScanApplication {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MyConfiguration.class);for (String beanDefinitionName : context.getBeanDefinitionNames()) {System.out.println(beanName beanDefinitionName);}}
}在org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext构造函数中执行了三个步骤我们重点关注refresh()方法。
public AnnotationConfigApplicationContext(Class?... componentClasses) {this();register(componentClasses);refresh();
}在org.springframework.context.support.AbstractApplicationContext#refresh方法中我们重点关注一下finishBeanFactoryInitialization(beanFactory)这方法会对实例化所有剩余非懒加载的单列Bean对象其他方法不是本次源码阅读的重点暂时忽略。
Override
public void refresh() throws BeansException, IllegalStateException {// ... [代码部分省略以简化]// 调用在上下文中注册为bean的工厂处理器invokeBeanFactoryPostProcessors(beanFactory);// ... [代码部分省略以简化]
}在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中又委托了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()进行调用。
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// ... [代码部分省略以简化]
}在org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中首先调用了 BeanDefinitionRegistryPostProcessor这是 BeanFactoryPostProcessor 的子接口。它专门用来在所有其他 bean 定义加载之前修改默认的 bean 定义。
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, ListBeanFactoryPostProcessor beanFactoryPostProcessors) {// ... [代码部分省略以简化]invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());// ... [代码部分省略以简化]
}在org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors方法中循环调用了实现BeanDefinitionRegistryPostProcessor接口中的postProcessBeanDefinitionRegistry(registry)方法
private static void invokeBeanDefinitionRegistryPostProcessors(Collection? extends BeanDefinitionRegistryPostProcessor postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {StartupStep postProcessBeanDefRegistry applicationStartup.start(spring.context.beandef-registry.post-process).tag(postProcessor, postProcessor::toString);postProcessor.postProcessBeanDefinitionRegistry(registry);postProcessBeanDefRegistry.end();}
}在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中调用了processConfigBeanDefinitions方法该方法的主要目的是处理和注册配置类中定义的beans。
Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {// ... [代码部分省略以简化]processConfigBeanDefinitions(registry);
}在org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中这个方法主要处理了配置类的解析和验证并确保了所有在配置类中定义的beans都被正确地注册到Spring的bean定义注册表中。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// ... [代码部分省略以简化]// 步骤1创建一个用于解析配置类的解析器ConfigurationClassParser parser new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 步骤2初始化候选配置类集合以及已解析配置类集合SetBeanDefinitionHolder candidates new LinkedHashSet(configCandidates);SetConfigurationClass alreadyParsed new HashSet(configCandidates.size());// 步骤3循环处理所有候选配置类直至没有候选类为止do {// 步骤3.1 解析配置类parser.parse(candidates);// 步骤3.2 验证配置类parser.validate();// 获取解析后的配置类并从中移除已经处理过的SetConfigurationClass configClasses new LinkedHashSet(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// 步骤4如果reader为空则创建一个新的Bean定义读取器if (this.reader null) {this.reader new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 步骤5使用读取器为解析的配置类加载Bean定义this.reader.loadBeanDefinitions(configClasses);// ... [代码部分省略以简化]} while (!candidates.isEmpty());// ... [代码部分省略以简化]
}在org.springframework.context.annotation.ConfigurationClassParser#parse方法中主要是遍历所有的配置类候选者并对每一个带有注解的Bean定义进行解析。这通常涉及到查找该配置类中的Bean方法、组件扫描指令等并将这些信息注册到Spring容器中。
public void parse(SetBeanDefinitionHolder configCandidates) {// ... [代码部分省略以简化]parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());// ... [代码部分省略以简化]
}在org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)方法中将注解元数据和Bean名称转化为一个配置类然后对其进行处理。处理配置类是Spring配置驱动的核心它涉及到许多关键操作如处理ComponentScan注解等等。
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}在org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass方法中处理一个给定的配置类。它首先递归地处理配置类及其父类以确保所有相关的配置都被正确地读取并解析。在递归处理完所有相关配置后它将配置类添加到已解析的配置类的映射中。
protected void processConfigurationClass(ConfigurationClass configClass, PredicateString filter) throws IOException {// ... [代码部分省略以简化]// 步骤1递归地处理配置类及其超类层次结构SourceClass sourceClass asSourceClass(configClass, filter);do {sourceClass doProcessConfigurationClass(configClass, sourceClass, filter);} while (sourceClass ! null);// 步骤2将处理后的配置类放入映射中this.configurationClasses.put(configClass, configClass);
}在org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中这个方法的目标是处理和解析标有 Configuration 的类执行组件扫描并确保所有相关的配置类都被递归地解析。
Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, PredicateString filter)throws IOException {// ... [代码部分省略以简化]// 处理任何 ComponentScan 注解// 获取当前类sourceClass的所有 ComponentScan 和 ComponentScans 注解的属性SetAnnotationAttributes componentScans AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);// 如果存在 ComponentScan 或 ComponentScans 注解并且该类没有被条件评估排除if (!componentScans.isEmpty() !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {// 遍历每一个 ComponentScan 注解for (AnnotationAttributes componentScan : componentScans) {// 对标有 ComponentScan 的配置类进行立即扫描SetBeanDefinitionHolder scannedBeanDefinitions this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// 检查扫描到的定义中是否有任何进一步的配置类如果需要则递归解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand null) {bdCand holder.getBeanDefinition();}// 检查 BeanDefinition 是否是一个配置类的候选者if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {// 如果是递归解析它parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// ... [代码部分省略以简化]// 没有父类 - 处理完成return null;
}在org.springframework.context.annotation.ComponentScanAnnotationParser#parse方法中主要目的是为 ComponentScan 配置的类提供了详细的处理并指导了如何根据给定的属性配置和执行组件扫描。
public SetBeanDefinitionHolder parse(AnnotationAttributes componentScan, final String declaringClass) {// 步骤1. 创建一个新的扫描器ClassPathBeanDefinitionScanner scanner new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean(useDefaultFilters), this.environment, this.resourceLoader);// 步骤2. 根据nameGenerator属性设置Bean名称生成器Class? extends BeanNameGenerator generatorClass componentScan.getClass(nameGenerator);boolean useInheritedGenerator (BeanNameGenerator.class generatorClass);scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));// 步骤3. 设置作用域代理模式或者作用域元数据解析器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));}// 步骤4. 设置资源模式scanner.setResourcePattern(componentScan.getString(resourcePattern));// 步骤5. 根据includeFilters和excludeFilters属性添加类型过滤器for (AnnotationAttributes filter : componentScan.getAnnotationArray(includeFilters)) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addIncludeFilter(typeFilter);}}for (AnnotationAttributes filter : componentScan.getAnnotationArray(excludeFilters)) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addExcludeFilter(typeFilter);}}// 步骤6. 设置bean是否为懒加载boolean lazyInit componentScan.getBoolean(lazyInit);if (lazyInit) {scanner.getBeanDefinitionDefaults().setLazyInit(true);}// 步骤7. 确定扫描器的基础包SetString 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));}// 步骤8. 确保声明ComponentScan的类本身不被注册为beanscanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {Overrideprotected boolean matchClassName(String className) {return declaringClass.equals(className);}});// 步骤9. 使用配置好的扫描器执行实际的组件扫描return scanner.doScan(StringUtils.toStringArray(basePackages));
}我们来到org.springframework.context.annotation.ComponentScanAnnotationParser#parse方法中的步骤1。在org.springframework.context.annotation.ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner方法中首先在这个构造方法初始化了一个新的ClassPathBeanDefinitionScanner对象根据传入的参数决定是否使用默认过滤器并设置了其环境和资源加载器。
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, Nullable ResourceLoader resourceLoader) {// 断言确保注册表不为空Assert.notNull(registry, BeanDefinitionRegistry must not be null);// 将传入的BeanDefinitionRegistry赋值给成员变量registrythis.registry registry;// 根据useDefaultFilters决定是否注册默认的过滤器if (useDefaultFilters) {registerDefaultFilters();}// 设置扫描器的环境setEnvironment(environment);// 设置资源加载器setResourceLoader(resourceLoader);
}在org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters方法中此方法主要用于注册默认的类型过滤器。它首先注册了用于查找带有Component注解的类的过滤器。然后它尝试注册两个JSR标准的注解过滤器JSR-250的ManagedBean和JSR-330的Named。如果相关的类不在类路径上那么这两个过滤器将不会被注册。
protected void registerDefaultFilters() {// 添加一个过滤器来包括带有Component注解的类this.includeFilters.add(new AnnotationTypeFilter(Component.class));// 获取当前类的类加载器ClassLoader cl ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {// 尝试添加一个过滤器来包括带有JSR-250 javax.annotation.ManagedBean注解的类this.includeFilters.add(new AnnotationTypeFilter(((Class? extends Annotation) ClassUtils.forName(javax.annotation.ManagedBean, cl)), false));logger.trace(JSR-250 javax.annotation.ManagedBean found and supported for component scanning);}catch (ClassNotFoundException ex) {// 如果JSR-250 1.1 API如Java EE 6中包含的不可用仅仅跳过}try {// 尝试添加一个过滤器来包括带有JSR-330 javax.inject.Named注解的类this.includeFilters.add(new AnnotationTypeFilter(((Class? extends Annotation) ClassUtils.forName(javax.inject.Named, cl)), false));logger.trace(JSR-330 javax.inject.Named annotation found and supported for component scanning);}catch (ClassNotFoundException ex) {// 如果JSR-330 API不可用仅仅跳过}
}我们来到org.springframework.context.annotation.ComponentScanAnnotationParser#parse方法中的步骤9。在org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法中主要目标是找到指定basePackages中所有的组件并为它们创建 BeanDefinition。这些 BeanDefinition 之后会被 Spring 容器用来创建实际的 bean 实例。
protected SetBeanDefinitionHolder doScan(String... basePackages) {// 断言确保至少有一个基础包被指定Assert.notEmpty(basePackages, At least one base package must be specified);// 用于保存找到的bean定义的集合SetBeanDefinitionHolder beanDefinitions new LinkedHashSet();// 遍历每个基础包for (String basePackage : basePackages) {// 步骤1. 在给定的基础包中找到所有候选的bean定义SetBeanDefinition candidates findCandidateComponents(basePackage);// 遍历找到的bean定义for (BeanDefinition candidate : candidates) {// 步骤2. 解析bean的作用域元数据ScopeMetadata scopeMetadata this.scopeMetadataResolver.resolveScopeMetadata(candidate);// 设置bean的作用域candidate.setScope(scopeMetadata.getScopeName());// 步骤3. 生成bean的名字String beanName this.beanNameGenerator.generateBeanName(candidate, this.registry);// 步骤4. 如果是AbstractBeanDefinition进行后处理if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}// 步骤5. 如果是AnnotatedBeanDefinition处理常见的注解定义if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}// 步骤6. 检查给定的bean名字是否已经存在如果不存在进行注册if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder new BeanDefinitionHolder(candidate, beanName);// 步骤7. 应用作用域代理模式如有必要为bean创建代理definitionHolder AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);// 将bean定义加入集合中beanDefinitions.add(definitionHolder);// 步骤8. 在bean注册表中注册bean定义registerBeanDefinition(definitionHolder, this.registry);}}}// 返回所有注册的bean定义return beanDefinitions;
}我们来到org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法中的步骤1。在org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents方法中主要提供了两种方式查找组件通过预先生成的索引如果可用且支持或通过传统的扫描方式我们重点关注传统的扫描方式。
public SetBeanDefinition findCandidateComponents(String basePackage) {// 如果存在组件索引并且支持include过滤器if (this.componentsIndex ! null indexSupportsIncludeFilters()) {// 从索引中添加候选组件return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);} else {// 扫描给定基础包中的候选组件return scanCandidateComponents(basePackage);}
}在org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法中首先是构建搜索路径用于在类路径中搜索指定包然后是扫描类路径获取匹配的资源通常是 .class 文件再然后是对于每个资源检查是否是候选组件例如是否有 Component 注解最后对于是候选组件的类创建一个 BeanDefinition 对象并添加到结果集中。
private SetBeanDefinition scanCandidateComponents(String basePackage) {// 用于保存候选的Bean定义SetBeanDefinition candidates new LinkedHashSet();try {// 构建包搜索路径例如classpath*:com/example/*String packageSearchPath ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX resolveBasePackage(basePackage) / this.resourcePattern;// 使用模式解析器获取所有匹配的资源即.class文件Resource[] resources getResourcePatternResolver().getResources(packageSearchPath);// ... [代码部分省略以简化]for (Resource resource : resources) {// ... [代码部分省略以简化]// 检查资源是否可读if (resource.isReadable()) {try {// 使用元数据读取器获取类的元数据MetadataReader metadataReader getMetadataReaderFactory().getMetadataReader(resource);// 检查类是否是候选组件例如是否带有Component注释if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);// 再次检查Bean定义是否是候选组件if (isCandidateComponent(sbd)) {// ... [代码部分省略以简化]candidates.add(sbd);} else {// ... [代码部分省略以简化]}} else {// ... [代码部分省略以简化]}}catch (Throwable ex) {// ... [代码部分省略以简化]}} else {// ... [代码部分省略以简化]}}}catch (IOException ex) {// ... [代码部分省略以简化]}return candidates;
}在org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent方法中首先确保类不在排除列表中然后检查它是否在包含列表中并确保它满足任何其他指定条件。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {// 遍历所有的排除过滤器for (TypeFilter tf : this.excludeFilters) {// 如果当前类与任一排除过滤器匹配则直接返回false说明不是候选组件if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}// 遍历所有的包含过滤器for (TypeFilter tf : this.includeFilters) {// 如果当前类与任一包含过滤器匹配if (tf.match(metadataReader, getMetadataReaderFactory())) {// 判断该组件是否满足特定的条件return isConditionMatch(metadataReader);}}// 默认返回false说明不是候选组件return false;
}我们来到org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法中的步骤6。在org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate方法中确保Spring容器中没有重名的、不兼容的bean定义。
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {// 检查bean定义注册表中是否已包含给定名称的bean定义if (!this.registry.containsBeanDefinition(beanName)) {return true; // 如果不存在相同名称的bean定义则返回true}// 获取已存在的bean定义BeanDefinition existingDef this.registry.getBeanDefinition(beanName);// 获取原始的bean定义如果有的话BeanDefinition originatingDef existingDef.getOriginatingBeanDefinition();if (originatingDef ! null) {existingDef originatingDef;}// 检查给定的bean定义与已存在的bean定义是否兼容if (isCompatible(beanDefinition, existingDef)) {return false; // 如果它们是兼容的则返回false}// 如果给定的bean定义与已存在的bean定义不兼容则抛出异常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() ]);
}我们来到org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法中的步骤8。在org.springframework.context.annotation.ClassPathBeanDefinitionScanner#registerBeanDefinition方法中主要调用 BeanDefinitionReaderUtils 类的 registerBeanDefinition 方法用于实际的 BeanDefinition 注册过程。
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}在org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition方法中主要用于将提供的 BeanDefinitionHolder 中的 BeanDefinition 及其所有别名注册到 BeanDefinitionRegistry 中。对于ComponentScan的扫描和注册阶段而言当registerBeanDefinition方法被调用时已经完成了。但对于整个Spring容器的生命周期来说还有其他重要的步骤将在后续发生如bean的生命周期回调、bean的实例化、bean的初始化等。
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// 获取 bean 的主名称并在 registry 中注册它String beanName definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// 如果提供了 bean 的别名则注册这些别名String[] aliases definitionHolder.getAliases();if (aliases ! null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}
}八、注意事项
默认扫描
如果未指定具体的包ComponentScan 默认会扫描声明此注解的类所在的包及其子包。
性能考虑
避免扫描不必要的包因为这可能导致性能问题。尤其是在大型项目中指定扫描的精确路径可以加速启动时间。
默认过滤器
默认情况下ComponentScan 使用的过滤器会搜索带有 Component, Service, Repository, 和 Controller 的类。可以通过 includeFilters 和 excludeFilters 属性进行定制。
冲突的 Bean 名称
确保没有重复的 Bean 名称否则可能会导致 BeanDefinitionStoreException。
使用 basePackages 和 basePackageClasses
basePackages 允许我们指定要扫描的包的名称而 basePackageClasses 允许我们指定一个或多个类Spring 将扫描这些类所在的包。
避免使用多个配置
不建议在同一个配置类中使用多个 ComponentScan。如果确实需要考虑使用 ComponentScans。
代理模式
考虑如何使用 scopedProxy 属性特别是当我们使用非单例作用域的 beans 时。
注解属性的覆盖
当多个 ComponentScan 在多个配置类中定义时后面的定义将覆盖前面的定义。这里需要我们自己去确认。
对于大型项目考虑使用模块化
在大型项目中为了更好的管理和维护可以考虑将应用分成多个模块每个模块有其自己的配置类和 ComponentScan。
九、总结
最佳实践总结
应用启动 在 ComponentScanApplication 的主方法中使用 AnnotationConfigApplicationContext 初始化了 Spring 上下文并将配置类 MyConfiguration 传递给它。这告诉 Spring 在 MyConfiguration 类中查找配置信息。 配置类 MyConfiguration 类被标记为 Configuration表明它是一个配置类。这个类进一步使用 ComponentScan 注解指定了 Spring 应该在哪里寻找组件。具体来说Spring 将扫描 com.xcs.spring 包及其所有子包。 扫描规则 在 ComponentScan 中我们使用 includeFilters 明确指定 SpecialComponent 类被包含在 Spring 容器中即使它没有使用任何 Spring 注解。同时使用 excludeFilters 指定 AdminService 类不应该被 Spring 容器管理即使它被标记为一个 Service。 组件类 UserRepository 类在 com.xcs.spring.repository 包中并被标记为 Repository因此它自动被 Spring 容器管理。UserService 类在 com.xcs.spring.service 包中并被标记为 Service因此它也自动被 Spring 容器管理。AdminService 虽然也被标记为 Service但由于 ComponentScan 的 excludeFilters 配置它没有被 Spring 容器管理。SpecialComponent 类没有使用任何 Spring 注解但由于 ComponentScan 的 includeFilters 配置它被 Spring 容器管理。 运行结果 当应用启动时所有被 Spring 容器管理的 beans 的名字都被打印出来这包括了 UserRepository, UserService, 和 SpecialComponent。不包括 AdminService因为它被排除了。
源码分析总结
应用启动 通过 AnnotationConfigApplicationContext 的构造方法传入配置类 MyConfiguration来启动Spring应用。 刷新上下文 在构造方法内部调用了 refresh() 方法开始执行容器的刷新操作。 执行BeanFactory的后处理器 invokeBeanFactoryPostProcessors(beanFactory) 方法被调用它主要执行 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor。其中 BeanDefinitionRegistryPostProcessor 是在所有其他bean定义加载之前用来修改默认的bean定义。 处理配置类 ConfigurationClassPostProcessor 是一个核心的后处理器它会解析配置类如带有 Configuration 的类找到 ComponentScan 注解并解析它的属性然后进行组件扫描。 执行组件扫描 通过 ComponentScanAnnotationParser 类进行详细的扫描操作。它创建一个 ClassPathBeanDefinitionScanner 对象设置其属性如是否使用默认过滤器、资源加载器、作用域解析器、资源模式、包含和排除的过滤器等然后扫描指定的基础包。 扫描候选组件 对于每个基础包它会查找所有的组件并为这些组件创建 BeanDefinition 对象。 注册Bean定义 找到的组件都会被注册到Spring容器中。这是通过调用 registerBeanDefinition 方法来完成的。如果在容器中已存在同名的bean定义会进行冲突检查。 完成组件扫描 当所有的基础包都被扫描完成ComponentScan 的操作就执行结束了。