刚做后端开发的时候,最早接触的是基础的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.factoriesEnableAutoConfiguration相关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,可以在使用方用@EnableAutoConfigurationexclude属性进行排除。

Spring Boot @EnableAutoConfiguration解析相关推荐

  1. Spring Boot @EnableAutoConfiguration和 @Configuration的区别

    Spring Boot @EnableAutoConfiguration和 @Configuration的区别 在Spring Boot中,我们会使用@SpringBootApplication来开启 ...

  2. Spring Boot 原理解析—启动类包扫描原理

    为了何更好的理解该篇内容,请先阅读Spring Boot 原理解析-入口SpringApplication. 我们知道在使用Spring Boot时,Spring会自动加载Spring Boot中启动 ...

  3. Spring Boot 原理解析

    文章目录 前言 一.DemoApplication入口类 二.@SpringBootApplication的原理 1.SpringApplication的run方法 三. SpringApplicat ...

  4. Spring Boot文档阅读笔记-Spring Boot @Bean解析

    利用SpringBoot的@Bean创建一个简单的Bean. Spring的@Bean注解是放在方法上的,带上这个注解的方法会被Spring容器管理.并且这个方法要返回一个值(对象),这个值和对象会被 ...

  5. spring boot定时任务解析

    在SpringBoot中定时任务一般使用的是@Scheduled注解. @Scheduled 1.注解内容: @Target({ElementType.METHOD, ElementType.ANNO ...

  6. spring boot使用jasypt加密原理解析

    目录 版本对应的坑 关键技术点 源码解析 将jar包引入到spring boot中 @EnableAutoConfiguration原理 JasyptSpringBootAutoConfigurati ...

  7. Spring源码深度解析(郝佳)-学习-Spring Boot体系原理

      Spring Boot是由Pivotal团队提供的全新框架,其设计目的用来简化新Spring应用初始化搭建以及开发过程,该框架使用了我写的方式进行配置,从而开发人员不再需要定义样板化的配置,通过这 ...

  8. spring boot 2.1.7启动过程源码解析

    约定 本文基于spring boot 2.1.7.RELEASE进行剖析,使用的spring cloud为Greenwich.SR6版本,github仓库为:spring boot演示.该仓库有多个子 ...

  9. Spring Boot 到底是怎么做到自动配置的?

    作者:祖大帅 juejin.im/post/5b679fbc5188251aad213110 SpringBoot的故事从一个面试题开始 Spring Boot.Spring MVC 和 Spring ...

最新文章

  1. 使用Python,OpenCV和Scikit-Image检测低对比度图像
  2. 学习笔记 Keras:基于Python的深度学习库
  3. Jquery中AJAX参数详细介绍
  4. 阿姆斯特朗数 matlab,数学实验报告
  5. Dubbo(三) 消费者、提供者工程搭建并实现远程调用
  6. dhcpd.conf配置的有关说明
  7. NYOJ 61:传纸条(一)(三维DP)
  8. C++,Java,Pathy这几种编程语言的区别
  9. 小艾果果的伤感空间日志发布:分手后,温暖很稀少
  10. Eclipse中快速查找类或代码
  11. 360面试java经验_360测试开发技术面试题目
  12. 2017 十款最佳iPhone渗透APP及工具
  13. 2010年山东省区县级农作物面积及产量统计数据
  14. java快捷键格式化_在Java中Format的快捷键是什么?
  15. charles android 抓取https 出现unknown简单明了的解决教程
  16. 【Ribbit研习】下载安装
  17. 之和质数c语言题判断,C语言经典例题100例——C语言练习实例33解答(质数判断)...
  18. PDF报表打印 -- Jasper Report
  19. 学习JAVABEANS
  20. UTC时间与Beijing时间转换工具

热门文章

  1. C++排序求最值函数的调用
  2. oracle提示符sqlprompt
  3. 电子杂志+php,phpwind推电子杂志《站长天下》 网罗站长故事
  4. Vue使用ElementUI的Table组件表头与内容不对齐问题
  5. python作业.创建两个文本框,一个按钮。第 1 个文本框绑定任意键事件,敲击键盘任意可显示字符,在交互窗口中显示该字符;第 2 个文本框绑定<a>键事件,敲击键盘 a 字符,在交互窗口中显示 10
  6. 基于SSM旅游纪念品购物网站(idea-javaweb-javaee-j2ee-springboot)订单管理-购物评价-会员管理-购物车实现
  7. 深入浅出Java 23种设计模式,最全PDF版本终于开放下载了!!(文末有福利)
  8. PHP 连接sql server
  9. async/await的用法
  10. 【详细搭建教程】在线客服系统源码3.0防黑版,即时聊天通讯源码 带机器人,防注入 无后门