本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等

我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序:

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

和我们之前使用普通Spring时繁琐的配置相比简直不要太方便,那么你知道SpringBoot实现这些的原理么

首先我们看到类上方包含了一个@SpringBootApplication注解

@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 {};
}

这个注解上边包含的东西还是比较多的,咱们先看一下两个简单的热热身

@ComponentScan 注解

@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

这个注解咱们都是比较熟悉的,无非就是自动扫描并加载符合条件的Bean到容器中,这个注解会默认扫描声明类所在的包开始扫描,例如:

cn.shiyujun.Demo类上标注了@ComponentScan 注解,则cn.shiyujun.controllercn.shiyujun.service等等包下的类都可以被扫描到

这个注解一共包含以下几个属性:

basePackages:指定多个包名进行扫描
basePackageClasses:对指定的类和接口所属的包进行扫
excludeFilters:指定不扫描的过滤器
includeFilters:指定扫描的过滤器
lazyInit:是否对注册扫描的bean设置为懒加载
nameGenerator:为扫描到的bean自动命名
resourcePattern:控制可用于扫描的类文件
scopedProxy:指定代理是否应该被扫描
scopeResolver:指定扫描bean的范围
useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行检测

@SpringBootConfiguration注解

这个注解更简单了,它只是对Configuration注解的一个封装而已

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

EnableAutoConfiguration注解

这个注解可是重头戏了,SpringBoot号称的约定大于配置,也就是本文的重点自动装配的原理就在这里了

@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {};
}

简单概括一下,这个注解存在的意义就是:利用@Import注解,将所有符合自动装配条件的bean注入到IOC容器中,关于@Import注解原理这里就不再阐述,感兴趣的同学可以参考此篇文章:Spring @Import注解源码解析

进入类AutoConfigurationImportSelector,观察其selectImports方法,这个方法执行完毕后,Spring会把这个方法返回的类的全限定名数组里的所有的类都注入到IOC容器中

public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
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.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}

观察上方代码:

  1. 第一行if时会首先判断当前系统是否禁用了自动装配的功能,判断的代码如下:
 protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}
  1. 如果当前系统禁用了自动装配的功能则会返回如下这个空的数组,后续也就无法注入bean了
private static final String[] NO_IMPORTS = new String[0];
  1. 此时如果没有禁用自动装配则进入else分枝,第一步操作首先会去加载所有Spring预先定义的配置条件信息,这些配置信息在org.springframework.boot.autoconfigure包下的META-INF/spring-autoconfigure-metadata.properties文件中

  2. 这些配置条件主要含义大致是这样的:如果你要自动装配某个类的话,你觉得先存在哪些类或者哪些配置文件等等条件,这些条件的判断主要是利用了@ConditionalXXX注解,关于@ConditionalXXX系列注解可以参考这篇文章:SpringBoot条件注解@Conditional

  3. 这个文件里的内容格式是这样的:

