diff --git a/README.md b/README.md index f323285..d81cd4e 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ 大家好呀,我是Lex。我是一名拥有8年经验的Java 后端开发人员,也是一个对 Spring 框架充满热情的程序员。为了帮助那些希望深入了解 Spring 框架的程序员们,我创建了这个 “Spring 源码阅读系列”。通过这个系列,我希望能够与你们共同探索 Spring 的内部工作机制。如果您有同样的兴趣或问题,请联系我! -## 🙏 顺手点个星? +## 🙏顺手点个星? 亲爱的朋友们,我真的花了很多心思去研究和整理这个“Spring 源码阅读系列”。如果你觉得这东西还不错,或者给你带来了一点点帮助,麻烦点一下星星吧🌟。这真的对我意义重大,每一颗星都能让我觉得所有的努力都是值得的。我知道这是小事一桩,但你的那一下点击,对我来说就是最好的鼓励。无论如何,都要感谢你抽时间阅读我的内容,真的很感激! -## ⚡ 技术 +## ⚡技术 ![Docker](https://img.shields.io/badge/-Docker-000?&logo=Docker) ![Kubernetes](https://img.shields.io/badge/-Kubernetes-000?&logo=Kubernetes) @@ -21,15 +21,15 @@ ![MySQL](https://img.shields.io/badge/-MySQL-000?&logo=MySQL) ![GitHub](https://img.shields.io/badge/-GitHub-181717?style=flat-square&logo=github) -## 📫 与我联系 +## 📫与我联系 ✉️ [Email](xuchengshengsuper@163.com) | 💬 [Issue](https://github.com/xuchengsheng/spring-reading/issues) | 🌐 [CSDN](https://blog.csdn.net/duzhuang2399?type=blog) Me about everything! -## ⛵ 欢迎贡献! +## ⛵欢迎贡献! 如果你发现任何错误或者有改进建议,欢迎提交 issue 或者 pull request。你的反馈对于我非常宝贵! -## 💻 我的 GitHub 统计 +## 💻我的 GitHub 统计 ![Github Stats](https://github-readme-stats.vercel.app/api?username=xuchengsheng&count_private=true&show_icons=true) [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=xuchengsheng&layout=compact)](https://github.com/anuraghazra/github-readme-stats) diff --git a/spring-annotation-componentScan/README-BACK.md b/spring-annotation-componentScan/README-BACK.md new file mode 100644 index 0000000..1b6d6f5 --- /dev/null +++ b/spring-annotation-componentScan/README-BACK.md @@ -0,0 +1,1693 @@ +## @ComponentScan + +[TOC] + +### 1、注解说明 + +`@ComponentScan`是Spring的核心注解,用于自动扫描和注册指定包下的组件。我们可以指定包路径、自定义组件过滤规则,并决定是否启用默认过滤器。该注解还支持懒加载、自定义命名策略、作用域解析,以及为特定生命周期的beans指定代理模式。默认情况下,它会扫描带有`@Component`、`@Service`、`@Repository`和`@Controller`的类,但可以通过`useDefaultFilters`属性调整。此外,从Spring 3.1开始,这个注解支持重复性,允许在单个配置类上多次使用,每次具有不同的属性设置。 + +### 2、注解源码 + +`@ComponentScan`注解是 Spring 框架自 3.0 版本开始引入的一个核心注解, + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Repeatable(ComponentScans.class) +public @interface ComponentScan { + + @AliasFor("basePackages") + String[] value() default {}; + + @AliasFor("value") + String[] basePackages() default {}; + + Class[] basePackageClasses() default {}; + + Class nameGenerator() default BeanNameGenerator.class; + + Class scopeResolver() default AnnotationScopeMetadataResolver.class; + + ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; + + String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; + + boolean useDefaultFilters() default true; + + Filter[] includeFilters() default {}; + + Filter[] excludeFilters() default {}; + + boolean lazyInit() default false; + + @Retention(RetentionPolicy.RUNTIME) + @Target({}) + @interface Filter { + + FilterType type() default FilterType.ANNOTATION; + + @AliasFor("classes") + Class[] value() default {}; + + @AliasFor("value") + Class[] classes() default {}; + + String[] pattern() default {}; + } +} +``` + +### 3、字段描述 + +#### 3.1 value + +别名为 basePackages。这指定了一个或多个要扫描的基础包。扫描器将在这些包及其子包中搜索符合条件的组件。 + +#### 3.2 basePackages + +与 value 属性功能相同,指定一个或多个要扫描的基础包。 + +#### 3.3 basePackageClasses + +通过指定类来定义要扫描的包。提供的类本身不必标记为一个组件,它们仅仅用于确定要扫描的基础包。 + +#### 3.4 nameGenerator + +提供一个Bean名称生成策略,用于为在组件扫描期间注册的bean定义生成名称。 + +#### 3.5 scopeResolver + +定义一个范围元数据解析策略,它为在组件扫描期间注册的bean定义提供范围信息。 + +#### 3.6 scopedProxy + +定义如何处理scoped beans的代理。这可以是创建一个接口代理还是使用CGLIB创建一个类的代理。 + +`ScopedProxyMode`有以下取值: + +1. **DEFAULT**: 默认设置,这意味着必须通过其他方式进行配置,例如全局配置。如果未在其他地方明确配置,则不会创建代理。 +2. **NO**: 表示不应为bean创建代理。在这种模式下,当你尝试注入短作用域的bean到更长作用域的bean时,会出现问题,因为你每次获得的都是同一个实例,而不是新的短作用域实例。 +3. **TARGET_CLASS**: 这意味着应为目标bean创建CGLIB代理。这种模式是完全基于类的,不需要接口。当目标bean被代理时,它会成为子类的实例。这对于那些没有实现接口或需要代理具体的类非常有用。 +4. **INTERFACES**: 代理必须通过接口实现。当使用此模式时,bean必须实现至少一个接口,代理将实现这些接口。如果bean没有实现任何接口,此模式将抛出异常。 + +#### 3.7 resourcePattern + +定义要扫描的资源的模式。默认情况下,只有“.class”文件会被扫描。 + +#### 3.8 useDefaultFilters + +是否应使用默认过滤器来检测例如 @Component、@Repository、@Service、@Controller 注解的组件。 + +#### 3.9 includeFilters + +为扫描指定自定义的包括过滤器,即哪些组件应该被Spring上下文包括。 + +#### 3.10 excludeFilters + +为扫描指定自定义的排除过滤器,即哪些组件应该被Spring上下文排除。 + +#### 3.11 lazyInit + +如果为true,则所有通过此扫描注册的组件都将默认为延迟初始化。这意味着它们不会被实例化,除非显式地请求或在注入点被引用。 + +#### 3.12 Filter + +Filter 定义了在组件扫描过程中使用的过滤器,可以基于注解、正则表达式等进行过滤。 + +###### 3.12.1 type + +指定过滤器的类型。默认情况下,它基于注解进行过滤。 + +###### 3.12.2 value + +指定过滤器要搜索的注解或类型。 + +###### 3.12.3 classes + +同 value,指定过滤器要搜索的注解或类型。 + +###### 3.12.4 pattern + +如果 type 被设置为 FilterType.REGEX 或 FilterType.ASPECTJ,则使用该属性指定模式。 + +### 4、如何使用 + +#### 4.1、使用basePackages或者value配置扫描包路径 + +使用basePackages或者value是我们最最最常见的一种配置扫描包的方式了,主要包含默认扫描包与指定扫描包2种方式。 + +**默认包扫描** + +当我们在一个配置类上简单地使用 `@ComponentScan` 无任何参数时,Spring 会扫描该配置类所在的包及其子包下的所有组件和服务。 + +```java +@Configuration +@ComponentScan +public class MyConfiguration { + +} +``` + +**指定包扫描** + +使用 `basePackages` 属性来指定扫描的包路径。 + +```java +@Configuration +@ComponentScan(basePackages = "com.xcs.spring") +public class MyConfiguration { + +} +``` + +或者使用 `value` 属性,它是 `basePackages` 的别名 + +```java +@Configuration +@ComponentScan("com.xcs.spring") +public class MyConfiguration { + +} +``` + +#### 4.2、使用basePackageClasses配置扫描包路径 + +`basePackageClasses` 允许开发者指定一个或多个类,Spring 会扫描这些类所在的包以及其子包中的所有类,以查找带有 `@Component`, `@Service`, `@Repository`, `@Controller` 以及其他相关注解的类,并将它们注册到 Spring 应用上下文中。 + +**示例**: + +假设你有以下的包结构: + +```java +com.xcs.spring + ├── config + │ └── MyConfiguration.java + ├── controller + │ ├── MyController1.java + │ └── MyController2.java + └── service + ├── MyService1.java + └── MyService2.java + └── MyPackageMarker.java +``` + +如果你想从 `com.xcs.spring` 开始扫描(包括所有子包),你可以在 `com.xcs.spring` 下创建一个标记类,`MyPackageMarker`类并不是为了执行某种实际功能而定义的,而是仅仅作为一个参考或者标记。它通常位于你想要扫描的包的根目录 + +**例如:** + +```java +package com.xcs.spring; + +public class MyPackageMarker { + +} +``` + +然后,在你的配置类中使用: + +```java +@Configuration +@ComponentScan(basePackageClasses = MyPackageMarker.class) +public class MyConfiguration { + +} +``` + +**使用basePackageClasses字段比basePackages或者value有何优势呢?** + +使用 `basePackageClasses` 比使用 `basePackages` 优雅,因为它避免了硬编码字符串包名。如果你重构代码并更改包名,那么使用 `basePackageClasses` 的方法会更安全,因为IDE会自动更新类的引用。而硬编码的字符串包名在重构时可能会被遗漏。 + +#### 4.3、使用nameGenerator自定义Bean的名称 + +`nameGenerator` 允许我们为Spring的Bean指定一个 `BeanNameGenerator` 实现,该实现将为组件生成bean的名字。 + +**用法:** + +如果你要使用自定义的 `BeanNameGenerator`,只需实现 `BeanNameGenerator` 接口,并在 `@ComponentScan` 中指定该类即可。 + +```java +@Configuration +@ComponentScan(nameGenerator = MyCustomBeanNameGenerator.class) +public class MyConfiguration { + +} +``` + +我们使用自定义的 `MyCustomBeanNameGenerator`,只需实现 `BeanNameGenerator` 接口中的generateBeanName方法,bean的名称根据自己的实际情况生成 + +```java +public class MyCustomBeanNameGenerator implements BeanNameGenerator { + + @Override + public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { + // 此处只是一个示例,我们可以根据自己的实际情况生成Bean名称 + String originalName = definition.getBeanClassName(); + if (originalName != null) { + return "_这是我自定义Bean名称的前缀_" + originalName; + } else { + // 你可以选择其他的逻辑处理或者抛出异常 + throw new IllegalArgumentException("Bean class name is null"); + } + } +} +``` + +#### 4.4、使用scopeResolver自定义Bean的作用域 + +`scopeResolver`属性允许您指定一个自定义的`ScopeMetadataResolver`,它解析bean的作用域元数据。通过这种方式,你可以控制和自定义bean的作用域策略。 + +**用法:** + +如果要使用自定义的`ScopeMetadataResolver`,需要实现`ScopeMetadataResolver`接口并在`@ComponentScan`中指定这个类。 + +```java +@Configuration +@ComponentScan(scopeResolver = MyCustomScopeMetadataResolver.class) +public class MyConfiguration { + +} +``` + +我们使用自定义的 `MyCustomScopeMetadataResolver`,只需实现 `ScopeMetadataResolver` 接口中的resolveScopeMetadata方法,bean的作用于根据自己的实际情况生成。我们此处给`MyService1`与`MyService2`设置成了多例模式,否则我们重新交由`AnnotationScopeMetadataResolver`类解析,如果不由`AnnotationScopeMetadataResolver`类处理的话会导致@Scope注解失效 + +```java +public class MyCustomScopeMetadataResolver implements ScopeMetadataResolver { + + private AnnotationScopeMetadataResolver resolver = new AnnotationScopeMetadataResolver(); + + @Override + public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { + ScopeMetadata scopeMetadata = new ScopeMetadata(); + + // 检查Bean的名称是否为'MyService1'或'MyService2',并据此设置相应的作用域。 + if (MyService1.class.getName().equals(definition.getBeanClassName()) || + MyService2.class.getName().equals(definition.getBeanClassName())) { + scopeMetadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); + return scopeMetadata; + } + // 再次交由AnnotationScopeMetadataResolver解析否则会导致@Scope失效 + return resolver.resolveScopeMetadata(definition); + } +} +``` + +**注意:** + + `scopedProxy` 和 `scopeResolver` 当两者都被设置时,`scopedProxy` 的优先级更高,并且 `scopeResolver` 会被忽略 + +#### 4.5、使用`includeFilters `定义哪些组件应该被包含 + +**基于ANNOTATION过滤** + +只有使用`@Service`注解的类会被包含 + +```java +@Service +public class MyService1 { + +} + +@Service +public class MyService2 { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class), useDefaultFilters = false) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中有被`@Service`注解标注的类,被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`ANNOTATION`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myService1 +beanName = myService2 +``` + +**基于ASSIGNABLE_TYPE过滤** + +只有实现`MyInterface`接口的组件会被包含 + +```java +public interface MyInterface { + +} + +@Component +public class MyComponent implements MyInterface { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyInterface.class), useDefaultFilters = false) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中有`MyComponent`类,因为`MyComponent`实现了`MyInterface`接口,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`ASSIGNABLE_TYPE`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponent +``` + +**基于CUSTOM过滤** + +类名以“Custom”结尾的组件会被包含 + +```java +public class MyCustomTypeFilter implements TypeFilter { + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { + return metadataReader.getClassMetadata().getClassName().endsWith("Custom"); + } +} + +@Component +public class MyComponentCustom { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyCustomTypeFilter.class), useDefaultFilters = false) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中有`MyComponentCustom`类,因为`MyComponentCustom`类符合以`“Custom”`结尾的组件,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`CUSTOM`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponentCustom +``` + +**基于REGEX过滤** + +类名匹配正则表达式`.*ComponentRegex`的组件会被包含。 + +```java +@Component +public class MyComponentRegex { + +} + +@Configuration +@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*ComponentRegex"), useDefaultFilters = false) +public class MyConfiguration { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中有`myComponentRegex`类,因为`MyComponentRegex`类符合以表达式`.*ComponentRegex`的组件,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`REGEX`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponentRegex +``` + +**基于AspectJ过滤** + +类名匹配AspectJ类型模式`*..*AspectJ`的组件会被包含。 + +```java +@Component +public class MyComponentAspectJ { + +} + +@Configuration +@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..*AspectJ"), useDefaultFilters = false) +public class MyConfiguration { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中有`myComponentAspectJ`类,因为`MyComponentRegex`类符合以AspectJ类型模式`*..*AspectJ`的组件,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`AspectJ`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponentAspectJ +``` + +**为什么网上很多博主说设置了includeFilters包含规则后,必须要把useDefaultFilters字段设置成false呢?为什么要这样做?** + +想象一下,既然你都使用了includeFilters字段,说明你只想在组件扫描中包括那些具有自定义注解的bean,而忽略所有其他的bean。假如说你设置了includeFilters字段,然后`useDefaultFilters`没有设置为`false`,这意味着具有`@Component`, `@Repository`, `@Service`, 和`@Controller`等注解的类会被自动检测并注册为beans,可能会出现重复扫描,可能会出现混淆,违背了我们使用includeFilters做过滤的初衷。因此,通常建议明确地设置`useDefaultFilters`的值,以避免任何混淆和不期望的行为。如果你仅想使用`includeFilters`,最好将`useDefaultFilters`设置为`false`。如果你想使用默认的过滤器和自定义的过滤器,请明确地设置`useDefaultFilters`为`true`。 + +#### 4.6、使用`excludeFilters`定义哪些组件应该被排除 + +**基于ANNOTATION过滤** + +所有使用`@Service`注解的类将不会被注册。 + +```java +@Service +public class MyService1 { + +} + +@Service +public class MyService2 { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中没有被`@Service`标注的类,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`ANNOTATION`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponent +beanName = myComponentAspectJ +beanName = myComponentCustom +beanName = myComponentRegex +beanName = myController1 +beanName = myController2 +``` + +**基于ASSIGNABLE_TYPE过滤** + +实现`ExcludedInterface`接口的组件都不会被注册。 + +```java +public interface MyInterface { + +} + +@Component +public class MyComponent implements MyInterface { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyInterface.class)) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中没有`MyComponent`类对象,因为`MyComponent`类实现了`MyInterface`所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`ASSIGNABLE_TYPE`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponentAspectJ +beanName = myComponentCustom +beanName = myComponentRegex +beanName = myController1 +beanName = myController2 +beanName = myService1 +beanName = myService2 +``` + +**基于CUSTOM过滤** + +类名以`"Custom"`结尾的组件都不会被注册。 + +```java +public class MyCustomTypeFilter implements TypeFilter { + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { + return metadataReader.getClassMetadata().getClassName().endsWith("Custom"); + } +} + +@Component +public class MyComponentCustom { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyCustomTypeFilter.class)) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中没有`MyComponentCustom`类对象,因为`MyComponentCustom`类名以`"Custom"`结尾的组件,所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`CUSTOM`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponent +beanName = myComponentAspectJ +beanName = myComponentRegex +beanName = myController1 +beanName = myController2 +beanName = myService1 +beanName = myService2 +``` + +**基于REGEX过滤** + +类名匹配正则表达式`.*ComponentRegex`的组件不会被注册。 + +```java +@Component +public class MyComponentRegex { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*ComponentRegex")) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中没有`MyComponentRegex`类对象,因为`MyComponentRegex`类名符合表达式`.*ComponentRegex`的组件,所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`REGEX`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponent +beanName = myComponentAspectJ +beanName = myComponentCustom +beanName = myController1 +beanName = myController2 +beanName = myService1 +beanName = myService2 +``` + +**基于AspectJ过滤** + +类名匹配AspectJ类型模式`*..*AspectJ`的组件不会被注册。 + +```java +@Component +public class MyComponentAspectJ { + +} + +// 还有其他被@Controller,@Component标注类再此不展示了。。。 + +@Configuration +@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..*AspectJ")) +public class MyComponentScanConfig { + +} +``` + +通过main方法运行查看结果 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +通过运行结果发现,在Bean容器中没有`MyComponentAspectJ`类对象,因为`MyComponentAspectJ`类名符合AspectJ类型模式`*..*AspectJ`的组件,所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`AspectJ`类型过滤已经生效 + +```java +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 = myComponentScanConfig +beanName = myComponent +beanName = myComponentCustom +beanName = myComponentRegex +beanName = myController1 +beanName = myController2 +beanName = myService1 +beanName = myService2 +``` + +### 5、源码分析 + +我们的`ComponentScanApplication`类是main方法入口,那我们就从这里开始跟踪源码,我们使用的是`AnnotationConfigApplicationContext`做为上下文环境,并传入了一个组件类的类名,那么我们继续进入`AnnotationConfigApplicationContext`的构造函数查看源码 + +```java +public class ComponentScanApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + System.out.println("beanName = " + beanDefinitionName); + } + } +} +``` + +首先我们来看看源码中的,构造函数中,执行了三个步骤,我们重点关注refresh()方法 + +```java +public AnnotationConfigApplicationContext(Class... componentClasses) { + this(); + register(componentClasses); + refresh(); +} +``` + +`refresh()`方法中我们重点关注一下`invokeBeanFactoryPostProcessors(beanFactory)`这方法,其他方法不是本次源码阅读的重点暂时忽略,在`invokeBeanFactoryPostProcessors(beanFactory)`方法会对实现了`BeanDefinitionRegistryPostProcessor`,`BeanFactoryPostProcessor`这两个接口进行接口回调。 + +```java +public void refresh() throws BeansException, IllegalStateException { + // ----------------------忽略其他代码--------------------------- + // Invoke factory processors registered as beans in the context. + invokeBeanFactoryPostProcessors(beanFactory); + // ----------------------忽略其他代码--------------------------- +} +``` + +在`invokeBeanFactoryPostProcessors()`中又委托了`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`进行调用 + +```java +protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { + PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); + // ----------------------忽略其他代码--------------------------- +} +``` + +在这个`invokeBeanFactoryPostProcessors(beanFactory, beanFactoryPostProcessors)`方法中,主要是对`BeanDefinitionRegistryPostProcessor`,`BeanFactoryPostProcessor`这两个接口的实现类进行回调,至于为什么这个方法里面代码很长呢?其实这个方法就做了一个事就是对处理器的执行顺序在做出来。比如说要先对实现了`PriorityOrdered.class`类回调,在对实现了`Ordered.class`类回调,最后才是对没有实现任何优先级的处理器进行回调。 + +```java +public static void invokeBeanFactoryPostProcessors( + ConfigurableListableBeanFactory beanFactory, + List beanFactoryPostProcessors) { + + // ----------------------忽略其他代码--------------------------- + + // 存储已经处理过的bean + Set processedBeans = new HashSet<>(); + + // 创建一个新的BeanDefinitionRegistryPostProcessor列表来存放当前正在处理的后处理器。 + List currentRegistryProcessors = new ArrayList<>(); + + // 获取所有已注册且为BeanDefinitionRegistryPostProcessor类型的bean的名称。 + String[] postProcessorNames = + beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + + // 遍历获取到的BeanDefinitionRegistryPostProcessor的名字 + for (String ppName : postProcessorNames) { + // 筛选出那些还实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor + if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + // 将筛选出的后处理器实例添加到currentRegistryProcessors列表中 + currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); + processedBeans.add(ppName); + } + } + + // 使用sortPostProcessors方法,根据它们的优先级对BeanDefinitionRegistryPostProcessor进行排序。 + sortPostProcessors(currentRegistryProcessors, beanFactory); + + // 将当前处理的currentRegistryProcessors添加到全局的registryProcessors列表中。 + registryProcessors.addAll(currentRegistryProcessors); + + // 调用invokeBeanDefinitionRegistryPostProcessors方法执行所有选定的后处理器。 + invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); + + // ----------------------忽略其他代码--------------------------- +} +``` + +下面是我画的一个时序图大家可以参考一下。 + +~~~mermaid +sequenceDiagram + participant Init as invokeBeanFactoryPostProcessors + participant BDRPP_PO as BDRPP(PriorityOrdered) + participant BDRPP_O as BDRPP(Ordered) + participant BDRPP as 其余的BDRPP + participant BFPP_PO as BFPP(PriorityOrdered) + participant BFPP_O as BFPP(Ordered) + participant BFPP as 其余的BFPP + + Init->>BDRPP_PO: 回调 + BDRPP_PO-->>Init: 完成 + Init->>BDRPP_O: 回调 + BDRPP_O-->>Init: 完成 + Init->>BDRPP: 回调 + BDRPP-->>Init: 完成 + Init->>BFPP_PO: 回调 + BFPP_PO-->>Init: 完成 + Init->>BFPP_O: 回调 + BFPP_O-->>Init: 完成 + Init->>BFPP: 回调 + BFPP-->>Init: 完成 + + Note right of BFPP: 提示: + Note right of BFPP: BDRPP = BeanDefinitionRegistryPostProcessor + Note right of BFPP: BFPP = BeanFactoryPostProcessor + + +~~~ + +从截图就可以看出`ConfigurationClassPostProcessor`也实现了`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry`方法,其中一个重要的功能就是解析`@ComponentScan`注解。 + +![image-20230816101148882](https://img-blog.csdnimg.cn/851ff307c3644840b5cee1062c513b4d.png#pic_center) + +`invokeBeanDefinitionRegistryPostProcessors(postProcessors)`方法中,循环调用了实现`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry(registry)`方法, + +```java +private static void invokeBeanDefinitionRegistryPostProcessors(Collection 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(); + } +} +``` + +在`ConfigurationClassPostProcessor`类中的`postProcessBeanDefinitionRegistry`回调方法中又调用了`processConfigBeanDefinitions(registry)`方法 + +```java +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); +} +``` + +在`processConfigBeanDefinitions(registry)`方法中,创建一个用于解析`@Configuration`类的解析器实例(在我们的Demo中只有一个`MyComponentScanConfig`的配置类),最后调用`parser.parse(candidates)`方法去解析。 + +```java +public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { + // ...[省略其他代码]... + + // 创建一个用于解析@Configuration类的解析器实例。 + // 传入所需的依赖,如元数据读取器工厂、问题报告器、环境、资源加载器等。 + ConfigurationClassParser parser = new ConfigurationClassParser( + this.metadataReaderFactory, this.problemReporter, this.environment, + this.resourceLoader, this.componentScanBeanNameGenerator, registry); + + // 初始化待解析的配置类集合以及已解析的配置类集合。 + Set candidates = new LinkedHashSet<>(configCandidates); + Set alreadyParsed = new HashSet<>(configCandidates.size()); + + // 循环解析候选的@Configuration类,直到所有配置类都被解析。 + do { + StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); + + // 使用解析器解析当前的候选配置类。如果发现更多的配置类,它们将被添加到candidates集合中。 + parser.parse(candidates); + + // 对解析的结果进行校验,确保它们不违反任何约束。 + parser.validate(); + } while (!candidates.isEmpty()); // 如果还有未解析的候选配置类,则继续循环。 + + // ...[省略其他代码]... +} +``` + +, + +```java +/** + * 解析给定的配置类候选集。 + * + * @param configCandidates 待解析的配置类的BeanDefinitionHolder集合 + */ +public void parse(Set configCandidates) { + // 遍历每一个配置类候选 + for (BeanDefinitionHolder holder : configCandidates) { + // 获取BeanDefinition + BeanDefinition bd = holder.getBeanDefinition(); + + try { + // 如果BeanDefinition是一个AnnotatedBeanDefinition(通过注解定义的) + if (bd instanceof AnnotatedBeanDefinition) { + // 根据其元数据和bean名称解析它 + parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); + } + // 如果BeanDefinition是一个AbstractBeanDefinition,并且它有一个bean类 + else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { + // 根据其bean类和bean名称进行解析 + parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); + } + // 其他情况,直接使用bean的类名进行解析 + else { + parse(bd.getBeanClassName(), holder.getBeanName()); + } + } + // 捕获BeanDefinitionStoreException,并直接重新抛出 + catch (BeanDefinitionStoreException ex) { + throw ex; + } + // 捕获其他异常,封装后抛出 + catch (Throwable ex) { + throw new BeanDefinitionStoreException( + "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); + } + } + + // 处理所有延迟的导入选择器 + this.deferredImportSelectorHandler.process(); +} +``` + +在parse使用给定的注解元数据和bean名称创建一个新的`ConfigurationClass`实例。 + +```java +protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { + processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); +} +``` + +这里的核心逻辑是通过do-while循环递归地调用`doProcessConfigurationClass(configClass, sourceClass, filter)`方法,目的是处理配置类及其超类层次结构。这确保了不仅仅是直接注解了`@Configuration`的类被处理,而且它的所有超类也都被解析。 + +```java +protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException { + // 1. 使用条件评估器判断当前的配置类是否应该被跳过。 + if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { + return; + } + + // 2. 检查当前的配置类是否已经被处理过。 + ConfigurationClass existingClass = this.configurationClasses.get(configClass); + + // 3. 如果该配置类已存在,检查其来源(是否由导入或其他方式得到)。 + if (existingClass != null) { + if (configClass.isImported()) { + if (existingClass.isImported()) { + // 合并两个导入的配置类的来源信息。 + existingClass.mergeImportedBy(configClass); + } + // 如果当前类是导入的,但已存在的类不是,则忽略当前类。 + return; + } else { + // 如果存在的类是导入的,但当前类不是,则删除旧的类并使用新类。 + this.configurationClasses.remove(configClass); + this.knownSuperclasses.values().removeIf(configClass::equals); + } + } + + // 4. 递归处理配置类及其所有的超类。 + SourceClass sourceClass = asSourceClass(configClass, filter); + do { + sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); + } while (sourceClass != null); + + // 5. 在处理完成后,将配置类添加到已处理列表中。 + this.configurationClasses.put(configClass, configClass); +} +``` + +我们来到`doProcessConfigurationClass(configClass, sourceClass, filter)`方法中,是不是看到了我们非常熟悉的`ComponentScan.class`注解类,我们之前配置的`@ComponentScan`注解就是在此处被解析了,并把解析后的内容封装在一个`AnnotationAttributes`中,然后调用 `this.componentScanParser.parse`进行解析,最后解析到的`BeanDefinitionHolder`进行递归处理,为什么要递归处理?因为扫描到的类还有可能是`@Configuration`标记的类,所以还要再次对扫描到的类继续解析。 + +```java +protected final SourceClass doProcessConfigurationClass( + ConfigurationClass configClass, SourceClass sourceClass, Predicate filter) + throws IOException { + // ----------------------忽略其他代码--------------------------- + + // 获取sourceClass上的@ComponentScan和@ComponentScans注解信息 + Set componentScans = AnnotationConfigUtils.attributesForRepeatable( + sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); + + // 判断sourceClass上是否有@ComponentScan或@ComponentScans注解,并且它没有被某种条件(如@Conditional注解)排除 + if (!componentScans.isEmpty() && + !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { + + // 遍历每一个@ComponentScan注解的属性 + for (AnnotationAttributes componentScan : componentScans) { + + // 执行具体的组件扫描,并返回扫描到的Bean定义 + Set scannedBeanDefinitions = + this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); + + // 遍历每个扫描到的Bean定义,检查其是否为配置类,并递归解析 + for (BeanDefinitionHolder holder : scannedBeanDefinitions) { + BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); + if (bdCand == null) { + bdCand = holder.getBeanDefinition(); + } + + // 如果当前Bean定义是一个配置类候选,则进行递归解析 + if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { + parse(bdCand.getBeanClassName(), holder.getBeanName()); + } + } + } + } + // ----------------------忽略其他代码--------------------------- + // No superclass -> processing is complete + return null; +} +``` + +我们来到`ComponentScanAnnotationParser`类中的`parse(componentScan,declaringClass)`方法 ,我们在`@ComponentScan`注解中定义的配置都被封装了了`AnnotationAttributes`类中,最后通过一个`ClassPathBeanDefinitionScanner`类扫描器去处理,并把我们之前在`MyComponentScanConfig`类中设置的`@ComponentScan`参数全部传入给这个扫描器。 + +```java +public Set parse(AnnotationAttributes componentScan, final String declaringClass) { + + // 1. 创建扫描器,设置是否使用默认的过滤器,如@Component、@Service等 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner( + this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); + + // 2. 设置Bean命名生成策略 + Class 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); + } + // 如果没有设置scopedProxy,就使用scopeResolver定义的解析器 + else { + Class resolverClass = componentScan.getClass("scopeResolver"); + scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); + } + + // 4. 设置资源的搜索模式,例如 "*.class" + scanner.setResourcePattern(componentScan.getString("resourcePattern")); + + // 5. 根据includeFilters添加包括类型的过滤器 + for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { + for (TypeFilter typeFilter : typeFiltersFor(filter)) { + scanner.addIncludeFilter(typeFilter); + } + } + + // 6. 根据excludeFilters添加排除类型的过滤器 + for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { + for (TypeFilter typeFilter : typeFiltersFor(filter)) { + scanner.addExcludeFilter(typeFilter); + } + } + + // 7. 设置懒加载 + boolean lazyInit = componentScan.getBoolean("lazyInit"); + if (lazyInit) { + scanner.getBeanDefinitionDefaults().setLazyInit(true); + } + + // 8. 确定要扫描的基本包 + Set 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)); + } + + // 9. 排除@ComponentScan的声明类自身,防止自己被扫描 + scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { + @Override + protected boolean matchClassName(String className) { + return declaringClass.equals(className); + } + }); + + // 10. 执行扫描并返回扫描到的Bean定义 + return scanner.doScan(StringUtils.toStringArray(basePackages)); +} +``` + +我们来看看这个`ClassPathBeanDefinitionScanner`类的构造方法,在这个类中有一个重要的初始化过程就是`registerDefaultFilters()`方法主要作用就是注册一个默认过滤器,有一个条件判断`useDefaultFilters`,而我们在启动的时候`@ComponentScan`注解中配置了`useDefaultFilters`为true,默认值也是true。 + +```java +public ClassPathBeanDefinitionScanner( + BeanDefinitionRegistry registry, + boolean useDefaultFilters, + Environment environment, + @Nullable ResourceLoader resourceLoader) { + + // 确保registry不为null,否则抛出异常。 + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + + // 将registry参数赋值给类成员变量。 + this.registry = registry; + + // 如果设置为使用默认过滤器,则注册这些默认过滤器。 + if (useDefaultFilters) { + registerDefaultFilters(); + } + + // 设置扫描器的环境。 + setEnvironment(environment); + + // 设置资源加载器。 + setResourceLoader(resourceLoader); +} +``` + +个方法确保了类路径扫描器可以识别带有`@Component`, 像我们的`@Service`,`@Controller`,`@Repository`都是`@Component`派生出来的注解,所以这些派生注解都能被扫描到,另外还注册了两个注解`@ManagedBean`和`@Named`注解的类。如果某些注解的库(如JSR-250或JSR-330)不在类路径上,扫描器会安全地跳过它们,不会导致任何错误。 + +```java +protected void registerDefaultFilters() { + // 添加一个默认的类型过滤器,用于检测带有@Component注解的类 + this.includeFilters.add(new AnnotationTypeFilter(Component.class)); + + // 获取当前类的类加载器 + ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); + + // 尝试为JSR-250的ManagedBean注解添加一个类型过滤器 + try { + this.includeFilters.add(new AnnotationTypeFilter( + ((Class) 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的ManagedBean类不在类路径上,只需简单跳过。 + } + + // 尝试为JSR-330的Named注解添加一个类型过滤器 + try { + this.includeFilters.add(new AnnotationTypeFilter( + ((Class) 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的Named类不在类路径上,只需简单跳过。 + } +} +``` + +我们回到`ComponentScanAnnotationParser`类中的`parse(componentScan,declaringClass)`方法来,在这个方法的最后会调用`ClassPathBeanDefinitionScanner`类中`doScan(basePackages)`方法中对于每个包名,它都会查找候选组件。这些候选组件基本上是那些可能需要被Spring管理的类,例如带有@Component、@Service、@Repository或@Controller注解的类。 + +```java +/** + * 扫描指定的基础包,并为找到的类注册bean定义。 + * + * @param basePackages 需要扫描的基础包名称。 + * @return 找到并注册的bean定义集合。 + */ +protected Set doScan(String... basePackages) { + // 确保提供的包名不为空 + Assert.notEmpty(basePackages, "At least one base package must be specified"); + + Set beanDefinitions = new LinkedHashSet<>(); + + for (String basePackage : basePackages) { + // 对给定的包进行扫描,查找候选组件 + Set candidates = findCandidateComponents(basePackage); + + for (BeanDefinition candidate : candidates) { + // 确定bean的作用域(如:singleton, prototype等) + ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); + candidate.setScope(scopeMetadata.getScopeName()); + + // 为bean定义生成一个唯一的名称 + String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); + + // 对AbstractBeanDefinition类型的bean定义进行后处理 + if (candidate instanceof AbstractBeanDefinition) { + postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); + } + + // 对带注解的bean定义进行处理,例如处理@Autowired、@Value等注解 + if (candidate instanceof AnnotatedBeanDefinition) { + AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); + } + + // 检查当前bean的名称是否已在注册表中使用 + if (checkCandidate(beanName, candidate)) { + BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); + // 如果bean具有特定的作用域(例如会话作用域),可能需要应用一个代理 + definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); + + beanDefinitions.add(definitionHolder); + + // 在bean定义注册表中注册bean定义 + registerBeanDefinition(definitionHolder, this.registry); + } + } + } + return beanDefinitions; +} +``` + +在看看`doScan`方法中调用的`findCandidateComponents`方法,这个方法首先对`spring.components`文件提供的配置进行扫描,目的是可以提高扫描速度,有兴趣的朋友可以去研究一下`@Component`注解中的`@Indexed`注解。我们本次测试是没有使用到`spring.components`文件加速,所以我们继续查看`scanCandidateComponents(basePackage)`方法。 + +```java +public Set findCandidateComponents(String basePackage) { + // 如果存在一个预建的组件索引(例如,通过spring.components文件),并且该索引支持当前配置的过滤条件 + if (this.componentsIndex != null && indexSupportsIncludeFilters()) { + // 使用该索引来查找符合条件的组件,这通常更快 + return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); + } + // 如果没有预建索引或索引不支持当前的过滤条件,则进行传统的运行时扫描 + else { + // 扫描basePackage下的所有类,查找标记为Spring组件的类 + return scanCandidateComponents(basePackage); + } +} +``` + +从指定的包路径`basePackage`中扫描并识别Spring管理的组件,并将它们返回为一组`BeanDefinition`。方法首先构建了一个搜索路径(`classpath*:com/xcs/spring/**/*.class`),用于在classpath中查找所有相关的资源。然后遍历每个找到的资源,尝试读取其元数据,并根据元数据判断该资源是否是一个有效的Spring组件。符合条件的组件被转化为`BeanDefinition`对象并添加到结果集中。 + +```java +private Set scanCandidateComponents(String basePackage) { + // 初始化一个用于保存候选Bean定义的集合 + Set 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 = getMetadataReaderFactory().getMetadataReader(resource); + + // 判断资源是否是一个候选组件 + if (isCandidateComponent(metadataReader)) { + // 创建一个ScannedGenericBeanDefinition对象,并设置其资源 + ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); + 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); + } + } + } + } + // 处理I/O异常 + catch (IOException ex) { + throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); + } + + // 返回识别出的Bean定义 + return candidates; +} +``` + +`isCandidateComponent`方法根据指定的排除和包含过滤器,判断一个类是否符合作为Spring组件的候选条件 + +```java +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())) { + // 则进一步检查它是否满足其他条件,例如@Condition注解 + return isConditionMatch(metadataReader); + } + } + + // 如果既没有匹配到排除过滤器也没有匹配到包含过滤器,则返回false + return false; +} +``` + +我们回到`ClassPathBeanDefinitionScanner`类中`doScan(basePackages)`方法方法来,在`doScan`中调用了`checkCandidate`方法,该`checkCandidate`方法验证新的`beanDefinition`在`beanDefinitionMap`内现有的是否冲突,并在冲突时抛出异常,确保bean名称的唯一性和兼容性。 + +```java +protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { + // 检查注册中心是否已经包含该bean名称的定义 + if (!this.registry.containsBeanDefinition(beanName)) { + // 若不包含,则为有效候选 + return true; + } + + // 获取当前注册中心已存在的bean定义 + BeanDefinition existingDef = this.registry.getBeanDefinition(beanName); + + // 检查是否存在原始的Bean定义,若存在,则使用原始的定义 + BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition(); + if (originatingDef != null) { + existingDef = originatingDef; + } + + // 检查新的bean定义与现有定义是否兼容 + if (isCompatible(beanDefinition, existingDef)) { + return false; + } + + // 如果新的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() + "]"); +} +``` + +`registerBeanDefinition`方法又委托了`BeanDefinitionReaderUtils.registerBeanDefinition`去执行 + +```java +protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { + BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); +} +``` + +最后我们扫描出来的`BeanDefinition`都通过`BeanDefinitionRegistry`注册的Spring上下文中(`AnnotationConfigApplicationContext`) + +```java +public static void registerBeanDefinition( + BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + // 获取BeanDefinitionHolder中的bean名称 + String beanName = definitionHolder.getBeanName(); + // 使用bean名称将BeanDefinition注册到BeanDefinitionRegistry中 + registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); + + // 获取BeanDefinitionHolder中的所有别名 + String[] aliases = definitionHolder.getAliases(); + if (aliases != null) { + // 循环每个别名并将其注册到BeanDefinitionRegistry中,与主bean名称关联 + for (String alias : aliases) { + registry.registerAlias(beanName, alias); + } + } +} +``` + +下面是调用时序图 + + + +~~~mermaid +sequenceDiagram +title:@ComponentScan注解源码时序图 +ComponentScanApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses) +AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh() +AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory) +AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors) +PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup) +PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry) +ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry) +ConfigurationClassPostProcessor->>ConfigurationClassParser:ConfigurationClassParser(...) +ConfigurationClassParser-->>ConfigurationClassPostProcessor:返回解析解析器 +ConfigurationClassPostProcessor->>ConfigurationClassParser:parser.parse(candidates) +ConfigurationClassParser->>ConfigurationClassParser:parse(metadata, String beanName) +ConfigurationClassParser->>ConfigurationClassParser:processConfigurationClass(configClass,filter) +ConfigurationClassParser->>ConfigurationClassParser:doProcessConfigurationClass(configClass,sourceClass,filter) +ConfigurationClassParser->>ComponentScanAnnotationParser:parse(componentScan,declaringClass) +ComponentScanAnnotationParser->>ClassPathBeanDefinitionScanner:ClassPathBeanDefinitionScanner(registry,useDefaultFilters,environment,resourceLoader) +ClassPathBeanDefinitionScanner->>ClassPathBeanDefinitionScanner:registerDefaultFilters() +ClassPathBeanDefinitionScanner-->>ComponentScanAnnotationParser:返回扫描器 +ComponentScanAnnotationParser->>ClassPathBeanDefinitionScanner:doScan(basePackages) +ClassPathBeanDefinitionScanner->>ClassPathScanningCandidateComponentProvider:findCandidateComponents(basePackage) +ClassPathScanningCandidateComponentProvider->>ClassPathScanningCandidateComponentProvider:scanCandidateComponents(basePackage) +ClassPathScanningCandidateComponentProvider-->>ClassPathBeanDefinitionScanner:返回BeanDefinition +ClassPathBeanDefinitionScanner->>ClassPathBeanDefinitionScanner:registerBeanDefinition(definitionHolder,registry) +ClassPathBeanDefinitionScanner->>BeanDefinitionReaderUtils:registerBeanDefinition(definitionHolder, registry) +BeanDefinitionReaderUtils->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition) +ClassPathBeanDefinitionScanner-->>ComponentScanAnnotationParser:返回BeanDefinition +~~~ + + + +### 6、常见问题 + +以下示例只是为了说明可能的问题,并不代表实际的错误代码。我们大家在实际开发中,应确保清楚了解每个注解的含义和效果。 + +#### 6.1、默认的扫描路径 + +如果不指定`basePackages`或`basePackageClasses`,`@ComponentScan`默认会从声明这个注解的类的包开始扫描。如果配置类不在根包路径下,可能会错过某些组件。 + +```java +package com.xcs.spring.config; + +@Configuration +// 默认会从此类所在的包开始扫描 (默认扫描路径为:com.xcs.spring.config) +// 如果你有一个Service类需要扫描,路径为:com.xcs.spring.service.MyService1此时会错过这个组件扫描 +@ComponentScan +public class AppConfig { + +} +``` + +#### 6.2、过度扫描 + +如果`basePackages`设置得过于宽泛,可能会无意中扫描到许多不必要的bean,导致启动时间增加和不必要的bean实例化。 + +```java +@Configuration +// 这将扫描com包下的所有子包,可能会扫描到很多不必要的组件 +// com下可能有成千上万的类,导致启动缓慢 +// 如果项目结构复杂,类很多,`@ComponentScan`可能导致启动速度减慢,因为它需要扫描大量的类。 +@ComponentScan(basePackages = "com") +public class AppConfig { + +} +``` + +#### 6.3、过滤问题 + + `includeFilters`和`excludeFilters`的不恰当设置可能导致意外地包含或排除某些组件。 + +```java +@Configuration +// 这里可能误包含了某些@Service组件或误排除了某些@Repository组件 +@ComponentScan(basePackages = "com.xcs", + includeFilters = @Filter(type = FilterType.ANNOTATION, classes = Service.class), + excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Repository.class)) +public class AppConfig { + +} +``` + +#### 6.4、重复的bean定义 + + 不同包中的类可能有相同的简单名称。默认情况下,bean名称是基于简单类名的,这可能导致bean名称冲突。 + +```java +// 在com.xcs.a包中 +@Component +public class MyService { +} + +// 在com.xcs.b包中 +@Component +public class MyService { + // com.xcs.a包中的MyService同名 +} +``` + +#### 6.5、与其他配置混淆 + +当使用`@ComponentScan`与XML配置或其他Java配置结合时,可能会有意外的bean定义覆盖。 + +在下述示例中,由于`MyServiceImpl`使用了`@Service`注解,并指定了相同的bean名称`myService`,因此`@ComponentScan`会扫描并注册这个bean。但在XML配置中,也为相同的bean名称提供了一个定义。 + +当Spring IoC容器初始化并处理bean定义时,由于两个定义具有相同的bean名称,因此后处理的定义(取决于加载顺序)可能会覆盖先处理的定义。这可能会导致意外的行为,例如使用了错误的bean属性或bean类。 + +为了避免此类问题,需要确保在整个应用程序中为每个bean提供一个唯一的名称,并明确知道哪个配置源负责定义哪个特定的bean。如果确实需要在多个配置源中定义同一个bean,最好明确地指定哪个定义应该优先。 + +```java +@Configuration +@ComponentScan(basePackages = "com.xcs.spring") +public class AppConfig { +} + +@Service("myService") +public class MyServiceImpl implements MyService { + private String message = "Hello from @Service!"; + + // getter and setter +} + +// 在XML中 + + + + + + +``` + +#### 6.6、条件扫描的误区 + +当使用`@Conditional`注解与`@ComponentScan`结合时,要确保条件正确配置,否则可能导致组件不被扫描。 + +例如,你可能想在开发环境中注册一个bean,但在生产环境中不这么做。或者,如果数据库类型是H2,你可能想注册一个bean,但如果是MySQL,则不这么做。 + +```java +@Component +// DevComponent bean只有在OnDevelopmentCondition条件满足时才会被Spring容器管理。 +@Conditional(OnDevelopmentCondition.class) +public class DevComponent { + // ... +} +``` + +```java +public class OnDevelopmentCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return "dev".equals(context.getEnvironment().getProperty("spring.profiles.active")); + } +} +``` + +在这个条件实现中,我们检查当前激活的Spring配置文件是否为"dev"。 + +如果你在使用`@ComponentScan`的时候忘记正确配置这样的条件或者误配置,可能会遇到以下情况: + +1. **预期的bean没有被注册**:如果你期望在某些条件下`DevComponent`被注册,但条件没有满足(例如,配置文件不正确或环境属性设置错误),则该bean将不会被扫描和注册。 +2. **不希望的bean被注册**:如果条件误配置并错误地满足了,可能会导致本不应该在当前环境中创建的bean被注册。 +3. **应用程序启动失败**:如果其他bean依赖于基于条件创建的bean,并且条件没有满足,那么应用程序在启动时可能会因为找不到依赖的bean而失败。 + +所以我们大家在使用`@Conditional`与`@ComponentScan`结合时,非常重要的是要确保你的条件是正确配置的,以确保spring程序中的bean正确的被加载。 + +#### 6.7、第三方库的扫描 + +不小心扫描了第三方库,可能会导致不必要的bean被注册或者出现版本冲突。 + +```java +@Configuration +// 可能导致第三方库不必要的bean注册 +@ComponentScan(basePackages = {"com.xcs.spring", "com.alibaba.dubbo"}) +public class AppConfig { + +} +``` + +#### 6.8、忽视了`useDefaultFilters` + + 有时我们使用了`includeFilters`包含过滤器,但是可能忘记将`useDefaultFilters`设置为`false`,导致除了自定义过滤器外,还应用了默认的过滤器。 + +```java +@Configuration +@ComponentScan(basePackages = "com.xcs.spring", + useDefaultFilters = true, // 使用了默认过滤器 + includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyAnnotation.class)) +// 既使用了默认过滤器又使用了自定义过滤器 +public class AppConfig { + +} +``` + +因为`useDefaultFilters`默认值为`true`,Spring将继续按默认的注解(`@Component`, `@Service`, `@Repository`, `@Controller`等)进行组件扫描,即使你可能只想按自定义的`includeFilters`进行扫描 + +### 7、总结 + +在你阅读完本次`@ComponentScan`注解的源码分析后,我们来大概做一下总结,在第一部分中首先是我们介绍了`@ComponentScan`注解说明。在第二部分中我们从spring源码总摘取了`@ComponentScan`注解的源码,从中了解了这个注解中有许多的字段。在第三部分中我们我们介绍了`@ComponentScan`注解的字段,比如说你要配置扫描包路径有`value`,`basePackages`,`basePackageClasses`三个字段都可以配置。又比如说你想设置Bean名称生成策略,那么你可以使用到`nameGenerator`字段,然后注解中有一个非常重要字段就是`useDefaultFilters`,当它的值被设置成true是会扫描到 @Component、@Repository、@Service、@Controller 注解的组件,最后是`includeFilters`,`excludeFilters`两个做过滤的字段。在第四部分中我们介绍了`@ComponentScan`注解如何使用,如果你忘记了可以回头在仔细看看上面的介绍。来到第五部分是我们的源码分析,在源码分析过程中我们发现`@ComponentScan`本身定义了多个属性,如 `basePackages`、`basePackageClasses` 和多种过滤器 `(includeFilters/excludeFilters`),当 Spring 容器启动并读取到配置类标记有 `@ComponentScan`时,会被我们的核心类`ConfigurationClassParser` 被用来解析配置类。在此过程中,会处理该注解,并确定要扫描的包路径。实际的扫描由 `ClassPathBeanDefinitionScanner` 完成。这个扫描器读取 `@ComponentScan` 的属性,并在指定的包路径下搜索候选的组件,扫描器在搜索组件时会使用 `includeFilters` 和 `excludeFilters`,这两个过滤器列表确定哪些组件应被包括和哪些应被排除,扫描完毕后,找到的组件会被解析并注册到 IOC容器中,从而允许后续的 bean 生命周期处理和自动装配功能。第六部分主要是介绍了一些常见问题。好啦,本次分析到此结束 \ No newline at end of file diff --git a/spring-annotation-componentScan/README.md b/spring-annotation-componentScan/README.md index 50964d8..24c5585 100644 --- a/spring-annotation-componentScan/README.md +++ b/spring-annotation-componentScan/README.md @@ -1,1691 +1,286 @@ ## @ComponentScan -[TOC] +- [@ComponentScan](#componentscan) + - [一、接口描述](#一接口描述) + - [二、注解源码](#二注解源码) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、时序图](#五时序图) + - [六、源码分析](#六源码分析) + - [七、注意事项](#七注意事项) + - [八、总结](#八总结) + - [8.1、最佳实践总结](#81最佳实践总结) + - [8.2、源码分析总结](#82源码分析总结) -### 1、注解说明 -`@ComponentScan`是Spring的核心注解,用于自动扫描和注册指定包下的组件。我们可以指定包路径、自定义组件过滤规则,并决定是否启用默认过滤器。该注解还支持懒加载、自定义命名策略、作用域解析,以及为特定生命周期的beans指定代理模式。默认情况下,它会扫描带有`@Component`、`@Service`、`@Repository`和`@Controller`的类,但可以通过`useDefaultFilters`属性调整。此外,从Spring 3.1开始,这个注解支持重复性,允许在单个配置类上多次使用,每次具有不同的属性设置。 +### 一、接口描述 -### 2、注解源码 +`@ComponentScan` 注解,用于自动扫描特定包(和其子包)中的组件,并自动注册为 Spring 容器中的 bean。当我们使用 Spring Boot,它默认会扫描主应用程序所在的包以及子包。但是,如果你需要更细粒度的控制,或者我们在使用传统的 Spring 而非 Spring Boot,那么我们可能会明确地使用 `@ComponentScan`。 + +### 二、注解源码 + +`@ComponentScan`注解是 Spring 框架自 3.1 版本开始引入的一个核心注解,用于指导如何扫描组件。与 `@Configuration` 配合使用,其功能与 Spring XML 的 `` 类似。除了允许指定要扫描的包,它还提供了多种属性,如命名生成器、范围解析器、代理设置等,以精细地控制组件的扫描和注册过程。若不指定扫描包,它默认从注解声明的位置开始。与此同时,`@Filter` 注解定义了类型过滤器,特别用于 `@ComponentScan` 中的组件包含和排除设置。它允许基于特定类型、类或模式来筛选组件。 ```java +/** + * 配置 @Configuration 类使用的组件扫描指令。 + * 提供与 Spring XML 的 元素相似的支持。 + * + * 可以指定 #basePackageClasses 或 #basePackages (或其别名 + * #value }) 来定义要扫描的特定包。如果没有定义特定的包, + * 则从声明此注解的类的包开始扫描。 + * + * 注意, 元素有一个 + * 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 { - - @AliasFor("basePackages") - String[] value() default {}; - - @AliasFor("value") - String[] basePackages() default {}; - - Class[] basePackageClasses() default {}; - - Class nameGenerator() default BeanNameGenerator.class; - - Class scopeResolver() default AnnotationScopeMetadataResolver.class; - - ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; - - String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; - - boolean useDefaultFilters() default true; - - Filter[] includeFilters() default {}; - - Filter[] excludeFilters() default {}; - - boolean lazyInit() default false; - - @Retention(RetentionPolicy.RUNTIME) - @Target({}) - @interface Filter { - - FilterType type() default FilterType.ANNOTATION; - - @AliasFor("classes") - Class[] value() default {}; - - @AliasFor("value") - Class[] classes() default {}; - - String[] pattern() default {}; - } -} -``` - -### 3、字段描述 - -#### 3.1 value - -别名为 basePackages。这指定了一个或多个要扫描的基础包。扫描器将在这些包及其子包中搜索符合条件的组件。 - -#### 3.2 basePackages - -与 value 属性功能相同,指定一个或多个要扫描的基础包。 - -#### 3.3 basePackageClasses - -通过指定类来定义要扫描的包。提供的类本身不必标记为一个组件,它们仅仅用于确定要扫描的基础包。 - -#### 3.4 nameGenerator - -提供一个Bean名称生成策略,用于为在组件扫描期间注册的bean定义生成名称。 - -#### 3.5 scopeResolver - -定义一个范围元数据解析策略,它为在组件扫描期间注册的bean定义提供范围信息。 - -#### 3.6 scopedProxy - -定义如何处理scoped beans的代理。这可以是创建一个接口代理还是使用CGLIB创建一个类的代理。 - -`ScopedProxyMode`有以下取值: - -1. **DEFAULT**: 默认设置,这意味着必须通过其他方式进行配置,例如全局配置。如果未在其他地方明确配置,则不会创建代理。 -2. **NO**: 表示不应为bean创建代理。在这种模式下,当你尝试注入短作用域的bean到更长作用域的bean时,会出现问题,因为你每次获得的都是同一个实例,而不是新的短作用域实例。 -3. **TARGET_CLASS**: 这意味着应为目标bean创建CGLIB代理。这种模式是完全基于类的,不需要接口。当目标bean被代理时,它会成为子类的实例。这对于那些没有实现接口或需要代理具体的类非常有用。 -4. **INTERFACES**: 代理必须通过接口实现。当使用此模式时,bean必须实现至少一个接口,代理将实现这些接口。如果bean没有实现任何接口,此模式将抛出异常。 - -#### 3.7 resourcePattern - -定义要扫描的资源的模式。默认情况下,只有“.class”文件会被扫描。 - -#### 3.8 useDefaultFilters - -是否应使用默认过滤器来检测例如 @Component、@Repository、@Service、@Controller 注解的组件。 - -#### 3.9 includeFilters - -为扫描指定自定义的包括过滤器,即哪些组件应该被Spring上下文包括。 - -#### 3.10 excludeFilters - -为扫描指定自定义的排除过滤器,即哪些组件应该被Spring上下文排除。 - -#### 3.11 lazyInit - -如果为true,则所有通过此扫描注册的组件都将默认为延迟初始化。这意味着它们不会被实例化,除非显式地请求或在注入点被引用。 - -#### 3.12 Filter - -Filter 定义了在组件扫描过程中使用的过滤器,可以基于注解、正则表达式等进行过滤。 - -###### 3.12.1 type - -指定过滤器的类型。默认情况下,它基于注解进行过滤。 - -###### 3.12.2 value - -指定过滤器要搜索的注解或类型。 - -###### 3.12.3 classes - -同 value,指定过滤器要搜索的注解或类型。 - -###### 3.12.4 pattern - -如果 type 被设置为 FilterType.REGEX 或 FilterType.ASPECTJ,则使用该属性指定模式。 - -### 4、如何使用 - -#### 4.1、使用basePackages或者value配置扫描包路径 - -使用basePackages或者value是我们最最最常见的一种配置扫描包的方式了,主要包含默认扫描包与指定扫描包2种方式。 - -**默认包扫描** - -当我们在一个配置类上简单地使用 `@ComponentScan` 无任何参数时,Spring 会扫描该配置类所在的包及其子包下的所有组件和服务。 - -```java -@Configuration -@ComponentScan -public class MyConfiguration { -} -``` + /** + * #basePackages 的别名。 + * 如果不需要其他属性,则允许更简洁的注解声明,例如,@ComponentScan("org.my.pkg") + * 而不是 @ComponentScan(basePackages = "org.my.pkg")。 + */ + @AliasFor("basePackages") + String[] value() default {}; -**指定包扫描** + /** + * 扫描带注解的组件的基础包。 + * #value 是此属性的别名(且与此属性互斥)。 + * 使用 #basePackageClasses 作为基于类型安全的替代方法 + * 来指定要扫描注解的组件的包。将扫描每个指定类的包。 + */ + @AliasFor("value") + String[] basePackages() default {}; -使用 `basePackages` 属性来指定扫描的包路径。 - -```java -@Configuration -@ComponentScan(basePackages = "com.xcs.spring") -public class MyConfiguration { + /** + * 指定要扫描的包的类型安全替代方法。每个指定类的包都会被扫描。 + * 考虑在每个包中创建一个特殊的无操作标记类或接口, + * 除了被此属性引用之外,没有其他用途。 + */ + Class[] basePackageClasses() default {}; + /** + * 在Spring容器内为检测到的组件命名的 BeanNameGenerator 类。 + * BeanNameGenerator 接口的默认值表明处理此 @ComponentScan 注解的扫描器 + * 应使用它的继承的bean命名生成器,例如默认的 + * AnnotationBeanNameGenerator 或在启动时提供给应用上下文的任何自定义实例。 + */ + Class nameGenerator() default BeanNameGenerator.class; + + /** + * 用于解析检测到的组件范围的 ScopeMetadataResolver。 + */ + Class 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 {}; + + } } + ``` -或者使用 `value` 属性,它是 `basePackages` 的别名 - -```java -@Configuration -@ComponentScan("com.xcs.spring") -public class MyConfiguration { - -} -``` - -#### 4.2、使用basePackageClasses配置扫描包路径 - -`basePackageClasses` 允许开发者指定一个或多个类,Spring 会扫描这些类所在的包以及其子包中的所有类,以查找带有 `@Component`, `@Service`, `@Repository`, `@Controller` 以及其他相关注解的类,并将它们注册到 Spring 应用上下文中。 - -**示例**: - -假设你有以下的包结构: - -```java -com.xcs.spring - ├── config - │ └── MyConfiguration.java - ├── controller - │ ├── MyController1.java - │ └── MyController2.java - └── service - ├── MyService1.java - └── MyService2.java - └── MyPackageMarker.java -``` - -如果你想从 `com.xcs.spring` 开始扫描(包括所有子包),你可以在 `com.xcs.spring` 下创建一个标记类,`MyPackageMarker`类并不是为了执行某种实际功能而定义的,而是仅仅作为一个参考或者标记。它通常位于你想要扫描的包的根目录 - -**例如:** - -```java -package com.xcs.spring; - -public class MyPackageMarker { - -} -``` - -然后,在你的配置类中使用: - -```java -@Configuration -@ComponentScan(basePackageClasses = MyPackageMarker.class) -public class MyConfiguration { - -} -``` - -**使用basePackageClasses字段比basePackages或者value有何优势呢?** - -使用 `basePackageClasses` 比使用 `basePackages` 优雅,因为它避免了硬编码字符串包名。如果你重构代码并更改包名,那么使用 `basePackageClasses` 的方法会更安全,因为IDE会自动更新类的引用。而硬编码的字符串包名在重构时可能会被遗漏。 - -#### 4.3、使用nameGenerator自定义Bean的名称 - -`nameGenerator` 允许我们为Spring的Bean指定一个 `BeanNameGenerator` 实现,该实现将为组件生成bean的名字。 - -**用法:** - -如果你要使用自定义的 `BeanNameGenerator`,只需实现 `BeanNameGenerator` 接口,并在 `@ComponentScan` 中指定该类即可。 - -```java -@Configuration -@ComponentScan(nameGenerator = MyCustomBeanNameGenerator.class) -public class MyConfiguration { - -} -``` - -我们使用自定义的 `MyCustomBeanNameGenerator`,只需实现 `BeanNameGenerator` 接口中的generateBeanName方法,bean的名称根据自己的实际情况生成 - -```java -public class MyCustomBeanNameGenerator implements BeanNameGenerator { - - @Override - public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { - // 此处只是一个示例,我们可以根据自己的实际情况生成Bean名称 - String originalName = definition.getBeanClassName(); - if (originalName != null) { - return "_这是我自定义Bean名称的前缀_" + originalName; - } else { - // 你可以选择其他的逻辑处理或者抛出异常 - throw new IllegalArgumentException("Bean class name is null"); - } - } -} -``` - -#### 4.4、使用scopeResolver自定义Bean的作用域 - -`scopeResolver`属性允许您指定一个自定义的`ScopeMetadataResolver`,它解析bean的作用域元数据。通过这种方式,你可以控制和自定义bean的作用域策略。 - -**用法:** - -如果要使用自定义的`ScopeMetadataResolver`,需要实现`ScopeMetadataResolver`接口并在`@ComponentScan`中指定这个类。 - -```java -@Configuration -@ComponentScan(scopeResolver = MyCustomScopeMetadataResolver.class) -public class MyConfiguration { - -} -``` - -我们使用自定义的 `MyCustomScopeMetadataResolver`,只需实现 `ScopeMetadataResolver` 接口中的resolveScopeMetadata方法,bean的作用于根据自己的实际情况生成。我们此处给`MyService1`与`MyService2`设置成了多例模式,否则我们重新交由`AnnotationScopeMetadataResolver`类解析,如果不由`AnnotationScopeMetadataResolver`类处理的话会导致@Scope注解失效 - -```java -public class MyCustomScopeMetadataResolver implements ScopeMetadataResolver { - - private AnnotationScopeMetadataResolver resolver = new AnnotationScopeMetadataResolver(); - - @Override - public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { - ScopeMetadata scopeMetadata = new ScopeMetadata(); - - // 检查Bean的名称是否为'MyService1'或'MyService2',并据此设置相应的作用域。 - if (MyService1.class.getName().equals(definition.getBeanClassName()) || - MyService2.class.getName().equals(definition.getBeanClassName())) { - scopeMetadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); - return scopeMetadata; - } - // 再次交由AnnotationScopeMetadataResolver解析否则会导致@Scope失效 - return resolver.resolveScopeMetadata(definition); - } -} -``` - -**注意:** - - `scopedProxy` 和 `scopeResolver` 当两者都被设置时,`scopedProxy` 的优先级更高,并且 `scopeResolver` 会被忽略 - -#### 4.5、使用`includeFilters `定义哪些组件应该被包含 - -**基于ANNOTATION过滤** - -只有使用`@Service`注解的类会被包含 - -```java -@Service -public class MyService1 { - -} - -@Service -public class MyService2 { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class), useDefaultFilters = false) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中有被`@Service`注解标注的类,被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`ANNOTATION`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myService1 -beanName = myService2 -``` - -**基于ASSIGNABLE_TYPE过滤** - -只有实现`MyInterface`接口的组件会被包含 - -```java -public interface MyInterface { - -} - -@Component -public class MyComponent implements MyInterface { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyInterface.class), useDefaultFilters = false) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中有`MyComponent`类,因为`MyComponent`实现了`MyInterface`接口,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`ASSIGNABLE_TYPE`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponent -``` - -**基于CUSTOM过滤** - -类名以“Custom”结尾的组件会被包含 - -```java -public class MyCustomTypeFilter implements TypeFilter { - @Override - public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { - return metadataReader.getClassMetadata().getClassName().endsWith("Custom"); - } -} - -@Component -public class MyComponentCustom { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyCustomTypeFilter.class), useDefaultFilters = false) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中有`MyComponentCustom`类,因为`MyComponentCustom`类符合以`“Custom”`结尾的组件,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`CUSTOM`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponentCustom -``` - -**基于REGEX过滤** - -类名匹配正则表达式`.*ComponentRegex`的组件会被包含。 - -```java -@Component -public class MyComponentRegex { - -} - -@Configuration -@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*ComponentRegex"), useDefaultFilters = false) -public class MyConfiguration { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中有`myComponentRegex`类,因为`MyComponentRegex`类符合以表达式`.*ComponentRegex`的组件,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`REGEX`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponentRegex -``` - -**基于AspectJ过滤** - -类名匹配AspectJ类型模式`*..*AspectJ`的组件会被包含。 - -```java -@Component -public class MyComponentAspectJ { - -} - -@Configuration -@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..*AspectJ"), useDefaultFilters = false) -public class MyConfiguration { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中有`myComponentAspectJ`类,因为`MyComponentRegex`类符合以AspectJ类型模式`*..*AspectJ`的组件,而其他被`@Controller`,`@Component`等都不在容器中说明基于`includeFilters`字段中的`AspectJ`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponentAspectJ -``` - -**为什么网上很多博主说设置了includeFilters包含规则后,必须要把useDefaultFilters字段设置成false呢?为什么要这样做?** - -想象一下,既然你都使用了includeFilters字段,说明你只想在组件扫描中包括那些具有自定义注解的bean,而忽略所有其他的bean。假如说你设置了includeFilters字段,然后`useDefaultFilters`没有设置为`false`,这意味着具有`@Component`, `@Repository`, `@Service`, 和`@Controller`等注解的类会被自动检测并注册为beans,可能会出现重复扫描,可能会出现混淆,违背了我们使用includeFilters做过滤的初衷。因此,通常建议明确地设置`useDefaultFilters`的值,以避免任何混淆和不期望的行为。如果你仅想使用`includeFilters`,最好将`useDefaultFilters`设置为`false`。如果你想使用默认的过滤器和自定义的过滤器,请明确地设置`useDefaultFilters`为`true`。 - -#### 4.6、使用`excludeFilters`定义哪些组件应该被排除 - -**基于ANNOTATION过滤** - -所有使用`@Service`注解的类将不会被注册。 - -```java -@Service -public class MyService1 { - -} - -@Service -public class MyService2 { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中没有被`@Service`标注的类,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`ANNOTATION`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponent -beanName = myComponentAspectJ -beanName = myComponentCustom -beanName = myComponentRegex -beanName = myController1 -beanName = myController2 -``` - -**基于ASSIGNABLE_TYPE过滤** - -实现`ExcludedInterface`接口的组件都不会被注册。 - -```java -public interface MyInterface { - -} - -@Component -public class MyComponent implements MyInterface { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyInterface.class)) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中没有`MyComponent`类对象,因为`MyComponent`类实现了`MyInterface`所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`ASSIGNABLE_TYPE`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponentAspectJ -beanName = myComponentCustom -beanName = myComponentRegex -beanName = myController1 -beanName = myController2 -beanName = myService1 -beanName = myService2 -``` - -**基于CUSTOM过滤** - -类名以`"Custom"`结尾的组件都不会被注册。 - -```java -public class MyCustomTypeFilter implements TypeFilter { - @Override - public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { - return metadataReader.getClassMetadata().getClassName().endsWith("Custom"); - } -} - -@Component -public class MyComponentCustom { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyCustomTypeFilter.class)) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中没有`MyComponentCustom`类对象,因为`MyComponentCustom`类名以`"Custom"`结尾的组件,所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`CUSTOM`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponent -beanName = myComponentAspectJ -beanName = myComponentRegex -beanName = myController1 -beanName = myController2 -beanName = myService1 -beanName = myService2 -``` - -**基于REGEX过滤** - -类名匹配正则表达式`.*ComponentRegex`的组件不会被注册。 - -```java -@Component -public class MyComponentRegex { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*ComponentRegex")) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中没有`MyComponentRegex`类对象,因为`MyComponentRegex`类名符合表达式`.*ComponentRegex`的组件,所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`REGEX`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponent -beanName = myComponentAspectJ -beanName = myComponentCustom -beanName = myController1 -beanName = myController2 -beanName = myService1 -beanName = myService2 -``` - -**基于AspectJ过滤** - -类名匹配AspectJ类型模式`*..*AspectJ`的组件不会被注册。 - -```java -@Component -public class MyComponentAspectJ { - -} - -// 还有其他被@Controller,@Component标注类再此不展示了。。。 - -@Configuration -@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..*AspectJ")) -public class MyComponentScanConfig { - -} -``` - -通过main方法运行查看结果 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -通过运行结果发现,在Bean容器中没有`MyComponentAspectJ`类对象,因为`MyComponentAspectJ`类名符合AspectJ类型模式`*..*AspectJ`的组件,所以被排除了,而其他被`@Controller`,`@Component`等都在容器中说明基于`excludeFilters`字段中的`AspectJ`类型过滤已经生效 - -```java -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 = myComponentScanConfig -beanName = myComponent -beanName = myComponentCustom -beanName = myComponentRegex -beanName = myController1 -beanName = myController2 -beanName = myService1 -beanName = myService2 -``` - -### 5、源码分析 - -我们的`ComponentScanApplication`类是main方法入口,那我们就从这里开始跟踪源码,我们使用的是`AnnotationConfigApplicationContext`做为上下文环境,并传入了一个组件类的类名,那么我们继续进入`AnnotationConfigApplicationContext`的构造函数查看源码 - -```java -public class ComponentScanApplication { - public static void main(String[] args) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponentScanConfig.class); - for (String beanDefinitionName : context.getBeanDefinitionNames()) { - System.out.println("beanName = " + beanDefinitionName); - } - } -} -``` - -首先我们来看看源码中的,构造函数中,执行了三个步骤,我们重点关注refresh()方法 - -```java -public AnnotationConfigApplicationContext(Class... componentClasses) { - this(); - register(componentClasses); - refresh(); -} -``` - -`refresh()`方法中我们重点关注一下`invokeBeanFactoryPostProcessors(beanFactory)`这方法,其他方法不是本次源码阅读的重点暂时忽略,在`invokeBeanFactoryPostProcessors(beanFactory)`方法会对实现了`BeanDefinitionRegistryPostProcessor`,`BeanFactoryPostProcessor`这两个接口进行接口回调。 - -```java -public void refresh() throws BeansException, IllegalStateException { - // ----------------------忽略其他代码--------------------------- - // Invoke factory processors registered as beans in the context. - invokeBeanFactoryPostProcessors(beanFactory); - // ----------------------忽略其他代码--------------------------- -} -``` - -在`invokeBeanFactoryPostProcessors()`中又委托了`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`进行调用 - -```java -protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { - PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); - // ----------------------忽略其他代码--------------------------- -} -``` - -在这个`invokeBeanFactoryPostProcessors(beanFactory, beanFactoryPostProcessors)`方法中,主要是对`BeanDefinitionRegistryPostProcessor`,`BeanFactoryPostProcessor`这两个接口的实现类进行回调,至于为什么这个方法里面代码很长呢?其实这个方法就做了一个事就是对处理器的执行顺序在做出来。比如说要先对实现了`PriorityOrdered.class`类回调,在对实现了`Ordered.class`类回调,最后才是对没有实现任何优先级的处理器进行回调。 - -```java -public static void invokeBeanFactoryPostProcessors( - ConfigurableListableBeanFactory beanFactory, - List beanFactoryPostProcessors) { - - // ----------------------忽略其他代码--------------------------- - - // 存储已经处理过的bean - Set processedBeans = new HashSet<>(); - - // 创建一个新的BeanDefinitionRegistryPostProcessor列表来存放当前正在处理的后处理器。 - List currentRegistryProcessors = new ArrayList<>(); - - // 获取所有已注册且为BeanDefinitionRegistryPostProcessor类型的bean的名称。 - String[] postProcessorNames = - beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); - - // 遍历获取到的BeanDefinitionRegistryPostProcessor的名字 - for (String ppName : postProcessorNames) { - // 筛选出那些还实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor - if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { - // 将筛选出的后处理器实例添加到currentRegistryProcessors列表中 - currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); - processedBeans.add(ppName); - } - } - - // 使用sortPostProcessors方法,根据它们的优先级对BeanDefinitionRegistryPostProcessor进行排序。 - sortPostProcessors(currentRegistryProcessors, beanFactory); - - // 将当前处理的currentRegistryProcessors添加到全局的registryProcessors列表中。 - registryProcessors.addAll(currentRegistryProcessors); - - // 调用invokeBeanDefinitionRegistryPostProcessors方法执行所有选定的后处理器。 - invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); - - // ----------------------忽略其他代码--------------------------- -} -``` - -下面是我画的一个时序图大家可以参考一下。 - -~~~mermaid -sequenceDiagram - participant Init as invokeBeanFactoryPostProcessors - participant BDRPP_PO as BDRPP(PriorityOrdered) - participant BDRPP_O as BDRPP(Ordered) - participant BDRPP as 其余的BDRPP - participant BFPP_PO as BFPP(PriorityOrdered) - participant BFPP_O as BFPP(Ordered) - participant BFPP as 其余的BFPP - - Init->>BDRPP_PO: 回调 - BDRPP_PO-->>Init: 完成 - Init->>BDRPP_O: 回调 - BDRPP_O-->>Init: 完成 - Init->>BDRPP: 回调 - BDRPP-->>Init: 完成 - Init->>BFPP_PO: 回调 - BFPP_PO-->>Init: 完成 - Init->>BFPP_O: 回调 - BFPP_O-->>Init: 完成 - Init->>BFPP: 回调 - BFPP-->>Init: 完成 - - Note right of BFPP: 提示: - Note right of BFPP: BDRPP = BeanDefinitionRegistryPostProcessor - Note right of BFPP: BFPP = BeanFactoryPostProcessor - - -~~~ - -从截图就可以看出`ConfigurationClassPostProcessor`也实现了`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry`方法,其中一个重要的功能就是解析`@ComponentScan`注解。 - -![image-20230816101148882](https://img-blog.csdnimg.cn/851ff307c3644840b5cee1062c513b4d.png#pic_center) - -`invokeBeanDefinitionRegistryPostProcessors(postProcessors)`方法中,循环调用了实现`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry(registry)`方法, - -```java -private static void invokeBeanDefinitionRegistryPostProcessors(Collection 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(); - } -} -``` - -在`ConfigurationClassPostProcessor`类中的`postProcessBeanDefinitionRegistry`回调方法中又调用了`processConfigBeanDefinitions(registry)`方法 - -```java -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); -} -``` - -在`processConfigBeanDefinitions(registry)`方法中,创建一个用于解析`@Configuration`类的解析器实例(在我们的Demo中只有一个`MyComponentScanConfig`的配置类),最后调用`parser.parse(candidates)`方法去解析。 - -```java -public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { - // ...[省略其他代码]... - - // 创建一个用于解析@Configuration类的解析器实例。 - // 传入所需的依赖,如元数据读取器工厂、问题报告器、环境、资源加载器等。 - ConfigurationClassParser parser = new ConfigurationClassParser( - this.metadataReaderFactory, this.problemReporter, this.environment, - this.resourceLoader, this.componentScanBeanNameGenerator, registry); - - // 初始化待解析的配置类集合以及已解析的配置类集合。 - Set candidates = new LinkedHashSet<>(configCandidates); - Set alreadyParsed = new HashSet<>(configCandidates.size()); - - // 循环解析候选的@Configuration类,直到所有配置类都被解析。 - do { - StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); - - // 使用解析器解析当前的候选配置类。如果发现更多的配置类,它们将被添加到candidates集合中。 - parser.parse(candidates); - - // 对解析的结果进行校验,确保它们不违反任何约束。 - parser.validate(); - } while (!candidates.isEmpty()); // 如果还有未解析的候选配置类,则继续循环。 - - // ...[省略其他代码]... -} -``` - -, +`ScopedProxyMode` 是一个枚举,定义了不同的作用域代理选项,用于决定如何为特定的作用域 bean 创建代理。作用域代理是 Spring 中一个高级特性,允许在不同的上下文中共享 bean 实例,如请求或会话。此枚举的主要用途是为这些作用域 bean 提供不同的代理机制。 ```java /** - * 解析给定的配置类候选集。 - * - * @param configCandidates 待解析的配置类的BeanDefinitionHolder集合 + * 枚举各种作用域代理选项。 + * + * 为了更完整地讨论什么是作用域代理,请查看 Spring 参考文档中标题为 '作为依赖的作用域 beans' 的部分。 + * + * @author Mark Fisher + * @since 2.5 + * @see ScopeMetadata */ -public void parse(Set configCandidates) { - // 遍历每一个配置类候选 - for (BeanDefinitionHolder holder : configCandidates) { - // 获取BeanDefinition - BeanDefinition bd = holder.getBeanDefinition(); +public enum ScopedProxyMode { + + /** + * 默认通常等于 #NO,除非在组件扫描指令级别配置了不同的默认值。 + */ + DEFAULT, + + /** + * 不创建一个作用域代理。 + *

