我们都知道 Spring 会根据 @ComponentScan(“xxx.xxx.xxx”) 指定的包路径,完成 bean 的扫描,那么 Spring 是如何实现扫描的呢?

  1. @Configuration
  2. @ComponentScan("org.wesoft.spring.scan")
  3. public class AppConfig {
  4. }

scan 方法

我们先做一个实验,去掉 @ComponentScan(“org.wesoft.spring.scan”) 这个注解,利用一个 API,来完成扫描

  1. @Configuration
  2. // @ComponentScan("org.wesoft.spring.scan")
  3. public class AppConfig {
  4. }
  5. public class App {
  6. public static void main(String[] args) {
  7. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
  8. ac.register(AppConfig.class);
  9. ac.scan("org.wesoft.spring.scan");
  10. ac.refresh();
  11. System.out.println(ac.getBean(TestBean.class)); // TestBean@3dd4520b
  12. }
  13. }

这里使用了 ac.scan("org.wesoft.spring.scan"); 这个 API,同样完成了 bean 的扫描,那么我们就知道 @ComponentScan(“org.wesoft.spring.scan”) 其实就是使用了 ac.scan("xxx")

scan 解析

  1. @Override
  2. public void scan(String... basePackages) {
  3. Assert.notEmpty(basePackages, "At least one base package must be specified");
  4. // 扫描 指定包 下的所有符合 spring 规则的类
  5. this.scanner.scan(basePackages);
  6. }

内部其实也调用了 this.scanner.scan(basePackages); 方法,那么 this.scanner 这个对象是从何而来,这个对象是 Spring 初始化的时候,就对 scanner 进行了初始化,本章暂不对 this.scanner 进行详解,后面会有章节详细描述

  1. public AnnotationConfigApplicationContext() {
  2. this.reader = new AnnotatedBeanDefinitionReader(this);
  3. this.scanner = new ClassPathBeanDefinitionScanner(this);
  4. }

点开 scan 方法,里面有一个 doScan(basePackages); 方法 ,这个方法完成了 bean 的扫描,在没有看 doScan 源码的情况下,我们思考一个问题,如果给我们一个包名,我们如何拿到所有的 @Component 标记的 bean 呢?

其实第一个想到的就是用 File I/O 来进行操作,递归拿到所有该包下的文件名,通过反射出对象,判断是否含有 @Component 注解

对了,其实 Spring 也就是这么做的,只是 Spring 进行了一些封装

下图是 Spring 扫描出来的该包下的所有的类
image.png

  1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Assert.notEmpty(basePackages, "At least one base package must be specified");
  3. Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  4. for (String basePackage : basePackages) {
  5. // 关键代码,完成扫描
  6. Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  7. for (BeanDefinition candidate : candidates) {
  8. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
  9. candidate.setScope(scopeMetadata.getScopeName());
  10. String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
  11. if (candidate instanceof AbstractBeanDefinition) {
  12. postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
  13. }
  14. if (candidate instanceof AnnotatedBeanDefinition) {
  15. AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
  16. }
  17. if (checkCandidate(beanName, candidate)) {
  18. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
  19. definitionHolder =
  20. AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  21. beanDefinitions.add(definitionHolder);
  22. registerBeanDefinition(definitionHolder, this.registry);
  23. }
  24. }
  25. }
  26. return beanDefinitions;
  27. }

继续看 findCandidateComponents(basePackage) 里的代码

  1. private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
  2. // 定义了一个存放 Spring Bean 的候选集合
  3. Set<BeanDefinition> candidates = new LinkedHashSet<>();
  4. try {
  5. // 解析 basePackage 对于的路径
  6. String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
  7. resolveBasePackage(basePackage) + '/' + this.resourcePattern;
  8. // 解析 packageSearchPath 获取所有类
  9. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
  10. boolean traceEnabled = logger.isTraceEnabled();
  11. boolean debugEnabled = logger.isDebugEnabled();
  12. for (Resource resource : resources) {
  13. if (traceEnabled) {
  14. logger.trace("Scanning " + resource);
  15. }
  16. if (resource.isReadable()) {
  17. try {
  18. // 解析当前这个类所有的元数据,包括注解信息
  19. MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
  20. // ★★★ 关键代码:判断当前类是否是符合 Spring 规则的 @Component
  21. if (isCandidateComponent(metadataReader)) {
  22. // ★ 创建了 ScannedGenericBeanDefinition
  23. // ★ 这里就证明了通过注解扫描出来的类都是 ScannedGenericBeanDefinition
  24. ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
  25. sbd.setResource(resource);
  26. sbd.setSource(resource);
  27. // ★★★ 关键代码:返回是(是否一个独立的类(可以通过构造函数创建)并且 不能是接口和抽象类)或者(是一个抽象类,并且 有 Lookup 注解)
  28. if (isCandidateComponent(sbd)) {
  29. if (debugEnabled) {
  30. logger.debug("Identified candidate component class: " + resource);
  31. }
  32. // ★ 加入到候选集合中
  33. candidates.add(sbd);
  34. }
  35. else {
  36. if (debugEnabled) {
  37. logger.debug("Ignored because not a concrete top-level class: " + resource);
  38. }
  39. }
  40. }
  41. else {
  42. if (traceEnabled) {
  43. logger.trace("Ignored because not matching any filter: " + resource);
  44. }
  45. }
  46. }
  47. catch (Throwable ex) {
  48. throw new BeanDefinitionStoreException(
  49. "Failed to read candidate component class: " + resource, ex);
  50. }
  51. }
  52. else {
  53. if (traceEnabled) {
  54. logger.trace("Ignored because not readable: " + resource);
  55. }
  56. }
  57. }
  58. }
  59. catch (IOException ex) {
  60. throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
  61. }
  62. return candidates;
  63. }

