SpringBoot IoC(3)找beanDefinitionMap的过程

610 阅读6分钟

代码

AbstractApplicationContext->refresh():
    invokeBeanFactoryPostProcessors(beanFactory);
AbstractApplicationContext->invokeBeanFactoryPostProcessors():
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
/**    
* PostProcessor为后置处理器
*/
PostProcessorRegistrationDelegate->invokeBeanFactoryPostProcessors():
    // 对beanFactoryPostProcessors分类,如果是BeanDefinitionRegistryPostProcessor的子类,就放到registryProcessors集合中,否则放入regularPostProcessors集合中
    ......
    // 从IoC容器的beanDefinitionNames中找到实现了BeanDefinitionRegistryPostProcessor接口的beanName数组
    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    for (String ppName : postProcessorNames) {
        // 如果该ppName所对应的类实现了PriorityOrdered接口的话,就加入到currentRegistryProcessors中
        // 内部很复杂的实现之后,可以看到org.springframework.context.annotation.internalConfigurationAnnotationProcessor这个ppName所对应的是org.springframework.context.annotation.ConfigurationClassPostProcessor类,它是继承了PriorityOrdered接口的
        if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
        // 把ConfigurationClassPostProcessor这个类添加到currentRegistryProcessors集合中
            currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
            processedBeans.add(ppName);
        }
    }
    sortPostProcessors(currentRegistryProcessors, beanFactory);
    // 把currentRegistryProcessors里的后置处理器添加到registryProcessors集合中
    registryProcessors.addAll(currentRegistryProcessors);
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);  // --1
    currentRegistryProcessors.clear();
    ......

这里只讲述第一次找bean的过程,invokeBeanFactoryPostProcessors()方法完整过程描述见此

PostProcessorRegistrationDelegate->invokeBeanDefinitionRegistryPostProcessors():    // --1
    // 根据不同的后置处理器,执行postProcessBeanDefinitionRegistry方法的结果也不一样
    // 以ConfigurationClassPostProcessor举例说明,这里的registry即为IoC容器
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
    	postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
ConfigurationClassPostProcessor->postProcessBeanDefinitionRegistry():
    processConfigBeanDefinitions(registry);
ConfigurationClassPostProcessor->processConfigBeanDefinitions():
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        // 从beanDef的attributes属性判断,首次进入不可能为true
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
            ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
            }
        // 如果加了@Configuration,那么对应的attributes为full
        // 如果类有@Component,@ComponentScan,@Import,@ImportResource,或方法中有@Bean,则为lite
        // 注解的注解也会扫描(以递归的方式实现),直到只剩元注解为止,如例二
        // 上面两类注解符合之一的话,就会添加到configCandidates集合中
        // 此时只有启动类符合两类注解
        } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    } 
    ......
    parser.parse(candidates);   // --2
    parser.validate();
    // parser.getConfigurationClasses()是以.class为单位的map对象
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    ......
    /*
    configClasses里每个configClass里的@Bean,@Import,@ImportResource加入到IoC容器的beanDefinitionMap和beanDefinitionNames变量中
    */
    this.reader.loadBeanDefinitions(configClasses);
    ......
// 这里的configCandidates仅仅是符合两类注解之一的BeanDefinitionHolder,一般是启动类
ConfigurationClassParser->parse():  // --2
    for (BeanDefinitionHolder holder : configCandidates) {
        // 解析配置类,扫描文件,符合两类注解之一的封装为ConfigurationClass,添加到configurationClasses对象中
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    }
    // 这里,会把默认的配置bean添加到configurationClasses对象中,可以参考我的另一篇文章
    this.deferredImportSelectorHandler.process();
ConfigurationClassParser->parse():
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
ConfigurationClassParser->processConfigurationClass(): 
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    // 只有当sourceClass为空时,才结束循环
    // 为空的条件就是除了Object类之外,没有父类了
    while (sourceClass != null);
ConfigurationClassParser->doProcessConfigurationClass():   
    // 如果配置类包含了@Component注解(仍然是递归查找),一系列的操作,最终还是调用的doProcessConfigurationClass()方法
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        processMemberClasses(configClass, sourceClass);
    }
    // 先找出类上的@ComponentScan和@ComponentScans注解的所有属性
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    ......
    // 如果配置类用到了@ComponentScan注解,扫描包下的.class文件
    // 找到包含@Component注解的class,立即添加到IoC容器的beanDefinitionMap和beanDefinitionNames中
    // 返回被封装成BeanDefinitionHolder对象的Set集合
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());  // --3
    // 递归,一系列的操作,最终还是调用的doProcessConfigurationClass()方法
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    	BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    	if (bdCand == null) {
    	    bdCand = holder.getBeanDefinition();
    	}
    	// 再一次验证,最终还是会调用doProcessConfigurationClass()方法
    	if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    	    parse(bdCand.getBeanClassName(), holder.getBeanName());
    	}
    }
    // 处理@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), true);
    // 处理@ImportResource注解
    ......
    // 处理方法的@bean注解
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    // 如果有父类的话,最终还是会调用doProcessConfigurationClass()方法
    if (sourceClass.getMetadata().hasSuperClass()) {
        ......
    }
