SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析
在上一篇博客中分析了springBoot
启动流程,大体的轮廓只是冰山一角。今天就来看一下springBoot
的亮点功能:自动化装配功能。
先从@SpringBootApplication
开始。在启动流程章节中,我们讲述了SpringBoot2大致的启动步骤,并进行了源码详解。但是在刷新容器这块并未展开,refreshContext(context);简单的一行代码,背后却做了太多事情。所以为了不喧宾夺主,本篇也尽量选取和注解@SpringBootApplication有关的方法讲解。
springBoot启动类加载
首先加载springBoot启动类注入到spring容器中beanDefinitionMap中,看下prepareContext方法中的load方法:load(context, sources.toArray(new Object[0]));
跟进该方法最终会执行BeanDefinitionLoader的load方法:
- private int load(Object source) {
- Assert.notNull(source, "Source must not be null");
- //如果是class类型,启用注解类型
- if (source instanceof Class<?>) {
- return load((Class<?>) source);
- }
- //如果是resource类型,启用xml解析
- if (source instanceof Resource) {
- return load((Resource) source);
- }
- //如果是package类型,启用扫描包,例如:@ComponentScan
- if (source instanceof Package) {
- return load((Package) source);
- }
- //如果是字符串类型,直接加载
- if (source instanceof CharSequence) {
- return load((CharSequence) source);
- }
- throw new IllegalArgumentException("Invalid source type " + source.getClass());
- }
继续跟进load(Class<?> source)
方法:
上述方法判断启动类中是否包含@component注解,可我们的启动类并没有该注解。继续跟进会发现,AnnotationUtils判断是否包含该注解是通过递归实现,注解上的注解若包含指定类型也是可以的。
启动类中包含@SpringBootApplication注解,进一步查找到@SpringBootConfiguration注解,然后查找到@Component注解,最后会查找到@Component注解:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Configuration {
- }
在查找到@Component
注解后,表面该对象为spring bean,然后会将其信息包装成 beanDefinitaion ,添加到容器的 beanDefinitionMap中。如下:
如此一来,我们的启动类就被包装成AnnotatedGenericBeanDefinition
了,后续启动类的处理都基于该对象了。
@EnableAutoConfiguration
@SpringBootApplication注解中包含了自动配置的入口注解:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan(excludeFilters = {
- @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
- @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
- public @interface SpringBootApplication {}
我们跟进去看看@EnableAutoConfiguration
- @AutoConfigurationPackage
- @Import(EnableAutoConfigurationImportSelector.class)
- public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage
- 自动配置包注解
- @Import(AutoConfigurationPackages.Registrar.class)
- public @interface AutoConfigurationPackage {}
@Import(AutoConfigurationPackages.Registrar.class):默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。如下
- @Order(Ordered.HIGHEST_PRECEDENCE)
- static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
- @Override
- public void registerBeanDefinitions(AnnotationMetadata metadata,
- BeanDefinitionRegistry registry) {
- //默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
- register(registry, new PackageImport(metadata).getPackageName());
- }
- @Override
- public Set<Object> determineImports(AnnotationMetadata metadata) {
- return Collections.<Object>singleton(new PackageImport(metadata));
- }
- }
@Import(EnableAutoConfigurationImportSelector.class)
EnableAutoConfigurationImportSelector: 导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。
- //EnableAutoConfigurationImportSelector的父类:AutoConfigurationImportSelector
- @Override
- public String[] selectImports(AnnotationMetadata annotationMetadata) {
- if (!isEnabled(annotationMetadata)) {
- return NO_IMPORTS;
- }
- try {
- AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
- .loadMetadata(this.beanClassLoader);
- AnnotationAttributes attributes = getAttributes(annotationMetadata);
- List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
- configurations = removeDuplicates(configurations);
- configurations = sort(configurations, autoConfigurationMetadata);
- Set<String> exclusions = getExclusions(annotationMetadata, attributes);
- checkExcludedClasses(configurations, exclusions);
- configurations.removeAll(exclusions);
- configurations = filter(configurations, autoConfigurationMetadata);
- fireAutoConfigurationImportEvents(configurations, exclusions);
- return configurations.toArray(new String[configurations.size()]);
- }
- catch (IOException ex) {
- throw new IllegalStateException(ex);
- }
- }
我们主要看第11行List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
会给容器中注入众多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件。获取这些组件后,还要过滤一下这些组件,我们跟进去看看
- protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
- AnnotationAttributes attributes) {
- List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
- //...
- return configurations;
- }
- protected Class<?> getSpringFactoriesLoaderFactoryClass() {
- return EnableAutoConfiguration.class;
- }
- public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
- public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
- String factoryClassName = factoryClass.getName();
- try {
- //从类路径的META-INF/spring.factories中加载所有默认的自动配置类
- Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
- ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
- List<String> result = new ArrayList<String>();
- while (urls.hasMoreElements()) {
- URL url = urls.nextElement();
- Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
- //获取EnableAutoConfiguration指定的所有值,也就是EnableAutoConfiguration.class的值
- String factoryClassNames = properties.getProperty(factoryClassName);
- result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
- }
- return result;
- }
- catch (IOException ex) {
- throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
- }
- }
SpringBoot启动的时候从类路径下的 META-INF/spring.factories中获取EnableAutoConfiguration指定的值,并将这些值作为自动配置类导入到容器中,自动配置类就会生效,最后完成自动配置工作。EnableAutoConfiguration默认在spring-boot-autoconfigure这个包中,如下图
最终有96个自动配置类被加载并注册进Spring容器中
我们也可以将需要自动配置的Bean写入这个文件
自定义starter
首先定义一个配置类模块:
- @Configuration
- @ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
- public class MyAutoConfiguration {
- static {
- System.out.println("myAutoConfiguration init...");
- }
- @Bean
- public SimpleBean simpleBean(){
- return new SimpleBean();
- }
- }
然后定义一个starter模块,里面无需任何代码,pom也无需任何依赖,只需在META-INF下面建一个 spring.factories
文件,添加如下配置:
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- spring.study.startup.bean.MyAutoConfiguration
如图所示:
最后只需在启动类项目的pom中引入我们的 starter 模块即可。
原理
最终在AutoConfigurationImportSelector
解析spring.factories
文件:
springBoot为我们提供的配置类有180多个,但是我们不可能会全部引入。按条件注解 @Conditional或者@ConditionalOnProperty等相关注解进行判断,决定是否需要装配。
我们自定义的配置类也是以相同的逻辑进行装配,我们指定了以下注解:
- @ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
默认为 true,所以自定义的starter成功执行。
SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析的更多相关文章
- Spring Boot之实现自动配置
GITHUB地址:https://github.com/zhangboqing/springboot-learning 一.Spring Boot自动配置原理 自动配置功能是由@SpringBootA ...
- 4、Spring Boot 2.x 自动配置原理
1.4 Spring Boot 自动配置原理 简介 spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate ...
- Spring Boot面试杀手锏————自动配置原理
转:https://blog.csdn.net/u014745069/article/details/83820511 引言不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技 ...
- 学记:为spring boot写一个自动配置
spring boot遵循"约定优于配置"的原则,使用annotation对一些常规的配置项做默认配置,减少或不使用xml配置,让你的项目快速运行起来.spring boot的神奇 ...
- Spring Boot2 系列教程(五)Spring Boot中的 yaml 配置
搞 Spring Boot 的小伙伴都知道,Spring Boot 中的配置文件有两种格式,properties 或者 yaml,一般情况下,两者可以随意使用,选择自己顺手的就行了,那么这两者完全一样 ...
- Spring Boot框架的自动配置
(图片来源于网络,侵删!!!) l @RestController 因为我们例子是写一个web应用,因此写的这个注解,这个注解相当于同时添加@Controller和@ResponseBody注解 l ...
- SpringBoot自动配置注解原理解析
1. SpringBoot启动主程序类: @SpringBootApplication public class DemoApplication { public static void main(S ...
- Spring Boot REST(二)源码分析
Spring Boot REST(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) SpringBoot RE ...
- Spring Boot Dubbo 应用启停源码分析
作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo | grep tid | grep -v "daemon" tid ...
随机推荐
- vc++木马源码免杀一些常用方法
1.字符串连接 ////////////////////////////////////////////////////////////把字符串"canxin"连接起来(字符串连接 ...
- Vue学习笔记和代码记录
## 安装 * 直接引入链接:建议初学者使用:* 通过node.js的NPM安装Vue:* 通过node.js的NPM安装Vue-cli脚手架(推荐安装方式): ## 基础 ### 数据类型 * 字符 ...
- Service Mesh 初体验
前言 计算机软件技术发展到现在,软件架构的演进无不朝着让开发者能够更加轻松快捷地构建大型复杂应用的方向发展.容器技术最初是为了解决运行环境的不一致问题而产生的,随着不断地发展,围绕容器技术衍生出来越来 ...
- python中函数定义与调用顺序问题
def main(): try: mtd(3) except Exception as e: print("程序出现异常:", e) mtd(3) def mtd(a): if a ...
- postman设置环境变量与全局变量
1.环境变量可以设置多组 设置环境变量 编辑环境变量 2.全局变量只能设置一组 可以在Pre-request Script和Tests中设置全局变量 如:pm.globals.set("na ...
- django测试开发-1.开始Hello django!
用python开发出一个web页面的时候,需要找一个支持python语言的web框架.django框架有丰富的文档和学习资料,也是非常成熟的web开发框架,本篇写一个简单的“hello django! ...
- OracleService服务不见了|OracleServiceXE服务没有了
服务里面本来应该有OracleService的(或者是Express版的OracleServiceXE),而服务列表没有此服务项,而启动数据库时出现: TNS监听程序当前无法识别连接描述符中请求的服务 ...
- (五)Unity插件生成
1)新建空的AndroidStudio工程,但是新建过程时最小SDK版本要与unity一致,如下图所示,本次操作均为api16 2)创建Library,如下图所示,新建module,然后选择Andro ...
- 数字麦克风PDM信号采集与STM32 I2S接口应用(三)
本文是数字麦克风笔记文章的数据处理篇. 读取数字麦克风的信号,需要嵌入式驱动和PC应用的结合,驱动负责信号采集,应用代码负责声音分析. 一般而言,在完成特征分析和实验之后,把优化过的代码固化到嵌入式端 ...
- spring cloud 面试题总结
前言,随着分布式的时代到来,现在微服务越来越火了,Spring Cloud已经成为一个面试必问的考点,下面我们就Cloud这一些列的组件来一个入门到面试的过程.开篇前,先让大家看几个常见的Spring ...