第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

2023年 7月 22日 87.3k 0

ConfigurationClassPostProcessor可以说是Spring中重要的处理器之一。为什么重要,从两个问题开始入手思考?

  • Spring 中是如何识别
    @Component @Repository @Service @Controller @Configuration注解标识的类?
  • Spring 中是如何解析
    @Component @Repository @Service @Controller @Configuration注解标识的类?
  • 本篇笔记将围绕着两个问题展开讨论。首先,了解一下 Spring 注解开发的简单使用!

    • application-context.xml 配置包扫描路径
    
    
    
       
    
    
    • 编写注解配置类
    @Component
    public class TestComponentAnno {
    }
    
    @Configuration
    public class TestConfiguration {
    }
    
    @Controller
    public class TestControllerAnno {
    }
    
    @Repository
    public class TestRepositoryAnno {
    }
    
    @Service
    public class TestServiceAnno {
    }
    

    这里主要编写 @Component @Repository @Service @Controller @Configuration 标识的类。这里要重点说明一下 @Component 注解和 @Repository @Service @Controller @Configuration 注解的关系。

    注解 含义
    @Component 标识Spring中最普通的组件,可以被注入到Spring容器中进行管理
    @Repository 作用于持久层
    @Service 作用于业务逻辑层
    @Controller 作用于表现层
    @Configuration 标识当前类是配置类

    (1)@Repository @Service @Controller @Configuration@Component 注解的扩展,被用于特定的业务场景;base-package 指定包路径下被以上注解修饰的类都能够被 Spring 容器自动识别

    (2)@Component 注解的功能就是将标识的类自动注入到 Spring 容器中, @Repository @Service @Controller @Configuration 都是被 @Component 注解修饰的注解

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Service {
       @AliasFor(annotation = Component.class)
       String value() default "";
    
    }
    
    • 编写注解测试类
    public class TestAnnoMain {
    
       public static void main(String[] args) {
          testComponent();
       }
    
       public static void testComponent() {
          ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext("application-context.xml");
    
          Teacher user = (Teacher) cx.getBean("teacher");
          System.out.println(user);
       }
    }
    

    运行之后,我们发现beanFactory中已经有了以上注解标识的beanName了!!!
    image.png

    一、 Spring 中是如何识别 @Component @Repository @Service @Controller @Configuration注解标识的类?

    所谓识别就是在 Spring 容器启动后,如何找到由这些注解标识的类

    Spring 容器扫描到相关配置类的依据就是
    标签,根据标签配置的 base-package去扫描对应包路径下的类,然后一一校验

    结论: 解析 标签时识别 @Component @Repository @Service @Controller @Configuration 注解标识的类

    • 关于Spring是如何解析标签,参考笔记:第7节 Spring源码之 obtainFreshBeanFactory 方法
    • 标签时是Spring自定义标签,所以走自定义标签解析逻辑

    image.png

    • 对应的命名空间解析器是 ContextNamespaceHandler

    image.png

    所以标签正在的解析逻辑是在 ComponentScanBeanDefinitionParser 中处理的

    • ComponentScanBeanDefinitionParserparse方法执行解析逻辑

    image.png
    核心逻辑就是通过创建 ClassPathBeanDefinitionScanner对象,然后调用doScan方法来扫描获取对应注解标识的类的BeanDefinitionHolder,然后向容器 beanFactory 中注册 BeanDefinition

    1. ClassPathBeanDefinitionScanner 的 doScan 方法

    protected Set doScan(String... basePackages) {
       Assert.notEmpty(basePackages, "At least one base package must be specified");
       Set beanDefinitions = new LinkedHashSet();
       for (String basePackage : basePackages) {
          
          // 扫描basePackage,将符合要求的bean定义全部找出来
          Set candidates = findCandidateComponents(basePackage);
          
          for (BeanDefinition candidate : candidates) {
             // 解析 @Scope注解,包括 scopeName 和 proxyMode
             ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
             candidate.setScope(scopeMetadata.getScopeName());
    
             // 使用beanName生成器来生成beanName
             String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
             if (candidate instanceof AbstractBeanDefinition) {
                // 处理beanDefinition对象,例如,此bean是否可以自动装配到其他bean中
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
             }
    
             if (candidate instanceof AnnotatedBeanDefinition) {
                // 处理定义在目标类上的通用注解,包括@Lazy,@Primary,@DependsOn,@Role,@Description
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
             }
    
             // 检查beanName是否已经注册过,如果注册过,检查是否兼容
             if (checkCandidate(beanName, candidate)) {
    
                // 将当前遍历bean的bean定义和beanName封装成BeanDefinitionHolder
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                // 根据proxyMode的值,选择是否创建作用域代理
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
    
                // 注册 beanDefinition
                registerBeanDefinition(definitionHolder, this.registry);
             }
          }
       }
       return beanDefinitions;
    }
    

    2. doScan 中的 registreBeanDefinition 方法

    该方法向 beanFactory 中注册一些后置处理器,比如:ConfigurationClassPostProcessor等,这点很重要!!!
    image.png

    注册之后我们可以看到 beanDefinitionMap 属性中有很多Spring内置的后置处理器,比如 ConfigurationClassPostProcessorAutowiredAnnotationBeanProcessor

    image.png

    • org.springframework.context.annotation.internalConfigurationAnnotationProcessor:用于处理 @Configuration 注解的后置处理器的bean
    • org.springframework.context.annotation.internalAutowiredAnnotationProcessor:于处理@Autowired、@Value、@Inject、@Lookup注解的后置处理器bean
    • org.springframework.context.annotation.internalCommonAnnotationProcessor:用于处理JSR-250注解,例如 @Resource、@PostConstruct、@PreDestroy 的后置处理器bean
    • org.springframework.context.annotation.internalPersistenceAnnotationProcessor:用于处理JPA注解的后置处理器bean
    • org.springframework.context.event.internalEventListenerProcessor:用于处理 @EventListener 注解的后置处理器的bean

    该注册Spring内置的后置处理器的逻辑封装在 AnnotationConfigUtilsregisterAnnotationConfigProcessors方法中。

    二、 Spring 中是如何解析 @Component @Repository @Service @Controller @Configuration注解标识的类?

    通过第一个问题,明白了Spring容器是如何将@Component @Repository @Service @Controller @Configuration注解标识的类如何转化成 BeanDefinition的。同时也会向Spring注册一些默认的后置处理器,比如 ConfigurationClassPostProcessor,该处理器就是用来解析 @Component @Repository @Service @Controller @Configuration 这些注解的。

    注意:

    • 第一个问题的引入是通过在 application.xml 中配置 ,然后通过 ClassPathXmlApplicationContext 来启动容器,所以会有解析标签的逻辑
    • 如果我们完全基于注解开发,是不会有走标签解析逻辑,而解析注解的逻辑还在ConfigurationClassPostProcessor中完成的。
    public class TestAnnoMain {
       public static void main(String[] args) {
          testComponentScan();
       }
    
       public static void testComponentScan() {
          ApplicationContext ac = new AnnotationConfigApplicationContext(BeanConfig.class);
          Teacher user = (Teacher) ac.getBean("teacher");
          System.out.println(user);
       }
    }
    
    @Configuration
    @ComponentScan(basePackages = "com.sff.demo")
    @ImportResource(locations = "classpath:application-context.xml")
    public class BeanConfig {
    }
    

    1. 注解解析时机

    image.png

    第9节 Spring源码之 invokeBeanFactoryPostProcessors 方法,调用 invokeBeanFactoryPostProcessors 方法的作用就是:注册 BeanFactoryPostProcessor 处理器,实例化并调用 BeanFactoryPostProcessor 处理器的实现。所以触发 ConfigurationClassPostProcessor处理器的执行是在调用 invokeBeanFactoryPostProcessors 方法中

    /**
     * 实例化并调用 BeanFactoryPostProcessor 处理器
     */
    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
       // 实例化并调用 BeanFactoryPostProcessor 处理器核心逻辑方法
       PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
       if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
          beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
          beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
       }
    }
    

    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors 方法主要执行 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 后置处理器的方法,即

    @FunctionalInterface
    public interface BeanFactoryPostProcessor {
       void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
    
    }
    
    public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
       void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
    }
    
    • BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的子类。
    • BeanFactoryPostProcessor 主要是对 BeanFactory 的操作;而 BeanDefinitionRegistryPostProcessor 主要对 BeanDefinition 的注册操作,在Spring中对注册可以理解为增删改查操作。
    • PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors 中优先执行postProcessBeanDefinitionRegistry方法,然后执行 postProcessBeanFactory 方法。

    结论: @Component @Repository @Service @Controller @Configuration注解的解析是在ConfigurationClassPostProcessor 处理器的 postProcessBeanDefinitionRegistry方法中完成的

    public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
          PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
           // 根据对应的 registry 对象生成 hashcode 值,此对象只会操作一次,如果之前处理过则抛出异常
           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);
    
           // 处理配置类的 BeanDefinition 信息
           processConfigBeanDefinitions(registry);
        }
    }
    

    2. processConfigBeanDefinitions(registry) 方法

    该方法的逻辑非常复杂,处理了Spring注解的解析逻辑,如果理解了该方法逻辑,那么对 SpringBoot自动装配原理也就清楚了!!!

    • 该方法的核心逻辑如下代码块,利用ConfigurationClassParserparse方法完成@Component、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean的解析,即完成 BeanDefinition的转换,并向Spring容器注入BeanDefinition
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    	List configCandidates = new ArrayList();
    
        // 省略代码 ....
        
    	// Parse each @Configuration class
    	ConfigurationClassParser parser = new ConfigurationClassParser(
    			this.metadataReaderFactory, this.problemReporter, this.environment,
    			this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    	// 存放 BeanDefinitionHolder 对象
    	Set candidates = new LinkedHashSet(configCandidates);
    	// 存放扫描包下的所有 bean
    	Set alreadyParsed = new HashSet(configCandidates.size());
    	do {
    		// 解析带有 @Component、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean 的 BeanDefinition
    		parser.parse(candidates);
    		// 将解析完的 Configuration 配置类进行校验,(1) 配置类不能是final;(2) @Bean修饰的方法必须可以重写以支持CGLIB
    		parser.validate();
    		// 获取所有的bean,包括扫描的bean对象、@Import导入的bean对象
    		Set configClasses = new LinkedHashSet(parser.getConfigurationClasses());
    		configClasses.removeAll(alreadyParsed);
    		// Read the model and create bean definitions based on its content
    		if (this.reader == null) {
    			this.reader = new ConfigurationClassBeanDefinitionReader(
    					registry, this.sourceExtractor, this.resourceLoader, this.environment,
    					this.importBeanNameGenerator, parser.getImportRegistry());
    		}
                    
    		// 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器
    		this.reader.loadBeanDefinitions(configClasses);
                    
    		alreadyParsed.addAll(configClasses);
    		candidates.clear();
    		
            // 省略代码 ....
    	} while (!candidates.isEmpty());
    	
    }
    
    • ConfigurationClassParserparse 方法
    public void parse(Set configCandidates) {
       this.deferredImportSelectors = new LinkedList();
    
       for (BeanDefinitionHolder holder : configCandidates) {
          BeanDefinition bd = holder.getBeanDefinition();
          // 根据 BeanDefinition 类型的不同,调用 parse 不同的重载方法,实际上最终都是调用 processConfigurationClass 方法
             if (bd instanceof AnnotatedBeanDefinition) {
    
                // 根据注解元数据和beanName解析配置文件,有注解元数据
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
             } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    
                // 根据Class和beanName解析配置文件,有Class对象
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
             } else {
    
                // 根据className和beanName解析配置文件,读取元数据
                parse(bd.getBeanClassName(), holder.getBeanName());
             }
       }
    
       /**
        * 执行找到的DeferredImportSelector
        * (1) ImportSelector 和 @Import注解 有同的效果,但是实现了 ImportSelector 的类可以条件性的决定导入某些配置
        * (2) DeferredImportSelector的执行时机是在所有其他的配置类被处理后才进行处理
        */
       processDeferredImportSelectors();
    }
    

    parse 方法内部又调用了私有的 parse 方法,核心调用链路如下:
    parse -> processConfigurationClass -> doProcessConfigurationClass,所重点掌握 doProcessConfigurationClass 方法的逻辑,即可了解Spring注解解析的过程,doProcessConfigurationClass 方法逻辑步骤如下:

    • 解析 @Configuration 注解标记的类

    • 解析 @PropertySource 注解标记的类,目的就是加载 properties 配置文件

    • 解析 @ComponentScan 或者 @ComponentScans 注解标记的类,主要是扫描basePackage=com.test属性对应包下 @Bean @Component @Repository @Service @Controller @Configuration 注解标识的类,识别后转化成BeanDefinition

    • 解析 @Import 注解标记的类

    • 解析 @ImportResource 注解标记的类

    • 解析 @Bean 注解标记的方法

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {

    // Recursively process any member (nested) classes first
    // 解析有 @Configuration 注解的类,该注解继承 @Component;递归解析配置类中的内部配置类
    processMemberClasses(configClass, sourceClass);

    // Process any @PropertySource annotations
    // 如果配置类上加了 @PropertySource 注解,那么就解析加载 properties 文件,并将属性添加到 Spring 上下文中
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
    if (this.environment instanceof ConfigurableEnvironment) {
    processPropertySource(propertySource);
    } else {
    logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
    }
    }

    /**
    * Process any @ComponentScan annotations
    * 1. 处理 @ComponentScan 或者 @ComponentScans 注解
    * 2. 将自定义的bean加载到IOC容器,因为扫描到的类可能也添加了 @ComponentScan 和 @ComponentScans 注解,所以需进行递归解析
    * 3. 该步骤逻辑只针对通过配置类配包扫描路径时才会在后置处理中将 basePackage 扫描到的类注册到到容器中
    *
    * @Configuration
    * @ComponentScan(basePackages = "com.sff.demo")
    * public class BeanConfig {
    * }
    *
    * 启动类应该使用 ApplicationContext ac = new AnnotationConfigApplicationContext(BeanConfig.class)
    */
    Set componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    for (AnnotationAttributes componentScan : componentScans) {

    /**
    * The config class is annotated with @ComponentScan -> perform the scan immediately
    * 1. 解析 @ComponentScan 和 @ComponentScans 的扫描包所包含的类,此时的 componentScan 对象就是 @ComponentScan 所有属性的封装
    * 2. 比如 basePackages = com.test, 那么在这一步会扫描出这个包及子包下有
    * @Bean @Component @Repository @Service @Controller @Configuration 注解标记的类,然后将其解析成 BeanDefinition
    */
    Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

    /**
    * Check the set of scanned definitions for any further config classes and parse recursively if needed
    * 1. 通过 componentScanParser.parse 方法扫描到了 basePackages = com.test 包下所有 @Bean @Component @Repository @Service @Controller @Configuration 注解标识的类
    * 2. 然后对这些 BeanDefinition 进行递归解析,因为它们可能会再添加 @ComponentScan、@ComponentScans注解
    */
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    if (bdCand == null) {
    bdCand = holder.getBeanDefinition();
    }
    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    // 通过递归方法进行解析
    parse(bdCand.getBeanClassName(), holder.getBeanName());
    }
    }
    }
    }

    // Process any @Import annotations
    // 解析 @Import 注解
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // Process any @ImportResource annotations
    // 处理 @ImportResource注解,导入 Spring 的配置文件;@ImportResource(locations = {"classpath:beans.xml"})
    AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
    String[] resources = importResource.getStringArray("locations");
    Class

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论