当前位置: 首页 > news >正文

源码拆解SpringBoot的自动配置机制

SpringBoot相比于Spring系列的前作,很大的一个亮点就是将配置进行了简化,引入了自动化配置,仅靠几个注解和yml文件就取代了之前XML的繁琐配置机制,这也是SpringBoot的独有特点,下面我们从源码角度,一点点拆开自动配置的机制是如何实现的。

从@SpringBootApplication开始

从SpringBoot项目初始类上的SpringBootApplication注解开始

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class,attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

发现SpringBootApplication注解是一个入口,里面涵盖了很多关于SpringBoot的启动注解,和自动化配置相关的主要是@EnableAutoConfiguration@ComponentScan

  • @EnableAutoConfiguration:这个注解是Spring Boot自动配置的入口点。它指示Spring Boot启动自动配置过程。
  • @ComponentScan:允许你指定一个或多个包,Spring容器将扫描这些包以及其子包中带有@Component@Service@Repository@Controller等注解的类。

 @EnableAutoConfiguration

打开这个注解的源码,看一下此注解主要做了什么

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

作为主要管控SpringBoot自动配置化的复合注解,抛开常用的,@EnableAutoConfiguration中主要的注解是@AutoConfigurationPackage
@Import其中@AutoConfigurationPackage 用于指示Spring Boot自动配置应该从哪个包开始扫描,是一个Spring扫描类的注解。 @Import则是自动配置的核心逻辑入口,源码中通过@Import导入了一个名为 AutoConfigurationImportSelector的类,而这个类 就是自动配置的加载选择器。

自动配置类的筛选流程

下面看一下AutoConfigurationImportSelector这个选择器主要干了什么:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();private static final String[] NO_IMPORTS = new String[0];private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);//……省略部分成员变量protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");return configurations;}//……省略部分方法protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}} }

这里AutoConfigurationImportSelector的内容太多,我只摘取了与自动配置相关的逻辑,关于自动化配置加载,主要是getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata)方法,该方法决定哪些自动配置类应该被导入到Spring的Bean定义中。方法的入参为AutoConfigurationMetadataAnnotationMetadata对象,这两个参数提供了自动配置的元数据和注解的元数据,对接外部配置注解与加载机制的连接。后续的逻辑处理需要用到配置信息。

核心流程

首先根据外部参数annotationMetadata来确定整个系统是否开启了自动化加载机制,这里其实是判断@EnableAutoConfiguration注解或spring.boot.enableautoconfiguration属性的设置,在SpringBoot中可以通过这两种方式决定是否启用自动化配置

然后读取@EnableAutoConfiguration注解的控制信息,根据配置信息进行确定加载哪些自动化配置类(携带@Configuration注解的类或者XML文件),排除哪些不需要的配置类(@EnableAutoConfiguration注解指定控制排除一些自动配置);

后面进行容错机制,包含去除重复的类、筛选出要排除的自动化配置类。根据自动配置元数据进一步过滤配置列表;

触发自动配置导入事件,允许其他监听器在自动配置类被导入之前进行操作后创建并返回一个AutoConfigurationEntry对象,它包含了最终确定要导入的自动配置类列表和被排除的类列表。 

返回对象的处理

返回的AutoConfigurationEntry 实体对象,它包含了最终确定要导入的自动配置类列表和被排除的类列表。这个对象最终会被注册为Spring容器中的一个Bean(Spring中万物皆bean的思想)

AutoConfigurationEntry 是一个内部类,在AutoConfigurationImportSelector类中定义源码如下:

protected static class AutoConfigurationEntry {private final List<String> configurations;private final Set<String> exclusions;private AutoConfigurationEntry() {this.configurations = Collections.emptyList();this.exclusions = Collections.emptySet();}AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {this.configurations = new ArrayList(configurations);this.exclusions = new HashSet(exclusions);}public List<String> getConfigurations() {return this.configurations;}public Set<String> getExclusions() {return this.exclusions;}}

随后Spring的Bean管理会对AutoConfigurationEntry 对象内的候选配置类集合(List<String> configurations)中的类逐一加载,加载中处理每个类上的特殊逻辑, 例如,某个自动配置类的触发条件等等(上篇文章中的LogNoteCondition 的逻辑即是在此处处理的);最后这些自动配置类也会变成bean被加载到Spring的上下文中。被排除的自动配置类(Set<String> exclusions)会被记录,但不会被导入。Spring Boot 会确保这些类在自动配置过程中被忽略。

我们随便启动一个SpringBoot项目,在Debug模式下可以看到处理结束的候选配置类和排除的配置类: 

XXXConfiguration和ConfigurationProperties的注入

