刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包<context:component-scan base-package="xxx" /> 或者增加注解@ComponentScan({ "xxx"})。当时觉得挺urgly的,但也没有去研究有没有更好的方式。

直到接触Spring Boot 后,发现其可以自动引入二方包的bean。不过一直没有看这块的实现原理。直到最近面试的时候被问到。所以就看了下实现逻辑。

使用姿势

讲原理前先说下使用姿势。

在project A中定义一个bean。

  1. package com.wangzhi;
  2.  
  3. import org.springframework.stereotype.Service;
  4.  
  5. @Service
  6. public class Dog {
  7. }

并在该project的resources/META-INF/下创建一个叫spring.factories的文件,该文件内容如下

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog

然后在project B中引用project A的jar包。

projectA代码如下:

  1. package com.wangzhi.springbootdemo;
  2.  
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. import org.springframework.context.ConfigurableApplicationContext;
  7. import org.springframework.context.annotation.ComponentScan;
  8.  
  9. @EnableAutoConfiguration
  10. public class SpringBootDemoApplication {
  11.  
  12. public static void main(String[] args) {
  13. ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
  14. System.out.println(context.getBean(com.wangzhi.Dog.class));
  15. }
  16.  
  17. }

打印结果:

  1. com.wangzhi.Dog@3148f668

原理解析

总体分为两个部分:一是收集所有spring.factoriesEnableAutoConfiguration相关bean的类,二是将得到的类注册到spring容器中。

收集bean定义类

在spring容器启动时,会调用到AutoConfigurationImportSelector#getAutoConfigurationEntry

  1. protected AutoConfigurationEntry getAutoConfigurationEntry(
  2. AutoConfigurationMetadata autoConfigurationMetadata,
  3. AnnotationMetadata annotationMetadata) {
  4. if (!isEnabled(annotationMetadata)) {
  5. return EMPTY_ENTRY;
  6. }
  7. // EnableAutoConfiguration注解的属性:exclude,excludeName等
  8. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  9. // 得到所有的Configurations
  10. List<String> configurations = getCandidateConfigurations(annotationMetadata,
  11. attributes);
  12. // 去重
  13. configurations = removeDuplicates(configurations);
  14. // 删除掉exclude中指定的类
  15. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  16. checkExcludedClasses(configurations, exclusions);
  17. configurations.removeAll(exclusions);
  18. configurations = filter(configurations, autoConfigurationMetadata);
  19. fireAutoConfigurationImportEvents(configurations, exclusions);
  20. return new AutoConfigurationEntry(configurations, exclusions);
  21. }
  22. getCandidateConfigurations会调用到方法loadFactoryNames
  23.  
  24. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  25. // factoryClassName为org.springframework.boot.autoconfigure.EnableAutoConfiguration
  26. String factoryClassName = factoryClass.getName();
  27. // 该方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径
  28. return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  29. }
  30.  
  31. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  32.  
  33. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  34. MultiValueMap<String, String> result = cache.get(classLoader);
  35. if (result != null) {
  36. return result;
  37. }
  38.  
  39. try {
  40. // 找到所有的"META-INF/spring.factories"
  41. Enumeration<URL> urls = (classLoader != null ?
  42. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  43. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  44. result = new LinkedMultiValueMap<>();
  45. while (urls.hasMoreElements()) {
  46. URL url = urls.nextElement();
  47. UrlResource resource = new UrlResource(url);
  48. // 读取文件内容,properties类似于HashMap,包含了属性的key和value
  49. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  50. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  51. String factoryClassName = ((String) entry.getKey()).trim();
  52. // 属性文件中可以用','分割多个value
  53. for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
  54. result.add(factoryClassName, factoryName.trim());
  55. }
  56. }
  57. }
  58. cache.put(classLoader, result);
  59. return result;
  60. }
  61. catch (IOException ex) {
  62. throw new IllegalArgumentException("Unable to load factories from location [" +
  63. FACTORIES_RESOURCE_LOCATION + "]", ex);
  64. }
  65. }

注册到容器

在上面的流程中得到了所有在spring.factories中指定的bean的类路径,在processGroupImports方法中会以处理@import注解一样的逻辑将其导入进容器。

  1. public void processGroupImports() {
  2. for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
  3. // getImports即上面得到的所有类路径的封装
  4. grouping.getImports().forEach(entry -> {
  5. ConfigurationClass configurationClass = this.configurationClasses.get(
  6. entry.getMetadata());
  7. try {
  8. // 和处理@Import注解一样
  9. processImports(configurationClass, asSourceClass(configurationClass),
  10. asSourceClasses(entry.getImportClassName()), false);
  11. }
  12. catch (BeanDefinitionStoreException ex) {
  13. throw ex;
  14. }
  15. catch (Throwable ex) {
  16. throw new BeanDefinitionStoreException(
  17. "Failed to process import candidates for configuration class [" +
  18. configurationClass.getMetadata().getClassName() + "]", ex);
  19. }
  20. });
  21. }
  22. }
  23.  
  24. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  25. Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
  26. ...
  27. // 遍历收集到的类路径
  28. for (SourceClass candidate : importCandidates) {
  29. ...
  30. //如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注
  31. // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
  32. // process it as an @Configuration class
  33. this.importStack.registerImport(
  34. currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
  35. // 当作 @Configuration 处理
  36. processConfigurationClass(candidate.asConfigClass(configClass));
  37. ...
  38. }
  39.  
  40. ...
  41. }

