Loading... 在 Spring 框架中,组件扫描机制(scan)是核心功能之一,它通过自动扫描类路径下的特定包或类,发现并注册带有特定注解的 Spring 组件(如 `@Component`、`@Service`、`@Repository`等)到容器中。Spring 的扫描机制由多层次的类和方法共同实现,下面将从源码角度深入剖析 Spring 的扫描机制。 ## 1. Spring 的组件扫描机制简介 Spring 中的组件扫描主要通过 `@ComponentScan`注解和 `<context:component-scan>`标签来启动。`@ComponentScan`注解通常放置在配置类上,它告诉 Spring 在指定的包路径下扫描带有 `@Component`、`@Service`、`@Controller`等注解的类,并将这些类注册为 Spring 的 Bean。这个扫描机制大大简化了开发者手动注册 Bean 的工作。 ## 2. `@ComponentScan` 和 `ClassPathBeanDefinitionScanner` Spring 的组件扫描机制依赖于 `@ComponentScan`注解或 XML 配置中的 `<context:component-scan>`标签,Spring 使用 `ClassPathBeanDefinitionScanner`类来执行具体的扫描工作。核心工作流程如下: ### 2.1 `@ComponentScan` 注解 当 Spring 启动时,首先会扫描配置类中的 `@ComponentScan`注解。Spring 会通过注解解析器获取 `@ComponentScan`的值(即要扫描的包路径),并根据这个包路径找到所有需要扫描的类。 ```java @ComponentScan(basePackages = "com.example") public class AppConfig { } ``` - `basePackages`:指定要扫描的包,Spring 会从这些包路径中递归查找符合条件的类。 **解释**:`@ComponentScan`是用于定义扫描路径的注解,Spring 根据这个注解启动扫描机制。 ### 2.2 `ClassPathBeanDefinitionScanner` Spring 使用 `ClassPathBeanDefinitionScanner`类来执行组件扫描,它是组件扫描的核心执行者。`ClassPathBeanDefinitionScanner`会扫描指定路径下的所有类,并通过一系列的过滤规则决定哪些类应该注册为 Spring 容器中的 Bean。 - `ClassPathBeanDefinitionScanner` 继承了 `ClassPathScanningCandidateComponentProvider`,这个类负责通过类路径扫描候选组件。 - 关键方法是 `doScan()`,它会根据传入的包路径扫描包下的所有类,并筛选出符合条件的类(通常是带有 `@Component`、`@Service`等注解的类)。 ```java public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { @Override protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { beanDefinitions.add(registerBeanDefinition(candidate)); } } return beanDefinitions; } } ``` **解释**: - `doScan`方法负责在指定包路径下扫描所有符合条件的类,并将其注册为 Bean。 - `findCandidateComponents`会查找符合条件的候选组件。 - `registerBeanDefinition`将找到的 Bean 注册到 Spring 容器中。 ### 2.3 `findCandidateComponents` 方法 `ClassPathScanningCandidateComponentProvider` 类中的 `findCandidateComponents()` 方法是扫描过程中最重要的部分,它通过指定的包路径找到所有符合条件的类并生成 `BeanDefinition`。具体逻辑如下: ```java public Set<BeanDefinition> findCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { for (Resource resource : resourcePatternResolver.getResources(basePackage)) { // 根据资源解析元数据并生成BeanDefinition MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); candidates.add(sbd); } } } catch (IOException ex) { // 异常处理 } return candidates; } ``` **解释**: - `findCandidateComponents`方法会遍历包路径下的所有类,生成候选的 `BeanDefinition`,并通过过滤器决定是否将该类注册为 Spring Bean。 - `MetadataReader`用于读取类的元数据(如注解),以便判断该类是否是符合条件的 Spring 组件。 ### 2.4 过滤机制 Spring 扫描机制中使用过滤器来确定哪些类可以作为候选组件。默认情况下,Spring 会过滤出带有 `@Component`、`@Repository`、`@Service`等注解的类。通过 `TypeFilter` 接口,开发者可以自定义过滤规则。 默认的过滤器逻辑如下: ```java public class AnnotationTypeFilter extends TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) { return metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()); } } ``` **解释**: - 过滤器通过 `MetadataReader` 读取类的注解信息,如果类上存在指定的注解(如 `@Component`),则该类会被认为是候选组件。 ## 3. BeanDefinition 注册 扫描到符合条件的类之后,Spring 会将其包装为 `BeanDefinition` 并注册到 Spring 容器中。这个过程发生在 `registerBeanDefinition()` 方法中。 ```java protected void registerBeanDefinition(BeanDefinition candidate) { String beanName = resolveBeanName(candidate); beanDefinitionRegistry.registerBeanDefinition(beanName, candidate); } ``` **解释**: - `registerBeanDefinition` 方法负责将生成的 `BeanDefinition` 注册到 `BeanDefinitionRegistry` 中,`beanDefinitionRegistry` 是 Spring 管理所有 Bean 的注册表。 - `beanName` 是该 Bean 的唯一标识,通常由类名或自定义名称生成。 ## 4. 总结与扫描机制工作流程 ### 4.1 扫描机制工作流程 1. **注解解析**:Spring 容器启动时解析 `@ComponentScan` 注解,获取要扫描的包路径。 2. **路径扫描**:`ClassPathBeanDefinitionScanner` 执行路径扫描,查找所有符合条件的类。 3. **过滤候选组件**:通过 `findCandidateComponents` 方法,使用注解或自定义过滤器筛选符合条件的组件。 4. **生成 BeanDefinition**:对符合条件的类生成 `BeanDefinition`。 5. **注册 Bean**:将 `BeanDefinition` 注册到 `BeanDefinitionRegistry` 中。 ### 4.2 Spring 扫描机制分析表 | 组件名称 | 功能描述 | | ---------------------------------- | --------------------------------------------------------- | | `@ComponentScan` | 指定扫描的包路径,用于启用组件扫描功能 | | `ClassPathBeanDefinitionScanner` | 执行类路径扫描,查找并注册候选组件 | | `findCandidateComponents` | 根据路径查找符合条件的候选组件,返回 `BeanDefinition` | | `TypeFilter` | 过滤规则,决定哪些类可以作为 Spring 组件 | | `BeanDefinitionRegistry` | 注册扫描到的 `BeanDefinition`,将其交给 Spring 容器管理 | ## 5. 结语 Spring 的扫描机制通过 `@ComponentScan` 注解与 `ClassPathBeanDefinitionScanner` 类协作,完成了从类路径扫描到组件注册的全流程。这种机制使得开发者可以轻松地通过注解配置来管理 Bean,从而提高了开发效率。 最后修改:2024 年 09 月 16 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