Spring注解@Configuration是如何被处理的?
从SpringApplication开始
一般情况下启动SpringBoot都是新建一个类包含main方法,然后使用SpringApplication.run来启动程序:
@SpringBootApplication
public class AutoConfigApplication {
public static void main(String[] args){
ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(AutoConfigApplication.class,args);
}
}
SpringApplication.run接收两个参数分别为:primarySource、运行参数(args),上面的代码使用AutoConfigApplication.class作为primarySource。SpringApplication还有一个实例方法也叫run,SpringBoot的大部分启动都由实例run方法来完成的,其中构造ApplicationContext由createApplicationContext方法完成:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
createApplicationContext根据this.webApplicationType来构造ApplicationContext,不同的环境都会使用不同的实例,但本文非web环境所有构造的时候会使用AnnotationConfigApplicationContext类。创建AnnotationConfigApplicationContext的时候会调用默认构造方法:
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
AnnotationConfigApplicationContext默认构造函数创建两个对象:
- reader(AnnotatedBeanDefinitionReader):用于手动注册bean
- scanner(ClassPathBeanDefinitionScanner): 用于扫描Component、Repository、Service等注解
AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner会注册一些注解处理器,注册的方式都是使用AnnotationConfigUtils的registerAnnotationConfigProcessors方法:
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
...
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
...
return beanDefs;
}
最终AnnotationConfigApplicationContext构造方法执行完成后ApplicationContext会有以下BeanDefinition:

构造完ApplicationContext后SpringApplicaiton紧接着会加载primarySource,上面提到 过primarySource是在运行的时候传递进来的(AutoConfigApplication.class),加载过程中不贴代码了,只要知道最终ApplicaitonContext中会多一个AutoConfigApplication的BeanDefinition:

小结
总的来说SpringApplicaiton主要干了这些事:
- 创建AnnotationConfigApplicationContext
- 加载一些处理注解的后处理器如:ConfigurationClassPostProcessor
- 将
primarySource加载进ApplicationContext
最重要的一点是,现在是有一个AnnotationConfigApplicationContext里面包含了primarySource(AutoConfigApplication)以及ConfigurationClassPostProcessor。打个断点在ApplicaitonContext刷新之前打印下context中的bean的名称,可以确定这样说没毛病!

@Configuration啥时候被解析?
虽说有了primarySource和ConfigurationClassPostProcessor后处理器,还是需要有个执行的入口。ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor的实现类,BeanDefinitionRegistryPostProcessor会在ApplicationContext的refresh操作时被处理:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
invokeBeanFactoryPostProcessors(beanFactory);
...
}
}
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
...
//找出所有类型为BeanDefinitionRegistryPostProcessor的bean的名称
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
//执行BeanDefinitionRegistryPostProcessor
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
...
}
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
//调用postProcessBeanDefinitionRegistry方法
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
invokeBeanDefinitionRegistryPostProcessors会调用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,通过断点调试工具确认下ConfigurationClassPostProcessor有没有在这一步被处理:

调试输出postProcessors集合里面有一个了ConfigurationClassPostProcessor元素,说明了ConfigurationClassPostProcessor的执行入口没有问题。
ConfigurationClassPostProcessor处理器
ConfigurationClassPostProcessor首先会判断在ApplicationContext中的bean是否被@Configuration注解标记,然后使用ConfigurationClassParser来解析@Configuration,ConfigurationClassPostProcessor的解析@Configuration的大致流程:
- 使用ConfigurationClassUtils.checkConfigurationClassCandidate检查BeanDefinition是否@Configuration注解标记
- 对@Configuration进行排序
- 使用ConfigurationClassParser解析@Configuration注解的信息
- 使用ConfigurationClassBeanDefinitionReader解析BeanDefinition
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//获取所有BeanDefinitio名称
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
//如果是full、lite则说明已经处理过的类
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
//检查BeanDefinition是否有@Configuration注解
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
//如果没有找到@Configuration标记的类,则返回不作处理也
if (configCandidates.isEmpty()) {
return;
}
//对@Configuration进行排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
...
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
//解析@Configuration class
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
//读取BeanDefinition
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
...
}
while (!candidates.isEmpty());
...
}
最后还是通过调试工具看一下示例中的的启动类AutoConfigApplication没有被处理:

图上显示configCandidates中有一个名称为autoConfigApplication的BeanDefinition的元素,说明AutoConfigApplication会被当作配置类解析,但是AutoConfigApplication并没有使用@Configuration注解,为什么还会被当做配置类呢?其实@Configuration在@SpringBootApplication注解中:

红色背景列出来的就是@Configuration注解,它是@SpringBootConfiguration的元注解。
Spring注解@Configuration是如何被处理的?的更多相关文章
- Spring注解 @Configuration
Spring注解 @Configuration 一.@Configuration的作用 二.@Configuration的Spring容器启动方式 三.不加@Configuration的@Bean的解 ...
- [转]Spring注解-@Configuration注解、@Bean注解以及配置自动扫描、bean作用域
1.@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,作用为:配置spring容器(应用上下文) package com.test.s ...
- Spring注解-@Configuration注解、@Bean注解以及配置自动扫描、bean作用域
1.@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,作用为:配置spring容器(应用上下文) package com.test.s ...
- Spring注解@Configuration和Java Config
1.从Spring 3起,JavaConfig功能已经包含在Spring核心模块,它允许开发者将bean定义和在Spring配置XML文件到Java类中.但是,仍然允许使用经典的XML方式来定义bea ...
- 【Spring注解开发】组件注册-使用@Configuration和@Bean给容器中注册组件
写在前面 在之前的Spring版本中,我们只能通过写XML配置文件来定义我们的Bean,XML配置不仅繁琐,而且很容易出错,稍有不慎就会导致编写的应用程序各种报错,排查半天,发现是XML文件配置不对! ...
- spring注解源码分析--how does autowired works?
1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...
- Spring之@Configuration配置解析
1.简单的示例: @Configuration @EnableConfigurationProperties({DemoProperties.class}) public class DemoConf ...
- Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariable (转)
最近需要做些接口服务,服务协议定为JSON,为了整合在Spring中,一开始确实费了很大的劲,经朋友提醒才发现,SpringMVC已经强悍到如此地步,佩服! 相关参考: Spring 注解学习手札(一 ...
- Spring 注解驱动(一)基本使用规则
Spring 注解驱动(一)基本使用规则 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 @Configur ...
随机推荐
- TensorFlow2.0(7):激活函数
.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...
- Hydra爆破神器使用
参数详解: -R 根据上一次进度继续破解-S 使用SSL协议连接-s 指定端口-l 指定用户名-L 指定用户名字典(文件)-p 指定密码破解-P 指定密码字典(文件)-e 空密码探测和指定用户密码探测 ...
- phpstorm中xdebug配置和断点调试
今天赶紧把这个搞了 运行环境: phpStorm 2018 PHP 5.45 nts VC9 Xdebug 2.4.1 0x01 PHP安装xdebug扩展 自己之前装一直没装起 原来是版本问题 下 ...
- 安装VMware Tools显示灰色正确解决办法
首先问题如下: 解决办法如下:1.关闭虚拟机: 2.在虚拟机设置分别设置CD/DVD.CD/DVD2和软盘为自动检测三个步骤: 3.再重启虚拟机,灰色字即点亮. 大功告成,如果解决了你的问题,点个赞鼓 ...
- PMP 德尔菲技术
1.德尔菲技术,必须遵守以下几个规则: 每个专家只与主持人单线联系. 专家之间完全背靠背,更不能进行讨论.为保证专家提出独立见解,甚至需要把专家分散在不同的物理地点. 专家以匿名的书面形式提出意见. ...
- CSAPP: 位操作实现基本运算
目录 实验要求 实现代码 1.pow2plus1 2.pow2plus4 3.bitXor 4.tmin 5.isTmax 6.allOddBits 7.negate 8.isAsciiDigit 9 ...
- js静态属性,实例属性,封装性,prototype,__proto__综合解析
原创作品,转载请注明来源,sogeisetsu,我的csdn上也有这篇文章csdn js静态属性,实例属性,封装性,prototype,__proto__综合解析 下面是我在写博客的源代码,您可以先不 ...
- 【Linux】【自学笔记】Linux下面docker安装mysql
写在前面: 捣腾继续,之前把一个SpringBoot的程序安装在docker上面,参考链接:https://www.cnblogs.com/aki-stones/p/2019-11-01-note.h ...
- 转:如何让phpmyadmin输入密码再进入
对于很多不熟悉PHP环境安装的朋友来说,用集成环境可以更快的上手,更方便的搭建PHP的运行环境,但是,WAMP的集成环境仅仅是将底层基础工作做好了,有些个别关键的配置操作并没有集成到环境安装中,所以给 ...
- 【ARC064-F】【XSY2575】Rotated Palindromes(DP)(字符串)
Description 然而,由于小C沉迷于制作游戏,他完全忘记了自己作为国家集训队的一员,还有156道作业题等他完成.还有一天作业就要截止了,而他一题还没有做.于是他赶紧挑了一道看起来最简单的题: ...