一.Springboot启动流程

由上图可以见到spring boot的整个启动流程和各个组件间的互相调用关系:
1.java程序由启动类主类调用main方法开始
2.调用SpringApplication的构造方法,实例一个Spring应用对象,在构造方法里主要完成启动环境初始化工作,如,推断主类,spring应用类型,加载配置文件,读取spring.factories文件等
3.调用run方法,所有的启动工作在该方法内完成,主要完成加载配置资源,准备上下文,创建上下文,刷新上下文,过程事件发布等
1.启动入口(SpringApplication)
java
@SpringBootApplication
public class SummaryApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication();
application.run(SummaryApplication.class, args);
}
}
1.1 @SpringBootApplication 注解
通过源码发现该注解只是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan 三个注解的组合

- @EnableAutoConfiguration借助@Import的帮助,根据项目引入的 jar 依赖,筛选 Spring Boot 内置的自动配置类,把框架预设的功能型 Bean(如 Tomcat、数据源、MVC 核心组件)加载到 IoC 容器,实现 "有什么依赖就自动配什么功能"的自动化配置核心注解
- @SpringBootConfiguration等价于@Configuration,就是将这个类标记韦配置类然后会被加载在容器中
- @ComponentScan组件扫描,默认扫描当前引导类所在包及其子包
2.构造器(Constructor)

3.启动方法(RUN)
- new一个后序会陆续使用到的"启动上下文"BootstarpContext
- 启动无输入设备为true,表示缺少显示器键盘等输入设备也可以正常启动
- 启动"运行监听器"同时发布启动事件
- 环境准备阶段
- 创建springApplicationContext上下文
- 上下文初始化
二.SpringBoot自动配置原理
什么是自动装配:
我们现在提到自动装配的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。
SpringBoot自动装配可简单理解为:**根据项目中添加的依赖和其它因素,自动创建和配置Spring应用所需要的Bean。**这意味着开发者不需要编写大量的XML配置或Java配置类来设置Spring容器
由上一点可知@SpringBootApplication实际上是由三个注解组成,而与SpringBoot自动配置原理有关的注解是**@EnableAutoConfiguration**
下面我们来看@EnableAutoConfiguration的注解源码
java
@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 {};
}
@EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 **AutoConfigurationImportSelector**类。
那么AutoConfigurationImportSelector(加载自动装配类)到底做了什么?
java
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
public interface DeferredImportSelector extends ImportSelector {
}
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
可以看出,AutoConfigurationImportSelector 类实现了**ImportSelector**接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。
java
private static final String[] NO_IMPORTS = new String[0];
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// <1>.判断自动装配开关是否打开
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//<2>.获取所有需要装配的bean
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。
该方法的调用链如下:

现在我们结合**getAutoConfigurationEntry()**的源码来详细分析一下:
java
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
//<1>.
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//<2>.
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//<3>.
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//<4>.
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 new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
第 1 步:
判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中设置
第 2 步 :
用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName。
第 3 步
获取需要自动装配的所有配置类,读取**META-INF/spring.factories**

第 4 步 :
到这里可能面试官会问你:"spring.factories中这么多配置,每次启动都要全部加载么?"。
很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了,因为,这一步有经历了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效
三.自定义starter
什么是starter?
SpringBoot里的starter就是用来简化开发的工具,主要解决依赖管理 和自动装配的问题;
它本质上是一个整合好的依赖包,里面包含了开发某个功能常用的所有依赖,不需要我们手动一个个去添加
**案例:**需求自定义redis-starter,要求当导入redis坐标时,SpringBoot会自动创建Jedis的Bean
案例:实现步骤
①创建redis-spring-boot-autoconfigure模块
②创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure模块
③在redis-spring-boot-autoconfigure模块中初始化Jedis的Bean,并定义META-INF/spring.factories文件
具体步骤:
①创建两个spring-initialer的模块:redis-spring-boot-autoconfigure,redis-spring-boot-starter
②在redis-spring-boot-starter中引入redis-spring-boot-autoconfigure的依赖
XML
<!-- 引入我们自己的自动配置模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>1.0.0</version>
</dependency>
③在redis-spring-boot-autoconfigure引入jedis的依赖
XML
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.9.0</version>
</dependency>
④编写RedisConfiguration.java
java
package com.itheima.redis.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
/**
* 提供Jedis的bean
*/
@Bean
public Jedis jedis(RedisProperties redisProperties) {
return new Jedis(redisProperties.getHost(), redisProperties.getPort());
}
}
⑤用户的端口和主机地址需要用户自己指定(配置文件中),不应该写死,所以我们创佳RedisProperties 类的代码。这是一个简化版的 Redis 配置属性类:
java
package com.itheima.redis.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
private String host ="localhost";//当用户不配置的时候,我们自己给它一个默认值
private int port =6379;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
java# 你的申请单(application.yml) redis: 房间号: "101号房" # ← 对应 host 字段 楼层: 5 # ← 对应 port 字段
RedisAutoConfiguration 依赖 RedisProperties 来获取配置信息,然后创建 Jedis Bean 供其他组件使用。
⑥定义META-INF/spring.factories文件
java
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.redis.autoconfigure.RedisConfiguration