当与非单例作用域实例一起使用时,这种代理模式通常不太有用,如果要作为依赖项使用, + * 它应该优先使用 #INTERFACES 或 #TARGET_CLASS 代理模式。 + */ + NO, + + /** + * 创建一个JDK动态代理,实现目标对象的类所暴露的所有接口。 + */ + INTERFACES, + + /** + * 创建一个基于类的代理(使用CGLIB)。 + */ + TARGET_CLASS - try { - // 如果BeanDefinition是一个AnnotatedBeanDefinition(通过注解定义的) - if (bd instanceof AnnotatedBeanDefinition) { - // 根据其元数据和bean名称解析它 - parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); - } - // 如果BeanDefinition是一个AbstractBeanDefinition,并且它有一个bean类 - else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { - // 根据其bean类和bean名称进行解析 - parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); - } - // 其他情况,直接使用bean的类名进行解析 - else { - parse(bd.getBeanClassName(), holder.getBeanName()); - } - } - // 捕获BeanDefinitionStoreException,并直接重新抛出 - catch (BeanDefinitionStoreException ex) { - throw ex; - } - // 捕获其他异常,封装后抛出 - catch (Throwable ex) { - throw new BeanDefinitionStoreException( - "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); - } - } - - // 处理所有延迟的导入选择器 - this.deferredImportSelectorHandler.process(); } ``` -在parse使用给定的注解元数据和bean名称创建一个新的`ConfigurationClass`实例。 - -```java -protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { - processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); -} -``` - -这里的核心逻辑是通过do-while循环递归地调用`doProcessConfigurationClass(configClass, sourceClass, filter)`方法,目的是处理配置类及其超类层次结构。这确保了不仅仅是直接注解了`@Configuration`的类被处理,而且它的所有超类也都被解析。 - -```java -protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException { - // 1. 使用条件评估器判断当前的配置类是否应该被跳过。 - if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { - return; - } - - // 2. 检查当前的配置类是否已经被处理过。 - ConfigurationClass existingClass = this.configurationClasses.get(configClass); - - // 3. 如果该配置类已存在,检查其来源(是否由导入或其他方式得到)。 - if (existingClass != null) { - if (configClass.isImported()) { - if (existingClass.isImported()) { - // 合并两个导入的配置类的来源信息。 - existingClass.mergeImportedBy(configClass); - } - // 如果当前类是导入的,但已存在的类不是,则忽略当前类。 - return; - } else { - // 如果存在的类是导入的,但当前类不是,则删除旧的类并使用新类。 - this.configurationClasses.remove(configClass); - this.knownSuperclasses.values().removeIf(configClass::equals); - } - } - - // 4. 递归处理配置类及其所有的超类。 - SourceClass sourceClass = asSourceClass(configClass, filter); - do { - sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); - } while (sourceClass != null); - - // 5. 在处理完成后,将配置类添加到已处理列表中。 - this.configurationClasses.put(configClass, configClass); -} -``` - -我们来到`doProcessConfigurationClass(configClass, sourceClass, filter)`方法中,是不是看到了我们非常熟悉的`ComponentScan.class`注解类,我们之前配置的`@ComponentScan`注解就是在此处被解析了,并把解析后的内容封装在一个`AnnotationAttributes`中,然后调用 `this.componentScanParser.parse`进行解析,最后解析到的`BeanDefinitionHolder`进行递归处理,为什么要递归处理?因为扫描到的类还有可能是`@Configuration`标记的类,所以还要再次对扫描到的类继续解析。 - -```java -protected final SourceClass doProcessConfigurationClass( - ConfigurationClass configClass, SourceClass sourceClass, Predicate filter) - throws IOException { - // ----------------------忽略其他代码--------------------------- - - // 获取sourceClass上的@ComponentScan和@ComponentScans注解信息 - Set componentScans = AnnotationConfigUtils.attributesForRepeatable( - sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); - - // 判断sourceClass上是否有@ComponentScan或@ComponentScans注解,并且它没有被某种条件(如@Conditional注解)排除 - if (!componentScans.isEmpty() && - !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { - - // 遍历每一个@ComponentScan注解的属性 - for (AnnotationAttributes componentScan : componentScans) { - - // 执行具体的组件扫描,并返回扫描到的Bean定义 - Set scannedBeanDefinitions = - this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); - - // 遍历每个扫描到的Bean定义,检查其是否为配置类,并递归解析 - for (BeanDefinitionHolder holder : scannedBeanDefinitions) { - BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); - if (bdCand == null) { - bdCand = holder.getBeanDefinition(); - } - - // 如果当前Bean定义是一个配置类候选,则进行递归解析 - if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { - parse(bdCand.getBeanClassName(), holder.getBeanName()); - } - } - } - } - // ----------------------忽略其他代码--------------------------- - // No superclass -> processing is complete - return null; -} -``` - -我们来到`ComponentScanAnnotationParser`类中的`parse(componentScan,declaringClass)`方法 ,我们在`@ComponentScan`注解中定义的配置都被封装了了`AnnotationAttributes`类中,最后通过一个`ClassPathBeanDefinitionScanner`类扫描器去处理,并把我们之前在`MyComponentScanConfig`类中设置的`@ComponentScan`参数全部传入给这个扫描器。 - -```java -public Set parse(AnnotationAttributes componentScan, final String declaringClass) { - - // 1. 创建扫描器,设置是否使用默认的过滤器,如@Component、@Service等 - ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner( - this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); - - // 2. 设置Bean命名生成策略 - Class 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); - } - // 如果没有设置scopedProxy,就使用scopeResolver定义的解析器 - else { - Class resolverClass = componentScan.getClass("scopeResolver"); - scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); - } - - // 4. 设置资源的搜索模式,例如 "*.class" - scanner.setResourcePattern(componentScan.getString("resourcePattern")); - - // 5. 根据includeFilters添加包括类型的过滤器 - for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filter)) { - scanner.addIncludeFilter(typeFilter); - } - } - - // 6. 根据excludeFilters添加排除类型的过滤器 - for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { - for (TypeFilter typeFilter : typeFiltersFor(filter)) { - scanner.addExcludeFilter(typeFilter); - } - } - - // 7. 设置懒加载 - boolean lazyInit = componentScan.getBoolean("lazyInit"); - if (lazyInit) { - scanner.getBeanDefinitionDefaults().setLazyInit(true); - } - - // 8. 确定要扫描的基本包 - Set 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)); - } - - // 9. 排除@ComponentScan的声明类自身,防止自己被扫描 - scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { - @Override - protected boolean matchClassName(String className) { - return declaringClass.equals(className); - } - }); - - // 10. 执行扫描并返回扫描到的Bean定义 - return scanner.doScan(StringUtils.toStringArray(basePackages)); -} -``` - -我们来看看这个`ClassPathBeanDefinitionScanner`类的构造方法,在这个类中有一个重要的初始化过程就是`registerDefaultFilters()`方法主要作用就是注册一个默认过滤器,有一个条件判断`useDefaultFilters`,而我们在启动的时候`@ComponentScan`注解中配置了`useDefaultFilters`为true,默认值也是true。 - -```java -public ClassPathBeanDefinitionScanner( - BeanDefinitionRegistry registry, - boolean useDefaultFilters, - Environment environment, - @Nullable ResourceLoader resourceLoader) { - - // 确保registry不为null,否则抛出异常。 - Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); - - // 将registry参数赋值给类成员变量。 - this.registry = registry; - - // 如果设置为使用默认过滤器,则注册这些默认过滤器。 - if (useDefaultFilters) { - registerDefaultFilters(); - } - - // 设置扫描器的环境。 - setEnvironment(environment); - - // 设置资源加载器。 - setResourceLoader(resourceLoader); -} -``` - -个方法确保了类路径扫描器可以识别带有`@Component`, 像我们的`@Service`,`@Controller`,`@Repository`都是`@Component`派生出来的注解,所以这些派生注解都能被扫描到,另外还注册了两个注解`@ManagedBean`和`@Named`注解的类。如果某些注解的库(如JSR-250或JSR-330)不在类路径上,扫描器会安全地跳过它们,不会导致任何错误。 - -```java -protected void registerDefaultFilters() { - // 添加一个默认的类型过滤器,用于检测带有@Component注解的类 - this.includeFilters.add(new AnnotationTypeFilter(Component.class)); - - // 获取当前类的类加载器 - ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); - - // 尝试为JSR-250的ManagedBean注解添加一个类型过滤器 - try { - this.includeFilters.add(new AnnotationTypeFilter( - ((Class) 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的ManagedBean类不在类路径上,只需简单跳过。 - } - - // 尝试为JSR-330的Named注解添加一个类型过滤器 - try { - this.includeFilters.add(new AnnotationTypeFilter( - ((Class) 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的Named类不在类路径上,只需简单跳过。 - } -} -``` - -我们回到`ComponentScanAnnotationParser`类中的`parse(componentScan,declaringClass)`方法来,在这个方法的最后会调用`ClassPathBeanDefinitionScanner`类中`doScan(basePackages)`方法中对于每个包名,它都会查找候选组件。这些候选组件基本上是那些可能需要被Spring管理的类,例如带有@Component、@Service、@Repository或@Controller注解的类。 +`FilterType` 是一个枚举,定义了与 `@ComponentScan` 注解结合使用时的不同类型过滤器选项。这些过滤器用于决定在组件扫描过程中哪些组件应被包括或排除。 ```java /** - * 扫描指定的基础包,并为找到的类注册bean定义。 - * - * @param basePackages 需要扫描的基础包名称。 - * @return 找到并注册的bean定义集合。 + * 与 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 */ -protected Set doScan(String... basePackages) { - // 确保提供的包名不为空 - Assert.notEmpty(basePackages, "At least one base package must be specified"); +public enum FilterType { - Set beanDefinitions = new LinkedHashSet<>(); + /** + * 过滤带有指定注解的候选项。 + * @see org.springframework.core.type.filter.AnnotationTypeFilter + */ + ANNOTATION, - for (String basePackage : basePackages) { - // 对给定的包进行扫描,查找候选组件 - Set candidates = findCandidateComponents(basePackage); + /** + * 过滤可以赋值给指定类型的候选项。 + * @see org.springframework.core.type.filter.AssignableTypeFilter + */ + ASSIGNABLE_TYPE, - for (BeanDefinition candidate : candidates) { - // 确定bean的作用域(如:singleton, prototype等) - ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); - candidate.setScope(scopeMetadata.getScopeName()); + /** + * 过滤与指定的AspectJ类型模式表达式匹配的候选项。 + * @see org.springframework.core.type.filter.AspectJTypeFilter + */ + ASPECTJ, - // 为bean定义生成一个唯一的名称 - String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); + /** + * 过滤与指定的正则表达式模式匹配的候选项。 + * @see org.springframework.core.type.filter.RegexPatternTypeFilter + */ + REGEX, - // 对AbstractBeanDefinition类型的bean定义进行后处理 - if (candidate instanceof AbstractBeanDefinition) { - postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); - } - - // 对带注解的bean定义进行处理,例如处理@Autowired、@Value等注解 - if (candidate instanceof AnnotatedBeanDefinition) { - AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); - } - - // 检查当前bean的名称是否已在注册表中使用 - if (checkCandidate(beanName, candidate)) { - BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); - // 如果bean具有特定的作用域(例如会话作用域),可能需要应用一个代理 - definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); - - beanDefinitions.add(definitionHolder); - - // 在bean定义注册表中注册bean定义 - registerBeanDefinition(definitionHolder, this.registry); - } - } - } - return beanDefinitions; -} -``` - -在看看`doScan`方法中调用的`findCandidateComponents`方法,这个方法首先对`spring.components`文件提供的配置进行扫描,目的是可以提高扫描速度,有兴趣的朋友可以去研究一下`@Component`注解中的`@Indexed`注解。我们本次测试是没有使用到`spring.components`文件加速,所以我们继续查看`scanCandidateComponents(basePackage)`方法。 - -```java -public Set findCandidateComponents(String basePackage) { - // 如果存在一个预建的组件索引(例如,通过spring.components文件),并且该索引支持当前配置的过滤条件 - if (this.componentsIndex != null && indexSupportsIncludeFilters()) { - // 使用该索引来查找符合条件的组件,这通常更快 - return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); - } - // 如果没有预建索引或索引不支持当前的过滤条件,则进行传统的运行时扫描 - else { - // 扫描basePackage下的所有类,查找标记为Spring组件的类 - return scanCandidateComponents(basePackage); - } -} -``` - -从指定的包路径`basePackage`中扫描并识别Spring管理的组件,并将它们返回为一组`BeanDefinition`。方法首先构建了一个搜索路径(`classpath*:com/xcs/spring/**/*.class`),用于在classpath中查找所有相关的资源。然后遍历每个找到的资源,尝试读取其元数据,并根据元数据判断该资源是否是一个有效的Spring组件。符合条件的组件被转化为`BeanDefinition`对象并添加到结果集中。 - -```java -private Set scanCandidateComponents(String basePackage) { - // 初始化一个用于保存候选Bean定义的集合 - Set 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 = getMetadataReaderFactory().getMetadataReader(resource); - - // 判断资源是否是一个候选组件 - if (isCandidateComponent(metadataReader)) { - // 创建一个ScannedGenericBeanDefinition对象,并设置其资源 - ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); - 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); - } - } - } - } - // 处理I/O异常 - catch (IOException ex) { - throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); - } - - // 返回识别出的Bean定义 - return candidates; -} -``` - -`isCandidateComponent`方法根据指定的排除和包含过滤器,判断一个类是否符合作为Spring组件的候选条件 - -```java -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())) { - // 则进一步检查它是否满足其他条件,例如@Condition注解 - return isConditionMatch(metadataReader); - } - } - - // 如果既没有匹配到排除过滤器也没有匹配到包含过滤器,则返回false - return false; -} -``` - -我们回到`ClassPathBeanDefinitionScanner`类中`doScan(basePackages)`方法方法来,在`doScan`中调用了`checkCandidate`方法,该`checkCandidate`方法验证新的`beanDefinition`在`beanDefinitionMap`内现有的是否冲突,并在冲突时抛出异常,确保bean名称的唯一性和兼容性。 - -```java -protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { - // 检查注册中心是否已经包含该bean名称的定义 - if (!this.registry.containsBeanDefinition(beanName)) { - // 若不包含,则为有效候选 - return true; - } - - // 获取当前注册中心已存在的bean定义 - BeanDefinition existingDef = this.registry.getBeanDefinition(beanName); - - // 检查是否存在原始的Bean定义,若存在,则使用原始的定义 - BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition(); - if (originatingDef != null) { - existingDef = originatingDef; - } - - // 检查新的bean定义与现有定义是否兼容 - if (isCompatible(beanDefinition, existingDef)) { - return false; - } - - // 如果新的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() + "]"); -} -``` - -`registerBeanDefinition`方法又委托了`BeanDefinitionReaderUtils.registerBeanDefinition`去执行 - -```java -protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { - BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); -} -``` - -最后我们扫描出来的`BeanDefinition`都通过`BeanDefinitionRegistry`注册的Spring上下文中(`AnnotationConfigApplicationContext`) - -```java -public static void registerBeanDefinition( - BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) - throws BeanDefinitionStoreException { - - // 获取BeanDefinitionHolder中的bean名称 - String beanName = definitionHolder.getBeanName(); - // 使用bean名称将BeanDefinition注册到BeanDefinitionRegistry中 - registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); - - // 获取BeanDefinitionHolder中的所有别名 - String[] aliases = definitionHolder.getAliases(); - if (aliases != null) { - // 循环每个别名并将其注册到BeanDefinitionRegistry中,与主bean名称关联 - for (String alias : aliases) { - registry.registerAlias(beanName, alias); - } - } -} -``` - -下面是调用时序图 - - - -~~~mermaid -sequenceDiagram -title:@ComponentScan注解源码时序图 -ComponentScanApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses) -AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh() -AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory) -AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors) -PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup) -PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry) -ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry) -ConfigurationClassPostProcessor->>ConfigurationClassParser:ConfigurationClassParser(...) -ConfigurationClassParser-->>ConfigurationClassPostProcessor:返回解析解析器 -ConfigurationClassPostProcessor->>ConfigurationClassParser:parser.parse(candidates) -ConfigurationClassParser->>ConfigurationClassParser:parse(metadata, String beanName) -ConfigurationClassParser->>ConfigurationClassParser:processConfigurationClass(configClass,filter) -ConfigurationClassParser->>ConfigurationClassParser:doProcessConfigurationClass(configClass,sourceClass,filter) -ConfigurationClassParser->>ComponentScanAnnotationParser:parse(componentScan,declaringClass) -ComponentScanAnnotationParser->>ClassPathBeanDefinitionScanner:ClassPathBeanDefinitionScanner(registry,useDefaultFilters,environment,resourceLoader) -ClassPathBeanDefinitionScanner->>ClassPathBeanDefinitionScanner:registerDefaultFilters() -ClassPathBeanDefinitionScanner-->>ComponentScanAnnotationParser:返回扫描器 -ComponentScanAnnotationParser->>ClassPathBeanDefinitionScanner:doScan(basePackages) -ClassPathBeanDefinitionScanner->>ClassPathScanningCandidateComponentProvider:findCandidateComponents(basePackage) -ClassPathScanningCandidateComponentProvider->>ClassPathScanningCandidateComponentProvider:scanCandidateComponents(basePackage) -ClassPathScanningCandidateComponentProvider-->>ClassPathBeanDefinitionScanner:返回BeanDefinition -ClassPathBeanDefinitionScanner->>ClassPathBeanDefinitionScanner:registerBeanDefinition(definitionHolder,registry) -ClassPathBeanDefinitionScanner->>BeanDefinitionReaderUtils:registerBeanDefinition(definitionHolder, registry) -BeanDefinitionReaderUtils->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition) -ClassPathBeanDefinitionScanner-->>ComponentScanAnnotationParser:返回BeanDefinition -~~~ - - - -### 6、常见问题 - -以下示例只是为了说明可能的问题,并不代表实际的错误代码。我们大家在实际开发中,应确保清楚了解每个注解的含义和效果。 - -#### 6.1、默认的扫描路径 - -如果不指定`basePackages`或`basePackageClasses`,`@ComponentScan`默认会从声明这个注解的类的包开始扫描。如果配置类不在根包路径下,可能会错过某些组件。 - -```java -package com.xcs.spring.config; - -@Configuration -// 默认会从此类所在的包开始扫描 (默认扫描路径为:com.xcs.spring.config) -// 如果你有一个Service类需要扫描,路径为:com.xcs.spring.service.MyService1此时会错过这个组件扫描 -@ComponentScan -public class AppConfig { + /** + * 使用给定的自定义 org.springframework.core.type.filter.TypeFilter 实现来过滤候选项。 + */ + CUSTOM } ``` -#### 6.2、过度扫描 +### 三、主要功能 -如果`basePackages`设置得过于宽泛,可能会无意中扫描到许多不必要的bean,导致启动时间增加和不必要的bean实例化。 +**指定扫描的包**:通过 `basePackages` 和 `basePackageClasses` 属性,用户可以明确告诉 Spring 在哪些包中查找带有 `@Component`、`@Service`、`@Repository` 和 `@Controller` 等注解的类。 -```java -@Configuration -// 这将扫描com包下的所有子包,可能会扫描到很多不必要的组件 -// com下可能有成千上万的类,导致启动缓慢 -// 如果项目结构复杂,类很多,`@ComponentScan`可能导致启动速度减慢,因为它需要扫描大量的类。 -@ComponentScan(basePackages = "com") -public class AppConfig { +**自动扫描**:如果用户没有明确指定要扫描的包,则默认从声明 `@ComponentScan` 的类所在的包开始进行扫描。 -} -``` +**过滤扫描的组件**:通过 `includeFilters` 和 `excludeFilters` 属性,用户可以更精细地控制哪些组件应被扫描或排除。 -#### 6.3、过滤问题 +**其他配置**:此注解还提供了其他属性,如 `nameGenerator`(为检测到的组件命名)、`scopeResolver`(解析组件的范围)、`scopedProxy`(是否为组件生成代理)等,以提供更高级的配置。 - `includeFilters`和`excludeFilters`的不恰当设置可能导致意外地包含或排除某些组件。 +### 四、最佳实践 -```java -@Configuration -// 这里可能误包含了某些@Service组件或误排除了某些@Repository组件 -@ComponentScan(basePackages = "com.xcs", - includeFilters = @Filter(type = FilterType.ANNOTATION, classes = Service.class), - excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Repository.class)) -public class AppConfig { -} -``` -#### 6.4、重复的bean定义 +### 五、时序图 - 不同包中的类可能有相同的简单名称。默认情况下,bean名称是基于简单类名的,这可能导致bean名称冲突。 +### 六、源码分析 -```java -// 在com.xcs.a包中 -@Component -public class MyService { -} +### 七、注意事项 -// 在com.xcs.b包中 -@Component -public class MyService { - // com.xcs.a包中的MyService同名 -} -``` +### 八、总结 -#### 6.5、与其他配置混淆 +#### 8.1、最佳实践总结 -当使用`@ComponentScan`与XML配置或其他Java配置结合时,可能会有意外的bean定义覆盖。 - -在下述示例中,由于`MyServiceImpl`使用了`@Service`注解,并指定了相同的bean名称`myService`,因此`@ComponentScan`会扫描并注册这个bean。但在XML配置中,也为相同的bean名称提供了一个定义。 - -当Spring IoC容器初始化并处理bean定义时,由于两个定义具有相同的bean名称,因此后处理的定义(取决于加载顺序)可能会覆盖先处理的定义。这可能会导致意外的行为,例如使用了错误的bean属性或bean类。 - -为了避免此类问题,需要确保在整个应用程序中为每个bean提供一个唯一的名称,并明确知道哪个配置源负责定义哪个特定的bean。如果确实需要在多个配置源中定义同一个bean,最好明确地指定哪个定义应该优先。 - -```java -@Configuration -@ComponentScan(basePackages = "com.xcs.spring") -public class AppConfig { -} - -@Service("myService") -public class MyServiceImpl implements MyService { - private String message = "Hello from @Service!"; - - // getter and setter -} - -// 在XML中 - - - - - - -``` - -#### 6.6、条件扫描的误区 - -当使用`@Conditional`注解与`@ComponentScan`结合时,要确保条件正确配置,否则可能导致组件不被扫描。 - -例如,你可能想在开发环境中注册一个bean,但在生产环境中不这么做。或者,如果数据库类型是H2,你可能想注册一个bean,但如果是MySQL,则不这么做。 - -```java -@Component -// DevComponent bean只有在OnDevelopmentCondition条件满足时才会被Spring容器管理。 -@Conditional(OnDevelopmentCondition.class) -public class DevComponent { - // ... -} -``` - -```java -public class OnDevelopmentCondition implements Condition { - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - return "dev".equals(context.getEnvironment().getProperty("spring.profiles.active")); - } -} -``` - -在这个条件实现中,我们检查当前激活的Spring配置文件是否为"dev"。 - -如果你在使用`@ComponentScan`的时候忘记正确配置这样的条件或者误配置,可能会遇到以下情况: - -1. **预期的bean没有被注册**:如果你期望在某些条件下`DevComponent`被注册,但条件没有满足(例如,配置文件不正确或环境属性设置错误),则该bean将不会被扫描和注册。 -2. **不希望的bean被注册**:如果条件误配置并错误地满足了,可能会导致本不应该在当前环境中创建的bean被注册。 -3. **应用程序启动失败**:如果其他bean依赖于基于条件创建的bean,并且条件没有满足,那么应用程序在启动时可能会因为找不到依赖的bean而失败。 - -所以我们大家在使用`@Conditional`与`@ComponentScan`结合时,非常重要的是要确保你的条件是正确配置的,以确保spring程序中的bean正确的被加载。 - -#### 6.7、第三方库的扫描 - -不小心扫描了第三方库,可能会导致不必要的bean被注册或者出现版本冲突。 - -```java -@Configuration -// 可能导致第三方库不必要的bean注册 -@ComponentScan(basePackages = {"com.xcs.spring", "com.alibaba.dubbo"}) -public class AppConfig { - -} -``` - -#### 6.8、忽视了`useDefaultFilters` - - 有时我们使用了`includeFilters`包含过滤器,但是可能忘记将`useDefaultFilters`设置为`false`,导致除了自定义过滤器外,还应用了默认的过滤器。 - -```java -@Configuration -@ComponentScan(basePackages = "com.xcs.spring", - useDefaultFilters = true, // 使用了默认过滤器 - includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyAnnotation.class)) -// 既使用了默认过滤器又使用了自定义过滤器 -public class AppConfig { - -} -``` - -因为`useDefaultFilters`默认值为`true`,Spring将继续按默认的注解(`@Component`, `@Service`, `@Repository`, `@Controller`等)进行组件扫描,即使你可能只想按自定义的`includeFilters`进行扫描 - -### 7、总结 - -在你阅读完本次`@ComponentScan`注解的源码分析后,我们来大概做一下总结,在第一部分中首先是我们介绍了`@ComponentScan`注解说明。在第二部分中我们从spring源码总摘取了`@ComponentScan`注解的源码,从中了解了这个注解中有许多的字段。在第三部分中我们我们介绍了`@ComponentScan`注解的字段,比如说你要配置扫描包路径有`value`,`basePackages`,`basePackageClasses`三个字段都可以配置。又比如说你想设置Bean名称生成策略,那么你可以使用到`nameGenerator`字段,然后注解中有一个非常重要字段就是`useDefaultFilters`,当它的值被设置成true是会扫描到 @Component、@Repository、@Service、@Controller 注解的组件,最后是`includeFilters`,`excludeFilters`两个做过滤的字段。在第四部分中我们介绍了`@ComponentScan`注解如何使用,如果你忘记了可以回头在仔细看看上面的介绍。来到第五部分是我们的源码分析,在源码分析过程中我们发现`@ComponentScan`本身定义了多个属性,如 `basePackages`、`basePackageClasses` 和多种过滤器 `(includeFilters/excludeFilters`),当 Spring 容器启动并读取到配置类标记有 `@ComponentScan`时,会被我们的核心类`ConfigurationClassParser` 被用来解析配置类。在此过程中,会处理该注解,并确定要扫描的包路径。实际的扫描由 `ClassPathBeanDefinitionScanner` 完成。这个扫描器读取 `@ComponentScan` 的属性,并在指定的包路径下搜索候选的组件,扫描器在搜索组件时会使用 `includeFilters` 和 `excludeFilters`,这两个过滤器列表确定哪些组件应被包括和哪些应被排除,扫描完毕后,找到的组件会被解析并注册到 IOC容器中,从而允许后续的 bean 生命周期处理和自动装配功能。第六部分主要是介绍了一些常见问题。好啦,本次分析到此结束 \ No newline at end of file +#### 8.2、源码分析总结 \ No newline at end of file