• spring探秘之ConfigurationClassPostProcessor(一):处理@ComponentScan注解


    1. 回顾BeanFactoryPostProcessor的执行

    回忆下 BeanFactoryPostProcessor 的执行,调用链如下:

    1. AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(java.lang.String...)
    2. |-AbstractApplicationContext#refresh
    3. |-AbstractApplicationContext#invokeBeanFactoryPostProcessors
    4. |-PostProcessorRegistrationDelegate
    5. #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)
    6. 复制代码

    PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)中,会两次调用ConfigurationClassPostProcessor的方法:

    • invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry):调用的是BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法;
    • invokeBeanFactoryPostProcessors(registryProcessors, beanFactory):调用的是BeanFactoryPostProcessor#postProcessBeanFactory方法;

    ConfigurationClassPostProcessor同时实现了BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor,因此上述两个方法都会执行到,下面我们来看下ConfigurationClassPostProcessor的这两个方法:

    1. /**
    2. * 先执行 postProcessBeanDefinitionRegistry(...)
    3. */
    4. @Override
    5. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    6. int registryId = System.identityHashCode(registry);
    7. if (this.registriesPostProcessed.contains(registryId)) {
    8. throw new IllegalStateException(...);
    9. }
    10. if (this.factoriesPostProcessed.contains(registryId)) {
    11. throw new IllegalStateException(...);
    12. }
    13. this.registriesPostProcessed.add(registryId);
    14. // 又调用了一个方法
    15. processConfigBeanDefinitions(registry);
    16. }
    17. /**
    18. * 执行完 postProcessBeanDefinitionRegistry(...) 后,再执行 postProcessBeanFactory(...)
    19. */
    20. @Override
    21. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    22. int factoryId = System.identityHashCode(beanFactory);
    23. if (this.factoriesPostProcessed.contains(factoryId)) {
    24. throw new IllegalStateException(
    25. "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    26. }
    27. this.factoriesPostProcessed.add(factoryId);
    28. if (!this.registriesPostProcessed.contains(factoryId)) {
    29. // 如果 beanFactory 没有被处理,会再执行一次 processConfigBeanDefinitions 方法
    30. // 一般情况下,这里不会被执行到
    31. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    32. }
    33. // 增强配置类
    34. enhanceConfigurationClasses(beanFactory);
    35. // 添加处理 ImportAware 回调的 BeanPostProcessor,与本文主题关系不大,不分析
    36. beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    37. }
    38. 复制代码

    对上述两个方法,说明如下:

    • 按照PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)的执行逻辑,会先执行postProcessBeanDefinitionRegistry(...),再执行postProcessBeanDefinitionRegistry(...)
    • postProcessBeanDefinitionRegistry(...)主要是调用processConfigBeanDefinitions(...)方法;
    • postProcessBeanFactory(...) 会先判断当前beanFactory是否执行过processConfigBeanDefinitions(...)方法,如果没有,则会执行processConfigBeanDefinitions(...)方法,之后会enhanceConfigurationClasses(...)方法对配置类进行增强。

    从以上分析来看,以上两个方法最终调用的是processConfigBeanDefinitions(...)enhanceConfigurationClasses(...),因此这两个方法是我们接下来分析的重点。

    2. spring 是如何处理 @ComponentScan 注解的?

    2.1 demo 准备

    在分析流程前,我们先准备调试demo:

    首先准备一个类,上面标了@ComponentScan注解:

    1. @ComponentScan("org.springframework.learn.explore.demo02")
    2. public class BeanConfigs {
    3. }
    4. 复制代码

    再准备两个Bean

    1. @Component
    2. public class BeanObj1 {
    3. public BeanObj1() {
    4. System.out.println("调用beanObj1的构造方法");
    5. }
    6. @Override
    7. public String toString() {
    8. return "BeanObj1{}";
    9. }
    10. }
    11. @Component
    12. public class BeanObj2 {
    13. public BeanObj2() {
    14. System.out.println("调用beanObj2的构造方法");
    15. }
    16. @Override
    17. public String toString() {
    18. return "BeanObj2{}";
    19. }
    20. }
    21. 复制代码

    最后是主类:

    1. public class Demo05Main {
    2. public static void main(String[] args) {
    3. ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfigs.class);
    4. Object obj1 = context.getBean("beanObj1");
    5. Object obj2 = context.getBean("beanObj2");
    6. System.out.println("obj1:" + obj1);
    7. System.out.println("obj2:" + obj2);
    8. System.out.println(context.getBean("beanConfigs"));
    9. }
    10. }
    11. 复制代码

    以上只是demo的主要部分,完整的demo见gitee/funcy.

    运行,结果如下:

    1. 调用beanObj1的构造方法
    2. 调用beanObj2的构造方法
    3. obj1:BeanObj1{}
    4. obj2:BeanObj2{}
    5. org.springframework.learn.explore.demo05.BeanConfigs@13eb8acf
    6. 复制代码

    接下来,就以这个demo为例,一步步进行分析。

    2.2 ApplicationContext的构造方法:AnnotationConfigApplicationContext(Class)

    我们进入AnnotationConfigApplicationContext的构造方法:

    1. public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    2. this();
    3. register(componentClasses);
    4. // spring 容器启动类
    5. refresh();
    6. }
    7. /**
    8. * this() 方法的调用内容
    9. */
    10. public AnnotationConfigApplicationContext() {
    11. // 对两个成员进行赋值
    12. // 如果调用的是`AnnotationConfigApplicationContext(Class)`方法 ,这两个属性不会用到
    13. // 如果调用的是`AnnotationConfigApplicationContext(String)`方法 ,这两个属性才会用到
    14. this.reader = new AnnotatedBeanDefinitionReader(this);
    15. this.scanner = new ClassPathBeanDefinitionScanner(this);
    16. }
    17. /**
    18. * register(...) 方法的内容
    19. */
    20. @Override
    21. public void register(Class<?>... componentClasses) {
    22. Assert.notEmpty(componentClasses, "At least one component class must be specified");
    23. this.reader.register(componentClasses);
    24. }
    25. 复制代码

    AnnotationConfigApplicationContext的构造方法内容如下:

    • this():调用无参构造方法,这个方法里主要是给readerscanner成员变量赋值;
    • register(componentClasses):注册component类到beanFactory中,调用的是reader.register(...)方法;
    • refresh():spring的容器启动方法,就不分析。

    执行完register(componentClasses);前后,beanFactory内的BeanDefinitionMap内容如下:

    执行前:

    执行后:

    可以看到,beanConfigs已经注册到beanDefinitionNames了。

    到这里,spring仅仅只是把beanConfigs注册到了beanDefinitionNamesBeanConfigsnew AnnotationConfigApplicationContext(BeanConfigs.class)传入的),并没有扫描@ComponentSacn注解指定的包名,也就是org.springframework.learn.explore.demo05,那么包的扫描是在哪里进行的呢?答应就是ConfigurationClassPostProcessor中,我们继续往下看。

    2.3 处理配置类:ConfigurationClassPostProcessor#processConfigBeanDefinitions

    根据开篇的分析,我们直接进入ConfigurationClassPostProcessor#processConfigBeanDefinitions方法,调用链如下:

    1. AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class)
    2. |-AbstractApplicationContext#refresh
    3. |-AbstractApplicationContext#invokeBeanFactoryPostProcessors
    4. |-PostProcessorRegistrationDelegate
    5. #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)
    6. |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
    7. |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
    8. |-ConfigurationClassPostProcessor#processConfigBeanDefinitions
    9. 复制代码

    方法内容如下:

    1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    2. List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    3. // 1. 获取所有BeanDefinition名称
    4. String[] candidateNames = registry.getBeanDefinitionNames();
    5. // 2. 循环candidateNames数组,标识Full与Lite
    6. for (String beanName : candidateNames) {
    7. BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    8. // 判断当前BeanDefinition是已经处理过了,处理过了就不再处理了
    9. if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
    10. // 这里只是打了个log,省略
    11. ...
    12. }
    13. // 判断是否为配置类,这里分两种情况:
    14. // 1. 带有 @Configuration 注解且 proxyBeanMethods != false 的类,spring 称其为 Full 配置类
    15. // 2. 带有 @Configuration 注解且 proxyBeanMethods == false, 或带有 @Component、@ComponentScan、
    16. // @Import、@ImportResource、@Bean 其中之一注解的类,spring 称其为 Lite 配置类
    17. // 这里会对Full与Lite,进行标识
    18. else if (ConfigurationClassUtils
    19. .checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
    20. configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    21. }
    22. }
    23. // 如果没有配置类,直接返回
    24. if (configCandidates.isEmpty()) {
    25. return;
    26. }
    27. // 省略无关的代码
    28. ...
    29. // 配置类解析器,这个类非常重要,下面就是用它来解析 对@Component、@Import等注解的
    30. ConfigurationClassParser parser = new ConfigurationClassParser(
    31. this.metadataReaderFactory, this.problemReporter, this.environment,
    32. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    33. // 两个结构,candidates:需要解析的配置类,alreadyParsed:完成解析的配置类
    34. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    35. Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    36. do {
    37. // 3. 解析配置类,这个方法做了很多事,
    38. // 如:解析@Component,@PropertySources,@ComponentScans,@ImportResource等注解
    39. // 注:这里是一次性解析所有的candidates
    40. parser.parse(candidates);
    41. parser.validate();
    42. Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    43. configClasses.removeAll(alreadyParsed);
    44. // 创建 reader,这个reader与前面ApplicationContext中的reader不是同一个
    45. if (this.reader == null) {
    46. this.reader = new ConfigurationClassBeanDefinitionReader(
    47. registry, this.sourceExtractor, this.resourceLoader, this.environment,
    48. this.importBeanNameGenerator, parser.getImportRegistry());
    49. }
    50. // 4. 处理本次解析的类
    51. // 就是把@Import引入的类、配置类中带@Bean的方法、@ImportResource引入的资源等转换成BeanDefinition
    52. this.reader.loadBeanDefinitions(configClasses);
    53. // 把configClasses加入到alreadyParsed
    54. alreadyParsed.addAll(configClasses);
    55. // 解析完成后,会把candidates的清空,接下来会把新添加的、未解析过的Full配置类添加到candidates中
    56. candidates.clear();
    57. // 5. 处理返回结果,如果新添加的类为 Full 配置类且未解析过,就把它添加到candidates中,在下次循环时再解析
    58. // 获得注册器里面BeanDefinition的数量 和 candidateNames进行比较
    59. // 如果大于的话,说明有新的BeanDefinition注册进来了
    60. if (registry.getBeanDefinitionCount() > candidateNames.length) {
    61. String[] newCandidateNames = registry.getBeanDefinitionNames();
    62. Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
    63. Set<String> alreadyParsedClasses = new HashSet<>();
    64. // 循环alreadyParsed。把类名加入到alreadyParsedClasses
    65. for (ConfigurationClass configurationClass : alreadyParsed) {
    66. alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
    67. }
    68. for (String candidateName : newCandidateNames) {
    69. if (!oldCandidateNames.contains(candidateName)) {
    70. BeanDefinition bd = registry.getBeanDefinition(candidateName);
    71. // 如果新加的类为配置类,且未解析过,就把它添加到candidates中,等待下次循环解析
    72. if (ConfigurationClassUtils
    73. .checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
    74. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
    75. candidates.add(new BeanDefinitionHolder(bd, candidateName));
    76. }
    77. }
    78. }
    79. candidateNames = newCandidateNames;
    80. }
    81. }
    82. // 省略与本文流程无关的代码
    83. ...
    84. }
    85. 复制代码

    正式分析以上方法前,先明确spring关于配置类的几个概念::

    • 配置类:带有 @Configuration@Component@ComponentScan@Import@ImportResource等其中之一注解的类;
    • Full配置类:带有 @Configuration 注解且 proxyBeanMethods != false 的类,spring 称其为 Full 配置类;
    • Lite配置类:带有 @Configuration 注解且 proxyBeanMethods == false, 或带有 @Component@ComponentScan@Import@ImportResource等其中之一注解的类,spring 称其为 Lite 配置类。

    以上方法有点长,总结下来大概做了以下几件事:

    1. 获取所有BeanDefinition的名称这步执行完成后,结果如下:

    2. 循环candidateNames数组,标识配置类的类型为Full还是Lite,这一步所做的工作就是对配置类对应的BeanDefinition进行标识(至于标识后有什么作用,在后面分析 @Configuration 注解时会再分析),beanConfigs没有@Configuration注解,因此是Lite 配置类。这一步得到的configCandidates如下:

    3. 解析配置类,即解析@Component@PropertySources@ComponentScans@ImportResource等注解标注的类,这个方法非常重要,下面会重点分析;

    4. 处理本次解析的类,把@Import引入的类、配置类中带@Bean的方法、@ImportResource引入的资源等转换成BeanDefinition,加载到BeanDefinitionMap中;

    5. 处理返回结果,如果新添加的类为Full配置类,且未解析过,就把它添加到candidates中,在下次循环时再解析。

    以上方法的流程就这样了,接下来我们就来看看配置类的解析,也就是上述的第3步。

    2.4 解析:ConfigurationClassParser#parse(Set)

    1. public void parse(Set<BeanDefinitionHolder> configCandidates) {
    2. // 循环传进来的配置类
    3. for (BeanDefinitionHolder holder : configCandidates) {
    4. BeanDefinition bd = holder.getBeanDefinition();
    5. try {
    6. // 如果获得BeanDefinition是AnnotatedBeanDefinition的实例
    7. // 前面得到的 beanConfigs就是AnnotatedBeanDefinition的实例,if里的方法会执行
    8. if (bd instanceof AnnotatedBeanDefinition) {
    9. parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    10. }
    11. else if (bd instanceof AbstractBeanDefinition
    12. && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    13. parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
    14. }
    15. else {
    16. parse(bd.getBeanClassName(), holder.getBeanName());
    17. }
    18. }
    19. catch (...) {
    20. ...
    21. }
    22. }
    23. this.deferredImportSelectorHandler.process();
    24. }
    25. /**
    26. * 调用parse进行解析
    27. */
    28. protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    29. // ConfigurationClass:metadata与beanName的包装类
    30. processConfigurationClass(new ConfigurationClass(metadata, beanName));
    31. }
    32. 复制代码

    前面传入的 BeanConfigs 会被包装成AnnotatedGenericBeanDefinition,它是AnnotatedBeanDefinition的实例,然后就会调用ConfigurationClassParser#parse(String, String),这里其实并没做什么实质性的工作,继续进入processConfigurationClass(...)方法:

    1. /**
    2. * 这个方法是在判断条件,保证配置类不重复解析
    3. * 实际干活的是 doProcessConfigurationClass(...)
    4. */
    5. protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    6. // 判断是否需要跳过处理,针对于 @Conditional 注解,判断是否满足条件
    7. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata() ,
    8. ConfigurationPhase.PARSE_CONFIGURATION)) {
    9. return;
    10. }
    11. ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    12. // 判断是否解析过,解析过就不解析了,与解析内容关系不大,省略
    13. if (existingClass != null) {
    14. ...
    15. }
    16. // SourceClass 同前面的 ConfigurationClass 一样,也是对metadata与beanName的包装
    17. SourceClass sourceClass = asSourceClass(configClass);
    18. do {
    19. // doXxx(...) 方法才是真正干活的
    20. // 如果返回的内容不为空,下面会再次循环
    21. sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    22. }
    23. while (sourceClass != null);
    24. this.configurationClasses.put(configClass, configClass);
    25. }
    26. 复制代码

    这个方法先判断配置类是否满足执行条件,然后在do-while循环中执行doProcessConfigurationClass(...),循环条件是doProcessConfigurationClass(...)返回的内容不为空,我们继续往下看。

    2.5 解析配置类:ConfigurationClassParser#doProcessConfigurationClass

    1. /**
    2. * 这个方法才是真正处理解析的方法
    3. */
    4. protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass,
    5. SourceClass sourceClass) throws IOException {
    6. // 1. 如果是 @Component 注解,递归处理内部类,本文不关注
    7. ...
    8. // 2. 处理@PropertySource注解,本文不关注
    9. ...
    10. // 3. 处理 @ComponentScan/@ComponentScans 注解
    11. // 3.1 获取配置类上的 @ComponentScan/@ComponentScans 注解
    12. Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    13. sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    14. // 如果没有打上ComponentScan,或者被@Condition条件跳过,就不再进入这个if
    15. if (!componentScans.isEmpty() && !this.conditionEvaluator
    16. .shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    17. // 循环处理componentScans,也就是配置类上的所有@ComponentScan注解的内容
    18. for (AnnotationAttributes componentScan : componentScans) {
    19. // 3.2 componentScanParser.parse(...):具体解析componentScan的操作
    20. // componentScan就是@ComponentScan上的具体内容,
    21. // sourceClass.getMetadata().getClassName()就是配置类的名称
    22. Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser
    23. .parse(componentScan, sourceClass.getMetadata().getClassName());
    24. // 3.3 循环得到的 BeanDefinition,如果对应的类是配置类,递归调用parse(...)方法
    25. // componentScan引入的类可能有被@Bean标记的方法,或者有@ComponentScan注解
    26. for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    27. BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    28. if (bdCand == null) {
    29. bdCand = holder.getBeanDefinition();
    30. }
    31. // 判断BeanDefinition对应的类是否为配置类
    32. if (ConfigurationClassUtils
    33. .checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    34. // 对得到的类,调用parse(...)方法,再次进行解析
    35. parse(bdCand.getBeanClassName(), holder.getBeanName());
    36. }
    37. }
    38. }
    39. }
    40. // 4. 处理@Import注解,本文不关注
    41. ...
    42. // 5. 处理@ImportResource注解,本文不关注
    43. ...
    44. // 6. 处理@Bean的注解,,本文不关注
    45. ...
    46. // 7. 返回配置类的父类,会在 processConfigurationClass(...) 方法的下一次循环时解析
    47. // sourceClass.getMetadata()就是配置类
    48. if (sourceClass.getMetadata().hasSuperClass()) {
    49. String superclass = sourceClass.getMetadata().getSuperClassName();
    50. if (superclass != null && !superclass.startsWith("java") &&
    51. !this.knownSuperclasses.containsKey(superclass)) {
    52. this.knownSuperclasses.put(superclass, configClass);
    53. return sourceClass.getSuperClass();
    54. }
    55. }
    56. return null;
    57. }
    58. /**
    59. * 这个方法会再次调用processConfigurationClass(...)方法进行解析
    60. * 目的是,新引入的类也有可能有被@Bean标记的方法,或者有ComponentScan等注解
    61. */
    62. protected final void parse(@Nullable String className, String beanName) throws IOException {
    63. Assert.notNull(className, "No bean class name for configuration class bean definition");
    64. MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
    65. // 又调回 processConfigurationClass(...) 方法了
    66. processConfigurationClass(new ConfigurationClass(reader, beanName));
    67. }
    68. 复制代码

    ConfigurationClassParser#doProcessConfigurationClass这个方法就是对@PropertySource@ComponentScan@Import@ImportResource@Bean等注解,本文我们仅关注@ComponentScan注解的处理,流程如下:

    1. 获取配置类上的 @ComponentScan/@ComponentScans 注解;
    2. componentScanParser.parse(...):具体解析componentScan的操作,后面会重点分析;
    3. 循环得到的 BeanDefinition,如果对应的类是配置类,递归调用parse(...)方法,如果扫描到的类包含 @Import@Bean@ComponentScan等注解,递归调用parse(...)方法时会被解析到。

    上面的操作流程在代码中已经注释得很清楚了,就不多说了,我们直接来看@ComponentScan的解析操作.

    2.6 真正解析的地方:ComponentScanAnnotationParser#parse

    @ComponentScan的解析在ComponentScanAnnotationParser#parse方法中,代码如下:

    1. public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    2. // 1. 定义一个扫描器,用来扫描包
    3. ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
    4. componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    5. // 2. 判断是否重写了默认的命名规则
    6. Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    7. boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    8. scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
    9. BeanUtils.instantiateClass(generatorClass));
    10. // 3. 解析 @ComponentScan 注解的属性
    11. // 3.1 处理 @ComponentScan 的 scopedProxy 属性
    12. ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    13. if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
    14. scanner.setScopedProxyMode(scopedProxyMode);
    15. }
    16. else {
    17. Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
    18. scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    19. }
    20. // 3.2 处理 @ComponentScan 的 resourcePattern 属性
    21. scanner.setResourcePattern(componentScan.getString("resourcePattern"));
    22. // 3.3 处理 @ComponentScan 的 includeFilters 属性
    23. // addIncludeFilter addExcludeFilter,最终是往List<TypeFilter>里面填充数据
    24. for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
    25. for (TypeFilter typeFilter : typeFiltersFor(filter)) {
    26. scanner.addIncludeFilter(typeFilter);
    27. }
    28. }
    29. // 3.4 处理 @ComponentScan 的 excludeFilters 属性
    30. for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
    31. for (TypeFilter typeFilter : typeFiltersFor(filter)) {
    32. scanner.addExcludeFilter(typeFilter);
    33. }
    34. }
    35. boolean lazyInit = componentScan.getBoolean("lazyInit");
    36. if (lazyInit) {
    37. scanner.getBeanDefinitionDefaults().setLazyInit(true);
    38. }
    39. Set<String> basePackages = new LinkedHashSet<>();
    40. // 3.5. @ComponentScan 指定了 basePackages 属性,这个属性的类型是String
    41. String[] basePackagesArray = componentScan.getStringArray("basePackages");
    42. for (String pkg : basePackagesArray) {
    43. String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
    44. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    45. Collections.addAll(basePackages, tokenized);
    46. }
    47. // 3.6. @ComponentScan 指定了 basePackageClasses, 这个属性的类型是Class
    48. // 即只要是与这几个类同级的,或者在这几个类下级的都可以被扫描到
    49. for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
    50. basePackages.add(ClassUtils.getPackageName(clazz));
    51. }
    52. // 3.7 以上都没有指定,默认会把配置类所在的包名作为扫描路径
    53. if (basePackages.isEmpty()) {
    54. basePackages.add(ClassUtils.getPackageName(declaringClass));
    55. }
    56. // 3.8 添加排除规则,这里就把注册类自身当作排除规则,真正执行匹配的时候,会把自身给排除
    57. scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
    58. @Override
    59. protected boolean matchClassName(String className) {
    60. return declaringClass.equals(className);
    61. }
    62. });
    63. // 4. 到这里,才开始扫描 @ComponentScan 指定的包
    64. // 扫描完成后,对符合条件的类,spring会将其添加到beanFactory的BeanDefinitionMap中
    65. return scanner.doScan(StringUtils.toStringArray(basePackages));
    66. }
    67. 复制代码

    以上方法执行流程如下:

    1. 定义一个扫描器,用来扫描包
    2. 判断是否重写了默认的命名规则
    3. 解析 @ComponentScan 注解的属性,完善扫描器
      1. 处理 @ComponentScanscopedProxy 属性
      2. 处理 @ComponentScanresourcePattern 属性
      3. 处理 @ComponentScanincludeFilters 属性
      4. 处理 @ComponentScanexcludeFilters 属性
      5. 处理 @ComponentScanbasePackages 属性,这个属性的类型是String
      6. 处理 @ComponentScanbasePackageClasses 属性, 这个属性的类型是Class
      7. 如果没有指定扫包规则,默认会把配置类所在的包名作为扫描路径
      8. 添加排除规则,这里就把注册类自身当作排除规则,真正执行匹配的时候,会把自身给排除
    4. 调用 ClassPathBeanDefinitionScanner#doScan 完成扫描

    最终,调用了ClassPathBeanDefinitionScanner#doScan方法来完成包的描述,这个方法我们在spring启动流程之包的扫描流程已经详细分析过了,这里就不再分析了。

    让我们回到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法:

    1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    2. ...
    3. parser.parse(candidates);
    4. ....
    5. }
    6. 复制代码

    执行前:

    执行后:

    可以看到,BeanObj1BeanObj2已经放入了BeanFactoryBeanDefinitionMap中了 。

    2.7 总结

    spring处理@ComponentScan的流程到这里就结束了,解析流程位于ConfigurationClassParser#doProcessConfigurationClass方法,这个方法除了解析@ComponentScan,还会解析@Bean@Import等注解,本文只分析@ComponentScan的处理流程,对以上流程总结如下:

    1. 获取配置类上的 @ComponentScan/@ComponentScans 注解
    2. 进行解析@ComponentScan的操作,解析时,先定义了一个描述器,然后根据@ComponentScan的属性,对描述器器进行属性填充,处理完这些之后,就开始进行包扫描操作;
    3. 遍历扫描得到的类,如果其为配置类,会通过调用parse(...)方法再一次调用ConfigurationClassParser#doProcessConfigurationClass进行解析,这一点非常重要,这就保证了扫描得到的类中的@Bean@Import@ComponentScan等注解得到解析。

    好了,本文就先到这里了,接下来几篇文章会继续分析ConfigurationClassPostProcessor对其他注解的处理。

  • 相关阅读:
    【docker】虚拟化和docker容器概念
    点击化学 PEG 试剂:1802907-92-1,Mtz-PEG4-NHS,甲基四嗪-四聚乙醇-活性酯
    Change Buffer 只适用于非唯一索引页?错
    ASO优化如何做?这几个技巧你绝对要了解
    【每日一题】1654. 到家的最少跳跃次数
    Unity Joint用法及案例
    OpenGL进阶(一)之帧缓冲FrameBuffer
    TiDB Lightning Web 界面
    常见的加密算法和类型
    53_Pandas中的条件替换值(where, mask)
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/127699930