debug 里,我们看到了成功装配了AutoConfigurationEntry 对象内的候选配置类,但是这里只是配置类的路径,却还没有配置类的实例对象以及对象内的自动配置值,基于上述的debug,我们选择ServletWebServerFactoryAutoConfiguration进行跟踪

@Configuration(proxyBeanMethods = false
)
@AutoConfigureOrder(Integer.MIN_VALUE)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {//……省略部分代码
}

这里对于ServletWebServerFactoryAutoConfiguration在进行bean加载时需要先处理@EnableConfigurationProperties(ServerProperties.class) ,它的意思是启用指定类的@ConfigurationProperties注解功能;目的是将配置文件中对应的值和 ServerProperties 绑定起来;并把
ServerProperties 加入到 IOC 容器中。

ServerProperties源码:

@ConfigurationProperties(prefix = "server",ignoreUnknownFields = true
)
public class ServerProperties {private Integer port;private InetAddress address;@NestedConfigurationPropertyprivate final ErrorProperties error = new ErrorProperties();private ForwardHeadersStrategy forwardHeadersStrategy;private String serverHeader;private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);private Shutdown shutdown;@NestedConfigurationPropertyprivate Ssl ssl;@NestedConfigurationPropertyprivate final Compression compression;@NestedConfigurationPropertyprivate final Http2 http2;private final Servlet servlet;// ……省略
}

ServerProperties中使用 @ConfigurationProperties注解绑定属性映射文件中的 server 开头的属性。

也就是大部分SpringBoot的yml文件中的:

server:port: 8081servlet:context-path: /testencoding:charset: UTF-8enabled: trueforce: true
spring:#省略后面配置

补充:自定义starter与SpringBoot的合并过程

在 上篇自定义starter中我们对于基本启动类,需要创建META-INFO下的spring.factories文件和主启动类中的@ComponentScan(basePackages = "org.example.lognote.*")注解,这里就连上了。

对于外部使用自定义的starter的SpringBoot项目,它在@EnableAutoConfiguration注解时便会触发自动配置类的筛选流程,通过筛选流程中的getAutoConfigurationEntry方法,根据外部starter项目中的spring.factories配置找到其启动类加载到AutoConfigurationEntry 对象中,随后SpringBean加载机制处理AutoConfigurationEntry 对象中的候选配置类时,starter中的启动类自然作为这个外部SpringBoot项目中的一个简单@Configuration被加载。通过starter项目启动类上的@ComponentScan注解将整个starter中的其他bean都加载到SpringBoot项目中的上下文里。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • hdfs命令
  • 采集PCM,将base64片段转换为wav音频文件
  • RuoYi-Vue 全新 Pro 版本:清除url地址栏路由参数
  • mysql面试(四)
  • vue 搜索框
  • 【Linux】gcc简介+编译过程
  • VIsual Studio:为同一解决方案下多个项目分别指定不同的编译器
  • 音视频入门基础:H.264专题(15)——FFmpeg源码中通过SPS属性获取视频帧率的实现
  • Varjo XR-4系列现已获得达索3DEXPERIENCE平台官方支持
  • 计算机网络-配置路由器ACL(访问控制列表)
  • 美摄科技企业级视频拍摄与编辑SDK解决方案
  • bug bug bug
  • 基于 HTML+ECharts 实现的大数据可视化平台模板(含源码)
  • 如何优化 Selenium 和 BeautifulSoup 的集成以提高数据抓取的效率?
  • MySQL:在 SELECT 查询中过滤数据
  • 【159天】尚学堂高琪Java300集视频精华笔记(128)
  • CSS实用技巧干货
  • css选择器
  • egg(89)--egg之redis的发布和订阅
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • Java编程基础24——递归练习
  • linux安装openssl、swoole等扩展的具体步骤
  • Linux下的乱码问题
  • scrapy学习之路4(itemloder的使用)
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • Vue全家桶实现一个Web App
  • XML已死 ?
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 网络应用优化——时延与带宽
  • 微信小程序--------语音识别(前端自己也能玩)
  • 我的业余项目总结
  • 我建了一个叫Hello World的项目
  • 新手搭建网站的主要流程
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • ​ArcGIS Pro 如何批量删除字段
  • ​queue --- 一个同步的队列类​
  • # centos7下FFmpeg环境部署记录
  • # Maven错误Error executing Maven
  • # 利刃出鞘_Tomcat 核心原理解析(二)
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (24)(24.1) FPV和仿真的机载OSD(三)
  • (Forward) Music Player: From UI Proposal to Code
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (分布式缓存)Redis分片集群
  • (离散数学)逻辑连接词
  • (三分钟)速览传统边缘检测算子
  • (一)Docker基本介绍
  • (自适应手机端)行业协会机构网站模板
  • ***通过什么方式***网吧
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...