可以看到,在第一步收集的bean类定义,最终会被以Configuration一样的处理方式注册到容器中。

End

@EnableAutoConfiguration注解简化了导入了二方包bean的成本。提供一个二方包给其他应用使用,只需要在二方包里将对外暴露的bean定义在spring.factories中就好了。对于不需要的bean,可以在使用方用@EnableAutoConfigurationexclude属性进行排除。

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

Spring Boot @EnableAutoConfiguration解析的更多相关文章

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

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

  2. Spring Boot Redis 解析

    redis使用示例 本示例主要内容 使用lettuce操作redis redis字符串存储(RedisStringController.java) redis对象存储(RedisObjectContr ...

  3. Spring Boot AOP解析

    Spring Boot AOP 面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. AOP(Aspec ...

  4. spring boot 原理解析一(spring boot 基础特征)

    spring boot 提供了完整的介绍 文档:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/documen ...

  5. spring boot EnableAutoConfiguration exclude 无效

    本文链接:https://blog.csdn.net/ID19870510/article/details/79373386 首先讲一下SpringBootApplication注解源码定义为 @Ta ...

  6. spring boot定时任务解析

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

  7. 【转载】Spring boot学习记录(三)-启动原理解析

    前言:本系列文章非本人原创,转自:http://tengj.top/2017/04/24/springboot0/ 正文 我们开发任何一个Spring Boot项目,都会用到如下的启动类 @Sprin ...

  8. Spring Boot入门,源码解析

    目录 1.Spring Boot简介 2.微服务 3.Spring Boot HelloWorld 3.1 创建一个Maven工程 3.2 导入依赖Spring Boot相关的依赖 3.3 编写一个主 ...

  9. 【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理

    Java及Spring Boot新手,首次尝试源码分析,欢迎指正! 一.概述 @EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关.本文将从@Enable ...

随机推荐

  1. LayoutSubviews的调用

    1.当view被添加到另一个view上时调用 2.布局子控件时调用 3.屏幕旋转的时候调用 4.当view的尺寸大小改变的时候调用

  2. Python函数作用域和匿名函数

    匿名函数的定义 全局变量和局部变量的概念 global(全局变量)和 nonlocal(局部变量) 闭包.递归.回调 匿名函数 匿名函数  lambda 语法规则:lambda   参数 : 表达式 ...

  3. bootstrap如何去除自带的样式----导航栏中的菜单实现平滑的过渡到对应的菜单区域-------动态跟换模态框中的内容

    问题1:如何去除bootstap中css中自带的overflow:hidden这个样式 今天遇见在bootstap中轮播图上的  附带图  片不能够显示出来,图片始终有一部分的高度  被隐藏了 后来通 ...

  4. 快读&快写模板【附O2优化】

    快读&快写模板 快读快写,顾名思义,就是提升输入和输出的速度.在这里简单介绍一下几种输入输出的优劣. C++ cin/cout 输入输出:优点是读入的时候不用管数据类型,也就是说不用背scan ...

  5. django haystack报错: ModuleNotFoundError: No module named 'blog.whoosh_cn_backend'

    在配置django haystack时报错: 解决方案: 将ENGINE的值 改为 这样就可以了.

  6. QBXT模拟赛1

    总结 期望得分:\(100 + 80 + 10 = 190\) 实际得分:\(90 + 80 + 10 = 180\) 这是在清北的第一场考试,也是在清北考的最高的一次了吧..本来以为能拿\(190\ ...

  7. 1130不允许连接到MySql server

    连接远程服务器mysql时,报错: 1130-host ... is not allowed to connect to this MySql server 这是因为默认只让localhost的主机连 ...

  8. 数据仓库005 - 复习Linux shell命令 - crontab调度 sh脚本 后台执行 软连接

    一.crontab调度 对于linux 自带crontab而言,      xxx.sh的一般编写格式以#!/bin/bash 解释器开头,可在脚本中加入: date 但是,shell脚本执行 需要 ...

  9. mysql事务隔离级别与设置

    mysql数据库,当且仅当引擎是InnoDB,才支持事务: 1.隔离级别 事务的隔离级别分为:未提交读(read uncommitted).已提交读(read committed).可重复读(repe ...

  10. HBase的java操作,最新API。(查询指定行、列、插入数据等)

    关于HBase环境搭建和HBase的原理架构,请见笔者相关博客. 1.HBase对java有着较优秀的支持,本文将介绍如何使用java操作Hbase. 首先是pom依赖: <dependency ...