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