代码中有一段逻辑 isCandidateComponent(metadataReader) ,是判断是否是 Spring bean 的关键方法,其中有一个 this.includeFilters 包含了两个注解,一个是 @Component 和 @ManagedBean,其中 @ManagedBean 是 JSR-250 的规范,同样可以让 Spring 扫描到

  1. protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  2. for (TypeFilter tf : this.excludeFilters) {
  3. if (tf.match(metadataReader, getMetadataReaderFactory())) {
  4. return false;
  5. }
  6. }
  7. // 判断是否是 Spring 所需要的类型 @Component
  8. for (TypeFilter tf : this.includeFilters) {
  9. if (tf.match(metadataReader, getMetadataReaderFactory())) {
  10. return isConditionMatch(metadataReader);
  11. }
  12. }
  13. return false;
  14. }

看到这里,是否有一个疑问?那么这样,如果我们提供了一个自定义注解,然后放到 this.includeFilters 中,不就可以扩展 Spring 了么?

没错,下面的文章,我们就会自定义一些注解,加入到 this.includeFilters 中,让 Spring 帮助我们扫描

上述代码中,我已经详细描述了 Spring 是如何扫描 @Component 注解的类了,并加入到 candidates 集合中

小结

也就是 Spring 会通过 I/O 读取 basePackage 下所有的类,然后进行判断(是否是一个独立的类(可以通过构造函数创建)并且 不能是接口和抽象类)或者(是一个抽象类,并且 有 Lookup 注解),如果符合要求,就加入 **candidates 集合**中,供后续逻辑执行

自定义 @MyComponent 注解

我们自定义注解一个注解 @MyComponent,让 Spring 帮助我们完成扫描,放入单例池,上面的文章也说了,关键就是让 Spring 认识我们提供的注解,核心就是 this.includeFilters 里面需要包含我们的注解,那么怎么给 includeFilters 添加注解呢?我们先看看 Spring 是怎么做的

  1. public AnnotationConfigApplicationContext() {
  2. this.reader = new AnnotatedBeanDefinitionReader(this);
  3. this.scanner = new ClassPathBeanDefinitionScanner(this);
  4. }

Spring 在初始化的时候,就直接 new ClassPathBeanDefinitionScanner(this) 来给 scanner,同时,在 new ClassPathBeanDefinitionScanner 的时候又初始化了默认的 Filters, registerDefaultFilters();

  1. public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
  2. Environment environment, @Nullable ResourceLoader resourceLoader) {
  3. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  4. this.registry = registry;
  5. // 关键代码:useDefaultFilters 默认为 true
  6. if (useDefaultFilters) {
  7. // 注册默认的过滤器,一般情况下会注册两个默认过滤器
  8. // 1、Component Filter
  9. // 2、ManagedBean Filter
  10. // 如果使用了 JSR-330 'javax.inject.Named' ,则会再注册一个 Named Filter
  11. registerDefaultFilters();
  12. }
  13. setEnvironment(environment);
  14. setResourceLoader(resourceLoader);
  15. }

其中 registerDefaultFilters 方法,就注册了 @Component,并尝试中注册 @ManagedBean 和 @Named

  1. protected void registerDefaultFilters() {
  2. // 注册默认过滤器
  3. // 这里传了一个 Component.class
  4. // 后续 spring 扫描出来一个 resource 以后需要判断它是否合理,这里的代码非常重要
  5. this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  6. ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
  7. // 尝试注册 @ManagedBean,这是 JSR-250 的 API
  8. try {
  9. this.includeFilters.add(new AnnotationTypeFilter(
  10. ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
  11. logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
  12. }
  13. catch (ClassNotFoundException ex) {
  14. // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
  15. }
  16. // 尝试注册 @Named,这是 JSR-330 的 API
  17. try {
  18. this.includeFilters.add(new AnnotationTypeFilter(
  19. ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
  20. logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
  21. }
  22. catch (ClassNotFoundException ex) {
  23. // JSR-330 API not available - simply skip.
  24. }
  25. }

所以,我们可以看到,其实关键代码就是

  1. this.includeFilters.add(new AnnotationTypeFilter(Component.class));

实现步骤

看到这里,其实实现起来就比较简单了,我们可以直接使用 ClassPathBeanDefinitionScanner 中的 addIncludeFilter 方法,就可以完成 filter 的添加

如有特殊需求,也可以写一个类继承 ClassPathBeanDefinitionScanner

  1. @MyComponent
  2. public class CustomTestBean {
  3. }
  4. @MyComponent
  5. public class CustomBean {
  6. public CustomBean(CustomTestBean customTestBean) {
  7. System.out.println(customTestBean);
  8. }
  9. }
  10. ClassPathBeanDefinitionScanner customScanner = new ClassPathBeanDefinitionScanner(ac);
  11. customScanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));
  12. int scan = customScanner.scan("org.wesoft.spring.scan");
  13. System.out.println(scan); // 扫描到符合要求 bean 的数量
  14. // org.wesoft.spring.scan.bean.TestBean@5ae63ade
  15. // 2

自定义 MyBatis 的 MapperScanner 注解

其实 MyBatis 的 MapperScanner 也是对 includeFilters 做了一些手脚,重写了 filter 的 match 方法,让他永远返回 true,同时,判断是一个接口

  1. @Override
  2. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  3. addIncludeFilter(new TypeFilter() {
  4. @Override
  5. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  6. return true;
  7. }
  8. });
  9. return beanDefinition.getMetadata().isInterface();
  10. }