本文包含: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. 洛谷 P3998 [SHOI2013]发微博

    洛谷 P3998 [SHOI2013]发微博 洛谷传送门 题目描述 刚开通的 SH 微博共有n个用户(1Ln标号),在这短短一个月的时间内, 用户们活动频繁,共有m 条按时间顺序的记录: ! x 表示 ...

  2. 第02组 Beta冲刺(1/4)

    队名:十一个憨批 组长博客 作业博客 组长黄智 过去两天完成的任务:了解整个游戏的流程 GitHub签入记录 接下来的计划:继续完成游戏 还剩下哪些任务:完成游戏 燃尽图 遇到的困难:没有美术比较好的 ...

  3. [LeetCode] 87. Scramble String 搅乱字符串

    Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrin ...

  4. 第09组 Beta冲刺(2/5)

    队名:观光队 链接 组长博客 作业博客 组员实践情况 王耀鑫 过去两天完成了哪些任务 文字/口头描述 学习 展示GitHub当日代码/文档签入记录 接下来的计划 完成短租车,页面美化 还剩下哪些任务 ...

  5. oracle--表空间基本操作

    --查表空间使用率情况(含临时表空间) SELECT d.tablespace_name "Name", d.status "Status", TO_CHAR ...

  6. OSG :三维无序离散点构建Delaunay三角网

    利用OSG的osgUtil库里面的DelaunayTriangulator类. points是需要构建三角网的点 osgUtil::DelaunayTriangulator* trig = new o ...

  7. C#程序只允许运行一个实例的解决方案

    最近在做winform的程序中,需要只能打开一个程序,如果已经存在,则激活该程序的窗口,并显示在最前端.在网上google了一哈,找到了很多的解决方案.这里我整理了3种方案,并经过了测试,现和朋友们分 ...

  8. windbg排查线上线程数爆炸问题

    1.早上发现有个job的线程数一直居高不下 2.于是dump一个文件拉到本地,查到都在执行 StartInner方法 3.查询代码,此方法是个静态类开启线程的地方,理论上没有任何问题 4.思索了半天, ...

  9. SpringBoot之CommandLineRunner接口和ApplicationRunner接口

    我们在开发中可能会有这样的情景.需要在容器启动的时候执行一些内容.比如读取配置文件,数据库连接之类的.SpringBoot给我们提供了两个接口来帮助我们实现这种需求.这两个接口分别为CommandLi ...

  10. R语言学习基础一

    笔者使用Rstudio编写R程序,本文主要总结在编写过程中遇到的一些实际 问题 与学习配套的的code上传到我的github,网址: https://github.com/LIU-HONGYANG/S ...