Spring Boot源码探索——自动配置的内部实现
前面写了两篇文章 《Spring Boot自动配置的魔法是怎么实现的》和 《Spring Boot起步依赖:定制starter》,分别分析了Spring Boot的自动配置和起步依赖。在感慨Spring Boot的方便之余,也不禁产生了一点疑惑,Spring Boot 内部究竟是怎么触发自动配置和起步依赖的?
先看下面这段代码,我们只需要调用SpringApplication.run()方法就能启动整个Spring应用。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
非常简单的代码,要了解Spring Boot运行原理,我们先从这个方法开始:
public ConfigurableApplicationContext run(String... args) {
...
...
try {
...
...
// 看到refresh很容易想到AbstractApplicationContext的refresh()方法
refreshContext(context);
...
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
...
}
run()方法里面有很多方法逻辑,我们不必每个都去深究,对于了解Spring的同学来说,看到refreshContext(context);很容易想到AbstractApplicationContext的refresh()方法。AbstractApplicationContext的refresh()方法正是Spring 核心功能的实现逻辑。我们来看看refreshContext()方法的代码,会发现里面有一个refresh()方法:
private void refreshContext(ConfigurableApplicationContext context) {
// 这个方法更类似了
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
继续到refresh()方法中去:
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
在这里我们看到应用上下文applicationContext被强转为AbstractApplicationContext,从而去调用AbstractApplicationContext的refresh()方法。AbstractApplicationContext的refresh()方法会做很多事情:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新的上下文环境
prepareRefresh();
// 初始化BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 对BeanFactory进行各种功能填充
prepareBeanFactory(beanFactory);
try {
// 子类覆盖方法做额外的处理
postProcessBeanFactory(beanFactory);
// 激活各种BeanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用是在getBean时
registerBeanPostProcessors(beanFactory);
// 国际化处理
initMessageSource();
// 初始化应用消息广播器,并放入“applicationEventMulticaster”Bean中
initApplicationEventMulticaster();
// 留给子类来初始化其它的Bean
onRefresh();
// 在所有注册的Bean中查找Listener Bean,并注册消息广播器中
registerListeners();
// 初始化单例Bean(非延迟初始化的)
finishBeanFactoryInitialization(beanFactory);
// 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知其它人
finishRefresh();
}
...
}
}
我们主要关注invokeBeanFactoryPostProcessors(beanFactory)方法,因为这个方法激活各种BeanFactory处理器,包括BeanDefinitionRegistryPostProcessor。BeanDefinitionRegistryPostProcessor可以在Spring 处理Bean的定义之前,注册Bean的定义。现在的项目基本都用Java Config的方式来配置Bean,这些Bean就是通过BeanDefinitionRegistryPostProcessor注册到Spring中的。
invokeBeanFactoryPostProcessors(beanFactory)方法中的代码挺多的,一开始可能根本找不到我们想要的代码。我们想知道Spring Boot是如何实现自动配置的,自动配置根本上来说,是根据判断条件来加载Bean。现在找到了Spring加载Bean的代码,但是目前我们在这个方法里根本看不到任何与Spring Boot有关的逻辑,只知道Spring Boot确实调用了这个方法。既然Spring Boot调用了这个方法,尽管情况不明,我们也可以试着debug一下。我直接把断点打在了invokeBeanFactoryPostProcessors()方法中:
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<>();
if (beanFactory instanceof BeanDefinitionRegistry) {
...
...
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
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);
}
}
...
}
...
}
上面这段代码会从Spring的BeanFactory中寻找BeanDefinitionRegistryPostProcessor类型的Bean,如果找到了就加载它。我们为什么要注意这段代码呢?前面说过,BeanDefinitionRegistryPostProcessor可以向Spring注册Bean定义,那么Spring Boot自动配置的那些Bean也应该是由一个我们现在不清楚的BeanDefinitionRegistryPostProcessor来注册的。如果你正在调试的SpringBoot应用没有配置任何额外东西的话,那么这里的调试信息,应该和下面的图相似:

我们看到一个 ConfigurationClassPostProcessor 的类,阅读这个类的文档可以知道,正是这个类处理 @Confiugration 注解的类。正好,@SpringBootApplication 间接继承 @Confiugration。显然,ConfigurationClassPostProcessor 将会对Spring Boot的配置进行处理。我们进一步去 ConfigurationClassPostProcessor 中查看:
/**
* Derive further bean definitions from the configuration classes in the registry.
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
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);
processConfigBeanDefinitions()方法(registry);
}
ConfigurationClassPostProcessor 中的 postProcessBeanDefinitionRegistry() 这个方法,是在上面的 AbstractApplicationContext 类中被调用。再继续看 processConfigBeanDefinitions() 方法:
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
...
...
// Parse each @Configuration class
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 {
parser.parse(candidates);
parser.validate();
...
}while (!candidates.isEmpty());
...
}
又是一个比较复杂的方法,不过好在源码中的注释表明 ConfigurationClassParser 类负责进行 @Configuration 类的解析。我们直接看 ConfigurationClassParser 类的 parse() 方法:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
processDeferredImportSelectors();
}
这个方法主要有两个逻辑,parse()方法对BeanDefinition进行解析,递归找出所有的 @Configuration 注解的类。接着processDeferredImportSelectors()处理 DeferredImportSelector 类。这个 DeferredImportSelector 类看源码文档注释:
A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans have been processed. This type of selector can be particularly useful when the selected imports are {@code @Conditional}.
意思就是说,这个类能够处理 @Conditional 类型的配置。恰好,我们知道,Spring Boot就是依赖 @Conditional 实现的自动配置。我们应该是找对了地方。
回过头来看 Spring Boot 的 @EnableAutoConfiguration 注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
...
}
我们看到了 @Import(AutoConfigurationImportSelector.class) 注解,其中它导入的 AutoConfigurationImportSelector 就是 DeferredImportSelector 接口的实现类。这也就意味着,AutoConfigurationImportSelector 是Spring Boot 自动配置的一个关键类。我们继续看看 AutoConfigurationImportSelector 类的实现:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// filter()方法是关键
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
很自然地,我们肯定先看从 ImportSelector 继承的方法 selectImports(),在这个方法里面,从字面意思的角度,我们肯定也会先看filter()方法:
private List<String> filter(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
...
...
}
这里我们看到一个 AutoConfigurationImportFilter 类,程序获取这个类,并调用它的 match()方法,这让我们联想到 Condition 类的 macthes() 方法。我们查看这个类的继承体系,会发现它是 OnClassCondition 类的父类。而 OnClassCondition 负责处理 @ConditionalOnClass 和 @ConditionalOnMissingClass 两个注解。
到这里,Spring Boot 自动配置的流程大致是清楚了。用一张程序调用栈的图片或许能够表达清楚:

这里我们只发现了 OnClassCondition ,实际上Spring Boot中还有很多其他的 Conditon,比如:OnBeanCondition、OnPropertyCondition、OnResourceCondition等等。他们基本上也是类似的调用流程。
Spring Boot源码探索——自动配置的内部实现的更多相关文章
- 曹工说Spring Boot源码(27)-- Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了.md
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 正 ...
- Spring Boot源码中模块详解
Spring Boot源码中模块详解 一.源码 spring boot2.1版本源码地址:https://github.com/spring-projects/spring-boot/tree/2.1 ...
- 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
随机推荐
- vue-cli3 + ts 定义全局方法
一.定义全局方法不生效 虽然在main.ts当中定义了全局方法,但是在使用的时候根本找不到,也是无语了. 二.解决方法 我在网上找了很多方法,其中很多大神都是这样做的: 但是,我这样写了还是不生效 ...
- eNSP基于接口地址池的dhcp服务
拓扑图如下 基于接口的dhcp是最简单的一种 我们对路由器的两个端口分别设置ip地址为192.168.1.254 192.168.2.254 然后分别进入接口进行下一步配置 dhcp select i ...
- QuickTime专业版 pro 注册码
打开QuickTime Player下拉编辑菜单--选偏好设置--注册 Name: Dawn M Fredette Key: 4UJ2-5NLF-HFFA-9JW3-X2KV 重新启动 QuickTi ...
- Select 查询语句
1.1 查询语句 1.1.1 select select 用于从数据看查询数据.语法 select field1,filed2,.. . from tablename [where cond ...
- Zuul【基础配置】
概述:zuul底层是基于servlet,是由一系列的filter链构成. 1.路由配置 a.单例serverId映射 zuul: routes: client-a: path: /client/** ...
- python 之 前端开发( JavaScript变量、数据类型、内置对象、运算符、流程控制、函数)
11.4 JavaScript 11.41 变量 1.声明变量的语法 // 1. 先声明后定义 var name; // 声明变量时无需指定类型,变量name可以接受任意类型 name= " ...
- C/C++中vector与list的区别
1.vector数据结构vector和数组类似,拥有一段连续的内存空间,并且起始地址不变.因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存 ...
- 'telent' 不是内部或外部命令,也不是可运行的程序或批处理文件。
今天在Windows 7操作系统中安装了memcached内存缓存软件,本想借助Windows的telnet程序向memcached缓存管理系统中添加一些数据,可是命令输入后竟然出现了如下图这样的错误 ...
- golang开发:(一)开发环境搭建vagrant+VirtualBox
开发环境介绍 不管何种开发语言,目前用的比较多的开发环境基本就是Vagrant+VirtualBox搭建的虚拟开发环境,这种开发环境的好处就是一次搭建处处可用,各个平台和系统都可以使用.开发团队中,可 ...
- 基于thymeleaf实现简单登录
1.引入thymeleaf.静态资源等依赖 <dependency> <groupId>org.springframework.boot</groupId> < ...