第10节 Spring源码之 ConfigurationClassPostProcessor 处理器
ConfigurationClassPostProcessor
可以说是Spring
中重要的处理器之一。为什么重要,从两个问题开始入手思考?
@Component
@Repository
@Service
@Controller
@Configuration
注解标识的类?@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了!!!
一、 Spring 中是如何识别 @Component
@Repository
@Service
@Controller
@Configuration
注解标识的类?
所谓识别就是在 Spring 容器启动后,如何找到由这些注解标识的类
Spring 容器扫描到相关配置类的依据就是
标签,根据标签配置的
base-package
去扫描对应包路径下的类,然后一一校验
结论: 解析 标签时识别
@Component
@Repository
@Service
@Controller
@Configuration
注解标识的类
- 关于Spring是如何解析标签,参考笔记:第7节 Spring源码之 obtainFreshBeanFactory 方法
标签时是Spring自定义标签,所以走自定义标签解析逻辑
对应的命名空间解析器是
ContextNamespaceHandler
所以标签正在的解析逻辑是在 ComponentScanBeanDefinitionParser
中处理的
ComponentScanBeanDefinitionParser
的parse
方法执行解析逻辑
核心逻辑就是通过创建 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
等,这点很重要!!!
注册之后我们可以看到 beanDefinitionMap 属性中有很多Spring内置的后置处理器,比如 ConfigurationClassPostProcessor
、AutowiredAnnotationBeanProcessor
等
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
:用于处理 @Configuration 注解的后置处理器的beanorg.springframework.context.annotation.internalAutowiredAnnotationProcessor
:于处理@Autowired、@Value、@Inject、@Lookup注解的后置处理器beanorg.springframework.context.annotation.internalCommonAnnotationProcessor
:用于处理JSR-250注解,例如 @Resource、@PostConstruct、@PreDestroy 的后置处理器beanorg.springframework.context.annotation.internalPersistenceAnnotationProcessor
:用于处理JPA注解的后置处理器beanorg.springframework.context.event.internalEventListenerProcessor
:用于处理 @EventListener 注解的后置处理器的bean
该注册Spring内置的后置处理器的逻辑封装在 AnnotationConfigUtils
的registerAnnotationConfigProcessors
方法中。
二、 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. 注解解析时机
第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
方法主要执行 BeanDefinitionRegistryPostProcessor
和 BeanFactoryPostProcessor
后置处理器的方法,即
@FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; } public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; }
BeanDefinitionRegistryPostProcessor
是BeanFactoryPostProcessor
的子类。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
自动装配原理也就清楚了!!!
- 该方法的核心逻辑如下代码块,利用
ConfigurationClassParser
的parse
方法完成@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()); }
ConfigurationClassParser
的parse
方法
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