```

org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet

org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,io.micrometer.core.instrument.MeterRegistry

org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration

 3. 具体的加载代码就不列出了,无法就是个读取配置文件
4. 这里放个加载之后的结果图:
![file](https://img2018.cnblogs.com/blog/1431571/201909/1431571-20190920090843541-1514005316.jpg)
4. 获取`@EnableAutoConfiguration`注解上的exclude、excludeName属性,这两个属性的作用都是排除一些类的
5. 这里又是关键的一步,可以看到刚才图片中spring-autoconfigure-metadata.properties文件的上方存在一个文件spring.factories,这个文件可就不止存在于`org.springframework.boot.autoconfigure`包里了,所有的包里都有可能存在这个文件,所以这一步是加载整个项目所有的spring.factories文件。这个文件的格式是这样的

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration

6. 这里存在一个知识点,SpringBoot中的star就是依靠这个文件完成的,假如我们需要自定义一个SpringBoot的Star,就可以在我们的项目的META-INF文件夹下新建一个spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.shiyujun.TestAutoConfiguration

这样当别的项目依赖我们的项目时就会自动把我们的`TestAutoConfiguration`类注入到Spring容器中
7. 删除重复的自动配置类
8. 下面三行就是去除我们指定排除的配置类
9. 接着这一行的逻辑稍微复杂一些,主要就是根据加载的配置条件信息来判断各个配置类上的`@ConditionalXXX`系列注解是否满足需求
10. 最后就是发布自动装配完成事件,然后返回所有能够自动装配的类的全限定名 到了这里我们已经把SpringBoot自动装配的原理搞清楚了,但是总感觉差点什么,那我们从这些自动装配的类里面挑一个我们比较熟悉的关于Servlet的类来看看咋回事吧:

@Configuration

@ConditionalOnWebApplication(

type = Type.SERVLET

)

public class ServletEndpointManagementContextConfiguration {

public ServletEndpointManagementContextConfiguration() {

}

@Bean
public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(WebEndpointProperties properties) {
Exposure exposure = properties.getExposure();
return new ExposeExcludePropertyEndpointFilter(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude(), new String[0]);
} @Configuration
@ConditionalOnClass({ResourceConfig.class})
@ConditionalOnMissingClass({"org.springframework.web.servlet.DispatcherServlet"})
public class JerseyServletEndpointManagementContextConfiguration {
public JerseyServletEndpointManagementContextConfiguration() {
} @Bean
public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
return new ServletEndpointRegistrar(properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
}
} @Configuration
@ConditionalOnClass({DispatcherServlet.class})
public class WebMvcServletEndpointManagementContextConfiguration {
private final ApplicationContext context; public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) {
this.context = context;
} @Bean
public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
DispatcherServletPathProvider servletPathProvider = (DispatcherServletPathProvider)this.context.getBean(DispatcherServletPathProvider.class);
String servletPath = servletPathProvider.getServletPath();
if (servletPath.equals("/")) {
servletPath = "";
} return new ServletEndpointRegistrar(servletPath + properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
}
}

}

自上而下观察整个类的代码,你会发现这些自动装配的套路都是一样的
1. 如果当前是Servlet环境则装配这个bean
2. 当存在类`ResourceConfig`以及不存在类`DispatcherServlet`时装配`JerseyServletEndpointManagementContextConfiguration`
3. 当存在`DispatcherServlet`类时装配`WebMvcServletEndpointManagementContextConfiguration`
4. 接下来如果还有面试官问你,你会了么?

SpringBoot自动装配原理解析的更多相关文章

  1. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  2. springboot自动装配原理,写一个自己的start

    springboot自动装配原理 第一次使用springboot的时候,都感觉很神奇.只要加入一个maven的依赖,写几行配置,就能注入redisTemple,rabbitmqTemple等对象. 这 ...

  3. SpringBoot | 2.1 SpringBoot自动装配原理

    @ 目录 前言 1. 引入配置文件与配置绑定 @ImportResource @ConfigurationProperties 1.1 @ConfigurationProperties + @Enab ...

  4. springboot自动装配原理

    最近开始学习spring源码,看各种文章的时候看到了springboot自动装配实现原理.用自己的话简单概括下. 首先打开一个基本的springboot项目,点进去@SpringBootApplica ...

  5. Spring Boot系列(二):Spring Boot自动装配原理解析

    一.Spring Boot整合第三方组件(Redis为例) 1.加依赖 <!--redis--> <dependency> <groupId>org.springf ...

  6. 【Springboot】Springboot自动装配原理

    1.核心注解就是 EnableAutoConfiguration  该注解会激活SpringBoot的自动装配功能: 代码如下: @Target(ElementType.TYPE) @Retentio ...

  7. springboot自动装配原理回顾、配置文件分析

    配置文件 spring boot官方文档 官方外部配置文件说明参考文档 自动配置原理分析 1. SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfigurat ...

  8. SpringBoot 自动装配原理

    早期的Spring项目需要添加需要配置繁琐的xml,比如MVC.事务.数据库连接等繁琐的配置.Spring Boot的出现就无需这些繁琐的配置,因为Spring Boot基于约定大于配置的理念,在项目 ...

  9. 我是如何做到springboot自动配置原理解析

    一前言 springboot 2.0.0版本分析,整体的自动配置流程如下: 具体配置参考官方文档:springboot-doc 二 @SpringBootApplication 核心注解@Spring ...

随机推荐

  1. ssh config 配置

    一个打开新连接免密码的小技巧 Host * ControlMaster auto ControlPath ~/.ssh/master-%r@%h:%p

  2. day32_8_14 并发编程三 线程的GIL

    一.GIL 什么是GIL? GIL是一个全局排他锁,简单来说就是为了防止多线程并行操作的锁.这里有官方解释: In CPython, the global interpreter lock, or G ...

  3. Eclipse IDE for java EE Developers下载和安装

    1.登录 http://www.eclipse.org/home/index.php ,下载Eclipse IDE for java EE Developers 2.解压缩压缩包到任意路径(推荐:G: ...

  4. springboot2.1+redis多数据源的配置

    springboot系列学习笔记全部文章请移步值博主专栏**: spring boot 2.X/spring cloud Greenwich.    由于是一系列文章,所以后面的文章可能会使用到前面文 ...

  5. 2019 SDN上机第3次作业

    1. 利用Mininet仿真平台构建如下图所示的网络拓扑,配置主机h1和h2的IP地址(h1:10.0.0.1,h2:10.0.0.2),测试两台主机之间的网络连通性 创建拓扑 配置主机h1和h2的I ...

  6. <LinkedList> 160

    160. Intersection of Two Linked Lists 分别从AB循环两次.如果第一次没循环到,第二次就会在节点相遇. public class Solution { public ...

  7. [LeetCode] 885. Spiral Matrix III 螺旋矩阵之三

    On a 2 dimensional grid with R rows and C columns, we start at (r0, c0) facing east. Here, the north ...

  8. 分布式共识算法 (二) Paxos算法

    系列目录 分布式共识算法 (一) 背景 分布式共识算法 (二) Paxos算法 分布式共识算法 (三) Raft算法 分布式共识算法 (四) BTF算法 一.背景 1.1 命名 Paxos,最早是Le ...

  9. 2.Python学习之路

    这里主要更新Python每个部分知识点的详细的目录...(知识点目录) Python基础 一.计算机基础 二.Python基础 三.函数 四.常用模块 五.模块和包 六.面向对象 Python进阶 七 ...

  10. 官方入门教程和文档 | Visual Studio

    Visual Studio 2017 概述 | Microsoft Docs(直接教你用vs) https://docs.microsoft.com/zh-cn/visualstudio/ide/vi ...