ComponentScanAnnotationParser->parse()  // --3
// 在此之前找到了basePackages,如果没有显示定义,那么就是启动类下的路径
ClassPathBeanDefinitionScanner->doScan:
    /* 查找启动类路径下以.class结尾的文件,返回Resource数组
    * 遍历Resource数组,如果属于排除的类,不操作,如果包含@Component或@ManagedBean注解,会添加到candidates集合中
    * 详见ClassPathScanningCandidateComponentProvider->scanCandidateComponents
    */
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

总结

如图所示,启动类配置@SpringBootApplication注解,那么此类attributes亦为full

总结:

  • 两类注解:
    • 一类是@Configuration
    • 一类是@Component,@ComponentScan,@Import,@ImportResource,或方法中有@Bean
  • 资源扫描(Resource数组)
    • 有@Component或@ManagedBean注解的
  1. 从beanDefinitionNames遍历,找出实现了BeanDefinitionRegistryPostProcessor、PriorityOrdered接口的名称,然后实例化,在这里是ConfigurationClassPostProcessor类
  2. 执行该后置处理器的postProcessBeanDefinitionRegistry()方法,遍历beanDefinitionNames,找到符合两类注解之一的bd,在这里是启动类
  3. 解析启动类
  4. 解析配置类:详细
  5. 将上述两步过程中找到的bean通通放入容器的到bdm和bdn变量中

解析可以理解成一个递归的过程,详细如下:

  • 如果带有@Component注解,解析内部类
  • 如果带有@PropertySource注解,解析
  • 如果带有@ComponentScan或者@ComponentScans注解,那么:扫描路径,找到有@Component或@ManagedBean注解的类,添加到bdm和bdn(只在这一步才会立即添加),设置full或者lite属性,遍历、解析
  • 如果带有@Import注解,解析
    • 如果被导入的类是普通类,会新生成configClass对象,在其importBy属性体现是谁导入的它,解析
    • 如果被导入的类实现了ImportSelector接口
      • 如果被导入的类实现DeferredImportSelector接口,说明是延迟导入,发现只有AutoConfigurationImportSelector继承了此接口,在解析配置类的时候(上述第四步过程)返回bean全限定名集合,然后新生成configClass对象,继续解析
      • 否则,会执行selectImports(AnnotationMetadata importingClassMetadata)方法,返回bean全限定名数组,解析全限定名数组所对应的类,然后新生成configClass对象,在其importBy属性体现是谁导入的它(不存在则报错) // --10
    • 如果被导入的类实现了ImportBeanDefinitionRegistrar接口,在原configClass对象的importBeanDefinitionRegistrars属性体现,放入容器时会执行registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法
  • 如果@ImportResource,解析
  • 如果方法有@Bean注解,添加到configClass对象的beanMethods属性中
  • 如果有父类,解析父类
  • 每调用一次解析方法,都会被记录在configurationClasses属性中,在this.reader.loadBeanDefinitions(configClasses)方法中最终完成bean的添加
  • 这里所指带有@Component注解,只要最终包含即为true,如@Service、@Configuration等诸多注解

--10处举例:
假如a import b,b的selectImports()返回c,该段过程如下:

  1. a import b,发现b实现了ImportSelector接口,执行selectImports方法返回c
  2. c是普通类,生成configClass对象,解析c类
  3. c类解析结束回到b类,b类不是普通类,不会生成configClass对象
  4. b类解析结束回到a类,a类@Import解析结束,继续进行@ImportResource的解析

条件注解

条件注解指内部含有@Conditional注解的注解,大部分作用在类和方法。将bean添加到bdm之前,至少会过一遍条件注解,如果不满足条件,就不会注入IoC容器。

  • @ConditionalOnClass(T.class) : 只有当前项目存在T类,才会注入
  • @ConditionalOnBean(value = T.class, name = "str") : 只有执行到此处的IoC容器中存在T类,有bean名称叫str的,才会注入
  • @ConditionalOnMissingBean(value = T.class, name = "str") : 执行到此处的IoC容器中存在T类,或有bean名称叫str的,就不注入
  • @Profile(value = "dev") : 只有当前是环境包含dev,才会注入

附录:beanDefinition种类

  1. AnnotatedGenericBeanDefinition
    1. SpringBoot启动类中SpringApplication构造方法里的参数,称为主源(primarySources成员变量),属于AnnotatedGenericBeanDefinition
    2. 通过spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration设定,属于AnnotatedGenericBeanDefinition
    3. 通过@Import指定类或者内部非静态类有注解@Configuration@Component,属于AnnotatedGenericBeanDefinition
  2. 通过@ComponentScan注解或者内部静态类有注解@Configuration@Component,属于ScannedGenericBeanDefinition
  3. IoC内部的,属于RootBeanDefinition,如后置处理器
  4. 通过@Bean注解,属于ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition
  5. 通过@EnableConfigurationProperties注解,属于GenericBeanDefinition