Spring Boot @EnableAutoConfiguration解析
刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包<context:component-scan base-package="xxx" />
或者增加注解@ComponentScan({ "xxx"})
。当时觉得挺urgly的,但也没有去研究有没有更好的方式。
直到接触Spring Boot 后,发现其可以自动引入二方包的bean。不过一直没有看这块的实现原理。直到最近面试的时候被问到。所以就看了下实现逻辑。
使用姿势
讲原理前先说下使用姿势。
在project A中定义一个bean。
package com.wangzhi;import org.springframework.stereotype.Service;@Service public class Dog { }
并在该project的resources/META-INF/
下创建一个叫spring.factories
的文件,该文件内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
然后在project B中引用project A的jar包。
projectA代码如下:
package com.wangzhi.springbootdemo;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan;@EnableAutoConfiguration public class SpringBootDemoApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);System.out.println(context.getBean(com.wangzhi.Dog.class));}}
打印结果:
com.wangzhi.Dog@3148f668
原理解析
总体分为两个部分:一是收集所有spring.factories
中EnableAutoConfiguration
相关bean的类,二是将得到的类注册到spring容器中。
收集bean定义类
在spring容器启动时,会调用到AutoConfigurationImportSelector#getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// EnableAutoConfiguration注解的属性:exclude,excludeName等AnnotationAttributes attributes = getAttributes(annotationMetadata);// 得到所有的ConfigurationsList<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);// 去重configurations = removeDuplicates(configurations);// 删除掉exclude中指定的类Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions); }
getCandidateConfigurations
会调用到方法loadFactoryNames
:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {// factoryClassName为org.springframework.boot.autoconfigure.EnableAutoConfigurationString factoryClassName = factoryClass.getName();// 该方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {// 找到所有的"META-INF/spring.factories"Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);// 读取文件内容,properties类似于HashMap,包含了属性的key和valueProperties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryClassName = ((String) entry.getKey()).trim();// 属性文件中可以用','分割多个valuefor (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryClassName, factoryName.trim());}}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}}
注册到容器
在上面的流程中得到了所有在spring.factories
中指定的bean的类路径,在processGroupImports
方法中会以处理@import注解一样的逻辑将其导入进容器。
public void processGroupImports() {for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {// getImports即上面得到的所有类路径的封装grouping.getImports().forEach(entry -> {ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());try {// 和处理@Import注解一样processImports(configurationClass, asSourceClass(configurationClass),asSourceClasses(entry.getImportClassName()), false);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +configurationClass.getMetadata().getClassName() + "]", ex);}});} }private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,Collection<SourceClass> importCandidates, boolean checkForCircularImports) {...// 遍历收集到的类路径for (SourceClass candidate : importCandidates) {...//如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->// process it as an @Configuration classthis.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());// 当作 @Configuration 处理 processConfigurationClass(candidate.asConfigClass(configClass));... }... }
可以看到,在第一步收集的bean类定义,最终会被以Configuration
一样的处理方式注册到容器中。
End
@EnableAutoConfiguration
注解简化了导入了二方包bean的成本。提供一个二方包给其他应用使用,只需要在二方包里将对外暴露的bean定义在spring.factories
中就好了。对于不需要的bean,可以在使用方用@EnableAutoConfiguration
的exclude
属性进行排除。
Spring Boot @EnableAutoConfiguration解析相关推荐
- Spring Boot @EnableAutoConfiguration和 @Configuration的区别
Spring Boot @EnableAutoConfiguration和 @Configuration的区别 在Spring Boot中,我们会使用@SpringBootApplication来开启 ...
- Spring Boot 原理解析—启动类包扫描原理
为了何更好的理解该篇内容,请先阅读Spring Boot 原理解析-入口SpringApplication. 我们知道在使用Spring Boot时,Spring会自动加载Spring Boot中启动 ...
- Spring Boot 原理解析
文章目录 前言 一.DemoApplication入口类 二.@SpringBootApplication的原理 1.SpringApplication的run方法 三. SpringApplicat ...
- Spring Boot文档阅读笔记-Spring Boot @Bean解析
利用SpringBoot的@Bean创建一个简单的Bean. Spring的@Bean注解是放在方法上的,带上这个注解的方法会被Spring容器管理.并且这个方法要返回一个值(对象),这个值和对象会被 ...
- spring boot定时任务解析
在SpringBoot中定时任务一般使用的是@Scheduled注解. @Scheduled 1.注解内容: @Target({ElementType.METHOD, ElementType.ANNO ...
- spring boot使用jasypt加密原理解析
目录 版本对应的坑 关键技术点 源码解析 将jar包引入到spring boot中 @EnableAutoConfiguration原理 JasyptSpringBootAutoConfigurati ...
- Spring源码深度解析(郝佳)-学习-Spring Boot体系原理
Spring Boot是由Pivotal团队提供的全新框架,其设计目的用来简化新Spring应用初始化搭建以及开发过程,该框架使用了我写的方式进行配置,从而开发人员不再需要定义样板化的配置,通过这 ...
- spring boot 2.1.7启动过程源码解析
约定 本文基于spring boot 2.1.7.RELEASE进行剖析,使用的spring cloud为Greenwich.SR6版本,github仓库为:spring boot演示.该仓库有多个子 ...
- Spring Boot 到底是怎么做到自动配置的?
作者:祖大帅 juejin.im/post/5b679fbc5188251aad213110 SpringBoot的故事从一个面试题开始 Spring Boot.Spring MVC 和 Spring ...
最新文章
- 使用Python,OpenCV和Scikit-Image检测低对比度图像
- 学习笔记 Keras:基于Python的深度学习库
- Jquery中AJAX参数详细介绍
- 阿姆斯特朗数 matlab,数学实验报告
- Dubbo(三) 消费者、提供者工程搭建并实现远程调用
- dhcpd.conf配置的有关说明
- NYOJ 61:传纸条(一)(三维DP)
- C++,Java,Pathy这几种编程语言的区别
- 小艾果果的伤感空间日志发布:分手后,温暖很稀少
- Eclipse中快速查找类或代码
- 360面试java经验_360测试开发技术面试题目
- 2017 十款最佳iPhone渗透APP及工具
- 2010年山东省区县级农作物面积及产量统计数据
- java快捷键格式化_在Java中Format的快捷键是什么?
- charles android 抓取https 出现unknown简单明了的解决教程
- 【Ribbit研习】下载安装
- 之和质数c语言题判断,C语言经典例题100例——C语言练习实例33解答(质数判断)...
- PDF报表打印 -- Jasper Report
- 学习JAVABEANS
- UTC时间与Beijing时间转换工具
热门文章
- C++排序求最值函数的调用
- oracle提示符sqlprompt
- 电子杂志+php,phpwind推电子杂志《站长天下》 网罗站长故事
- Vue使用ElementUI的Table组件表头与内容不对齐问题
- python作业.创建两个文本框,一个按钮。第 1 个文本框绑定任意键事件,敲击键盘任意可显示字符,在交互窗口中显示该字符;第 2 个文本框绑定<a>键事件,敲击键盘 a 字符,在交互窗口中显示 10
- 基于SSM旅游纪念品购物网站(idea-javaweb-javaee-j2ee-springboot)订单管理-购物评价-会员管理-购物车实现
- 深入浅出Java 23种设计模式,最全PDF版本终于开放下载了!!(文末有福利)
- PHP 连接sql server
- async/await的用法
- 【详细搭建教程】在线客服系统源码3.0防黑版,即时聊天通讯源码 带机器人,防注入 无后门