SpringBoot版本-2.X
@SpringBootApplication 标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;
@SpringBootApplication
public class StudyProjectApplication {
public static void main(String[] args) {
SpringApplication.run(StudyProjectApplication.class, args);
}
}
当我们点进去后发现:这是一个集成注解,为什么它能集成这么多的注解的功能呢?是在于它上面的 @Inherited 注解, @Inherited 表示自动继承注解类型。
@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 {
/**
* 自动装配要排除的类,功能来自于 @EnableAutoConfiguration
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* 自动装配要排除的类名,功能来自于 @EnableAutoConfiguration
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* 配置扫描的包,功能来自于 @ComponentScan
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* 配置扫描的class,该class所在的包都会被扫描,功能来自于 @ComponentScan
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* 是否启用 @Bean 方法代理,功能来自于 @Configuration
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootApplication 是一个组合注解,包含了 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解的功能;@SpringBootApplication 中也提供了一些配置属性,而这些属性来自于以上三个注解。
我们点进注解发现:它是 springboot 的配置类,标注在某个类上,表示这是一个 springboot的配置类。

这个注解比较简单,上面标记了 @Configuration,然后是一个属性 proxyBeanMethods(),它来自于 @Configuration。因此,@SpringBootConfiguration 并没有做什么,仅仅只是将 @Configuration 使用了 @Configuration 的功能。
@EnableAutoConfiguration主要 用来开启自动装配功能,在第二章SpringBoot自动装配原理中详细说明。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 将被 @SpringBootApplication 标记的类所在的包,包装成 BasePackages,然后注册到 spring 容器中;
@AutoConfigurationPackage
// 将当前项目支持的自动配置类添加到 spring 容器中;
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 可自行定义排除自动装配的类
*/
Class<?>[] exclude() default {};
/**
* 可自行定义排除自动装配的类名
*/
String[] excludeName() default {};
}
从代码中可以看到,
@AutoConfigurationPackage 注解的功能,该注解用来指定自动装配的包;@Import 注解引入了一个类 AutoConfigurationImportSelector,这个类是自动装配的关键;Class 对象),也可以根据类名 (包名.类名) 排除。这个注解想必大家已经很熟悉了,它指定了包扫描路径,如果不指定,就扫描所在类的包。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
TypeExcludeFilter: 这个类表示在进行包扫描时,可以排除一些类。
AutoConfigurationExcludeFilter:用来排除自动配置类,也就是说,spring 在进行包扫描时,不会扫描自动配置类。核心源码如下:
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
private ClassLoader beanClassLoader;
private volatile List<String> autoConfigurations;
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
// isConfiguration(...):当前类是否被 @Configuration 标记
// isAutoConfiguration(...):当前类是否为自动配置类
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
private boolean isAutoConfiguration(MetadataReader metadataReader) {
// 获取所有的自动配置类,然后判断当前类是否存在于其中
return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
this.autoConfigurations = SpringFactoriesLoader
.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);
}
return this.autoConfigurations;
}
}
我们主要看 match(...) 方法,它的匹配的类为:
@Configuration 标记;isAutoConfiguration(...) 可以看到,在判断是否为自动配置类上,springboot 先使用 SpringFactoriesLoader 加载所有配置类,然后再判断传入的类是否为其中之一。)满足以上两个条件,spring 就不会对其进行扫描处理。
@SpringBootApplication
public class StudyProjectApplication {
public static void main(String[] args) {
SpringApplication.run(StudyProjectApplication.class, args);
}
}
我们进入这个run方法:
public class SpringApplication {
...
// primarySource 就是我们传入的 StudyProjectApplication.class,
// args 就是 main() 方法的参数
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 将 primarySource 包装成数组,继续调用 run(...) 方法
return run(new Class<?>[] { primarySource }, args);
}
// primarySources 就是我们传入的 StudyProjectApplication.class 包装成的数组,
// args 就是 main() 方法的参数
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 核心代码
return new SpringApplication(primarySources).run(args);
}
...
}
分析关键代码: return new SpringApplication(primarySources).run(args);
这块代码需要拆开来看,可以拆成以下两部分:
SpringApplication#SpringApplication(Class>...)SpringApplication#run(String...)看来,这两个方法就是 springboot 的启动所有流程了,接下来我们就来分析这两个方法。
创建SpringApplication对象
/**
* 这里就最终调用的构造方法了
* resourceLoader 为 null
* primarySources 为 StudyProjectApplication.class
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1. 将传入的resourceLoader设置到成员变量,这里的值为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 2. 将传入的primarySources设置到成员变量,这里的值为 StudyProjectApplication.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3. 当前的 web 应用类型,REACTIVE,NONE,SERVLET
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4. 设置初始化器,getSpringFactoriesInstances:从 META-INF/spring.factories 中获取配置
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 5. 设置监听器,getSpringFactoriesInstances:从 META-INF/spring.factories 中获取配置
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 6. 返回包含main()方法的class
this.mainApplicationClass = deduceMainApplicationClass();
}
this.webApplicationType = WebApplicationType.deduceFromClasspath();
WebApplicationType.deduceFromClasspath() 方法是用来推断当前项目是什么类型的,代码如下:
public enum WebApplicationType {
// 不是 web 应用
NONE,
// servlet 类型的 web 应用
SERVLET,
// reactive 类型的 web 应用
REACTIVE;
...
private static final String[] SERVLET_INDICATOR_CLASSES = {
"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS
= "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS
= "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS
= "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
// classpath 中仅存在 WEBFLUX 相关类
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// classpath 不存在 SERVLET 相关类
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 默认 web 类型为 SERVLET
// 也就是说,同时存在 WEBFLUX 与 SERVLET 相关类,最终返回的是 SERVLET
return WebApplicationType.SERVLET;
}
...
}
可以看到,springboot 定义了三种项目类型:NONE(不是 web 应用)、SERVLET(servlet 类型的 web 应用)、REACTIVE(reactive 类型的 web 应用),WebApplicationType.deduceFromClasspath() 的执行流程如下:
classpath 中仅存在 WEBFLUX 相关类,则表明当前项目是 reactive 类型的 web 应用,返回;classpath 中不存在 SERVLET 相关类,则表明当前项目不是 web 应用,返回;servlet 类型的 web 应用。由于 demo 引用了 spring-boot-starter-web 相关依赖,因此当前项目是 servlet 类型的 web 应用。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
这行代码分为两部分:
ApplicationContextInitializer:getSpringFactoriesInstances(ApplicationContextInitializer.class)setInitializers(...)public class SpringApplication {
...
// type 为 ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
/**
* type 为 ApplicationContextInitializer.class
* parameterTypes 为 ew Class>[] {}
* args 为 null
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 从 META-INF/spring.factories 加载内容
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化,使用的反射操作
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 排序,比较的是 @Order 注解,或实现的 Orderd 接口
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
...
}
先从 META-INF/spring.factories 获取内容,然后使用反射进行实例化,进行排序后再返回。那么最终会有多少个 ApplicationContextInitializer 加载进来呢?

通过调试,发现一共有 7 个:对这 7 个 ApplicationContextInitializer,说明如下:
ConfigurationWarningsApplicationContextInitializer:报告 IOC 容器的一些常见的错误配置ContextIdApplicationContextInitializer:设置 Spring 应用上下文的 IDDelegatingApplicationContextInitializer:加载 application.properties 中 context.initializer.classes 配置的类RSocketPortInfoApplicationContextInitializer:将 RSocketServer 实际使用的监听端口写入到 Environment 环境属性中ServerPortInfoApplicationContextInitializer:将内置 servlet 容器实际使用的监听端口写入到 Environment 环境属性中SharedMetadataReaderFactoryContextInitializer:创建一个 SpringBoot 和 ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory 对象ConditionEvaluationReportLoggingListener:将 ConditionEvaluationReport 写入日志获取到 ApplicationContextInitializer,我们再来看看 setInitializers(...) 方法:
public class SpringApplication {
...
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
...
}
这是一个标准的 setter 方法,所做的就只是设置成员变量。
注意: 这里会把项目中所有的spring.factories中的key都存到内存里,以便后面快速找到对应的key
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
从形式上看,同 Initializer 一样,也是先从 META-INF/spring.factories 中加载 ApplicationListener,然后添加到成员变量中,这里我们直接看能获取到哪些 listener:

这些Listener的作用是:
ClearCachesApplicationListener:应用上下文加载完成后对缓存做清除工作ParentContextCloserApplicationListener:监听双亲应用上下文的关闭事件并往自己的子应用上下文中传播CloudFoundryVcapEnvironmentPostProcessor:对 CloudFoundry 提供支持FileEncodingApplicationListener:检测系统文件编码与应用环境编码是否一致,如果系统文件编码和应用环境的编码不同则终止应用启动AnsiOutputApplicationListener:根据 spring.output.ansi.enabled 参数配置 AnsiOutputConfigFileApplicationListener:从常见的那些约定的位置读取配置文件DelegatingApplicationListener:监听到事件后转发给 application.properties 中配置的 context.listener.classes 的监听器ClasspathLoggingApplicationListener:对环境就绪事件 ApplicationEnvironmentPreparedEvent 和应用失败事件 ApplicationFailedEvent 做出响应LoggingApplicationListener:配置 LoggingSystem,使用 logging.config 环境变量指定的配置或者缺省配置LiquibaseServiceLocatorApplicationListener:使用一个可以和 SpringBoot 可执行 jar 包配合工作的版本替换 LiquibaseServiceLocatorBackgroundPreinitializer:使用一个后台线程尽早触发一些耗时的初始化任务再来看看 SpringApplication#setListeners:
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
this.mainApplicationClass = deduceMainApplicationClass();
所谓主类,就是包含 main(String[]),也就是当前 spring 应用的启动类,SpringApplication#deduceMainApplicationClass 代码如下:
private Class<?> deduceMainApplicationClass() {
try {
// 获取调用栈
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 遍历调用栈,找 到main方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
这里主要是通过 new RuntimeException().getStackTrace() 获取调用栈,然后遍历,得到包含 main 方法的类,得到的调用栈如下:
可以看到,main() 就包含在调用栈中了。

创建好了SpringApplication实例后,我们继续分析: 实例方法:SpringApplication#run(String...)
run方法的内容如下:
public ConfigurableApplicationContext run(String... args) {
//1.创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//准备空的ApplicationContext,以及一组异常报告器
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//2. 配置 headless 属性,java.awt.headless,java.awt.headless模式是系统的一种配置模式,在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式
configureHeadlessProperty();
// 3. 获取监听器,也是从 META-INF/spring.factories 中获取
SpringApplicationRunListeners listeners = getRunListeners(args);
// starting():首次启动run方法时立即调用。可用于非常早期的初始化(准备运行时环境之前)。
// 4. 发布事件
listeners.starting();
try {
// 封装传入的参数,args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 5. 处理环境参数
// 创建并配置当前SpringBoot应用将要使用的Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 6. 如果有配置 spring.beaninfo.ignore,则将该配置设置进系统参数
configureIgnoreBeanInfo(environment);
// 7.准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
// 8. 创建applicationContext
context = createApplicationContext();
//启动错误报告的自定义回调接口
//这一步的逻辑和实例化初始化器和监听器的一样,都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
// 9. 准备上下文,设置了一系列的属性值
//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// 10. 调用 AbstractApplicationContext.refresh,启动spring容器
refreshContext(context);
// 11. 刷新后的处理
//扩展接口,设计模式中的模板方法,默认为空实现。
// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
afterRefresh(context, applicationArguments);
// 停止 StopWatch 统计时长
stopWatch.stop();
// 打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 12. 发布事件
listeners.started(context);
//13. 调用 runner,实现了 ApplicationRunner或CommandLineRunner 的接口
//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
//14. 发布应用上下文就绪事件
//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
// 这样整个Spring Boot项目就正式启动完成了。
try {
listeners.running(context);
} catch (Throwable ex) {
// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回容器
return context;
}
下面进行详细分析:
一开始,springboot 就创建了 stopWatch 实例,然后调用 StopWatch#start() 启动计时功能,关于这个类没啥好说的,它就是个计时器,这里是用来计算 springboot 启动耗时,以下日志中的时间就是由这个计时器得到的:

SpringApplication#configureHeadlessProperty 方法的相关代码如下:
public class SpringApplication {
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
...
private boolean headless = true;
public void setHeadless(boolean headless) {
this.headless = headless;
}
...
private void configureHeadlessProperty() {
// 将 java.awt.headless 的值配置到系统变量
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
Boolean.toString(this.headless)));
}
...
}
这个方法仅仅是将 java.awt.headless 的值设置到了系统变量,设置成 true,表示启动 java.awt.headless 模式。那么它是个啥模式呢?简单来说,该模式就是系统缺少了显示设备、键盘或鼠标等情况的模式,服务器一般都是在这种情况下工作的。
SpringApplicationRunListener 依然是从 META-INF/spring.factories 中获取,那 SpringApplicationRunListener 是个啥呢?我们来看代码:
public interface SpringApplicationRunListener {
/**
* 首次启动run方法时立即调用。可用于非常早期的初始化。
*/
default void starting() {
}
/**
* 准备好环境(Environment构建完成),但在创建ApplicationContext之前调用。
*/
default void environmentPrepared(ConfigurableEnvironment environment) {
}
/**
* 在创建和构建ApplicationContext之后,但在加载之前调用。
*/
default void contextPrepared(ConfigurableApplicationContext context) {
}
/**
* ApplicationContext已加载但在刷新之前调用。
*/
default void contextLoaded(ConfigurableApplicationContext context) {
}
/**
* ApplicationContext已刷新,应用程序已启动,
* 但尚未调用CommandLineRunners和ApplicationRunners。
*/
default void started(ConfigurableApplicationContext context) {
}
/**
* 在运行方法彻底完成之前立即调用,
* 刷新ApplicationContext并调用所有CommandLineRunners和ApplicationRunner。
*/
default void running(ConfigurableApplicationContext context) {
}
/**
* 在运行应用程序时失败时调用。
*/
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
SpringApplicationRunListener 是一个接口,定义了一系列的方法来监听 springboot 的启动过程,方法的说明已经在文档中有详细定义,如果我们需要在 springboot 的启动中的某个过程做一些事情,就可以实现 SpringApplicationRunListener 然后重写对应的方法。
通过调试,发现 springboot 获得的运行监听器如下:SpringApplication运行方法的侦听器。

回到 SpringApplication#run(java.lang.String...),获取到运行监听器后,会立即调用 starting() 方法来发布启动事件:
// 获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// starting():首次启动run方法时立即调用。可用于非常早期的初始化(准备运行时环境之前)。
listeners.starting();
进入 SpringApplicationRunListeners#starting 方法:
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
可以看到,所谓的发布事件,就是遍历所有的监听器,逐一调用其 starting() 罢了,this.listeners 就是上面获取到的所有的运行监听器。不仅如此,SpringApplicationRunListener 的其他方法如 environmentPrepared(...)、contextPrepared(...) 等都是这样的调用套路,后面看到了就不重复分析了。
接下我们来看看环境参数的处理,代码如下:
// 封装传入的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 处理环境参数
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
进入 SpringApplication#prepareEnvironment:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 获取环境,如果不存在则创建
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置运行时环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// SpringApplicationRunListener 的 environmentPrepared 方法
// (Environment构建完成,但在创建ApplicationContext之前)
// 配置文件加载监听器(ConfigFileApplicationListener)
listeners.environmentPrepared(environment);
// 环境与应用绑定
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
获取或创建环境:
这个方法是根据应用类型来创建对应的 Environment 实例,当前应用类型是 SERVLET。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
StandardServletEnvironment()中包含了如下内容:
System.getenv() 得到的参数;System.getProperties() 得到的参数;servlet 的 servletContext 与 servletConfig.配置环境
我们继续分析,接着看看配置环境的流程,也就是 SpringApplication#configureEnvironment 方法:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
// 添加转换器,处理参数类型转换,如 String转Number,Integer转Enum等
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// 将启动参数添加到 environment 中
configurePropertySources(environment, args);
// 处理 ActiveProfiles 值
configureProfiles(environment, args);
}
springboot 的启动参数有啥好解析的呢?在启动 spring 项目时,我们可以这样指定参数:
java -jar xxx.jar --a1=aaa --b1=bbb
然后我们就能通过 @Value("${a1}") 获取到,关键就是在这里,可以看到,springboot 会把传入的 --a1=aaa、--b1=bbb 解析成 a1/aaa、b1/bbb 键值对的形式,保存到 Environment,后续要用到时,就可很方便地从 Environment 中获取了。
environment还有一个很重要的功能——加载配置文件,在下面章节中详细说明。
创建 ioc 容器的代码如下:
ConfigurableApplicationContext context = null;
....
// 创建applicationContext
context = createApplicationContext();
我们进入 SpringApplication#createApplicationContext 方法:
/** 默认的 ApplicationContext */
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
/** servlet 应用的的 ApplicationContext */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
/** Reactive 应用的 ApplicationContext */
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 根据应用类型来创建不同的容器
switch (this.webApplicationType) {
case SERVLET:
// 使用的是 AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// 默认使用的是 AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(...);
}
}
// 使用反射进行实例化
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
这个方法主要就是根据应用类型来创建不同 ApplicationContext,使用反射的方法进行实例化,各应用类型对应的 ApplicationContext 如下:
servlet 应用:AnnotationConfigServletWebServerApplicationContextreactive 应用:AnnotationConfigReactiveWebServerApplicationContextAnnotationConfigApplicationContext当前应用的类型是 servlet,因此创建的 ApplicationContext 是 AnnotationConfigReactiveWebServerApplicationContext,来看看它的构造方法:
我们进入 SpringApplication#createApplicationContext 方法:
/** 默认的 ApplicationContext */
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
/** servlet 应用的的 ApplicationContext */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
/** Reactive 应用的 ApplicationContext */
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 根据应用类型来创建不同的容器
switch (this.webApplicationType) {
case SERVLET:
// 使用的是 AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// 默认使用的是 AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(...);
}
}
// 使用反射进行实例化
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
这个方法主要就是根据应用类型来创建不同 ApplicationContext,使用反射的方法进行实例化,各应用类型对应的 ApplicationContext 如下:
servlet 应用:AnnotationConfigServletWebServerApplicationContextreactive 应用:AnnotationConfigReactiveWebServerApplicationContextAnnotationConfigApplicationContext当前应用的类型是 servlet,因此创建的 ApplicationContext 是 AnnotationConfigReactiveWebServerApplicationContext,来看看它的构造方法:
public class AnnotationConfigServletWebServerApplicationContext
extends ServletWebServerApplicationContext implements AnnotationConfigRegistry {
// 用来处理 BeanDefinition 的注册
private final AnnotatedBeanDefinitionReader reader;
// 用来处理包的扫描
private final ClassPathBeanDefinitionScanner scanner;
...
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
...
}
AnnotationConfigServletWebServerApplicationContext 的构造方法还是比较简单的,只是设置了两个属性,就不多说了。不过,我们也要把目光放远一点,看看其父类的构造方法,最终在 GenericApplicationContext 的构造方法中找到这么一句:
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
这行代码创建了 DefaultListableBeanFactory 并将其赋值给了 beanFactory,这表明我们在 ApplicationContext 使用的 beanFactory 就是 DefaultListableBeanFactory。
创建完 ioc 容器后,接着就是对容器进行一些准备操作,代码如下:
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
public class SpringApplication {
...
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 将创建好的应用环境设置到IOC容器中
context.setEnvironment(environment);
// 设置容器的一些参数
postProcessApplicationContext(context);
// 应用Initializer进行初始化操作
applyInitializers(context);
// 监听器:SpringApplicationRunListeners的contextPrepared方法
// (在创建和准备ApplicationContext之后,但在加载之前)
listeners.contextPrepared(context);
// 打印个日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 获取beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 将运行参数作为bean注册到beanFactory中
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
// 将 banner 作为bean注册到beanFactory中
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 是否允许bean的信息被覆盖
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 处理懒加载
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(
new LazyInitializationBeanFactoryPostProcessor());
}
// 获取所有资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载 class
load(context, sources.toArray(new Object[0]));
// 发布事件
listeners.contextLoaded(context);
}
}
接下来我们来看看 SpringApplication#refreshContext 方法:
private void refreshContext(ConfigurableApplicationContext context) {
// 启动spring容器
refresh(context);
if (this.registerShutdownHook) {
try {
// 注册 ShutdownHook
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
这个方法操作就两个:
refresh(context):启动 spring 容器,也不是调用 AbstractApplicationContext#refresh 方法;context.registerShutdownHook():注册 ShutdownHook,可以在 jvm 进程关闭时处理一些特定的操作。进入 SpringApplication#refresh:
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// spring 容器的启动操作了
((AbstractApplicationContext) applicationContext).refresh();
}
这个方法很简单,先判断 applicationContext 的类型是否为 AbstractApplicationContext,然后再调用 AbstractApplicationContext#refresh()。
关于 AbstractApplicationContext#refresh(),那可是大名鼎鼎啊,该方法涵盖了 spring 容器的启动流程。
**refreshContext(context)**的核心代码如下:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 准备此上下文以进行刷新。将 servletContext 与 servletConfig 设置到了 Environment 中。
prepareRefresh();
// 告诉子类刷新内部 Bean 工厂。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备 Bean 工厂以在此上下文中使用。
prepareBeanFactory(beanFactory);
try {
// 允许在上下文子类中对 Bean 工厂进行后处理。这个 BeanPostProcessor 是用来处理 ServletContextAware 与 ServletConfigAware 两个 Aware 接口的
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 执行 BeanFactoryPostProcessors。在这个方法中,有个重的 BeanFactoryPostProcessor 会被执行:ConfigurationClassPostProcessor,springboot 的自动装配的启用注解 @EnableAutoConfiguration 会在这里处理,自动装配类的加载、条件注解也是在 ConfigurationClassPostProcessor 中。
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 初始化此上下文的消息源,用于国际化操作。
initMessageSource();
// 为此上下文初始化事件多播程序。
initApplicationEventMulticaster();
// 在特定上下文子类中初始化其他特殊 bean。web 服务器是在这个方法中创建的,如tomcat/jetty等
onRefresh();
// 注册事件监听器
registerListeners();
// 初始化单例 bean
finishBeanFactoryInitialization(beanFactory);
// 完成启动操作
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已创建的单例以避免悬而未决的资源。
destroyBeans();
// 重置“活动”标志。
cancelRefresh(ex);
// 将异常传播给调用方。
throw ex;
}
finally {
// 重置 Spring 核心中的常见内省缓存,因为我们可能不再需要单例 Bean 的元数据了......
resetCommonCaches();
contextRefresh.end();
}
}
}
刷新后的处理
afterRefresh(context, applicationArguments);
刷新后的处理方法为 SpringApplication#afterRefresh,内容如下:
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
可以看到,这是一个空方法,springboot 提供的扩展。
Runner回调
处理运行器的方法是 SpringApplication#callRunners,代码如下:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 获取所有的 ApplicationRunner 与 CommandLineRunner
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 排序
AnnotationAwareOrderComparator.sort(runners);
// 遍历调用
for (Object runner : new LinkedHashSet<>(runners)) {
// 调用 ApplicationRunner#run 方法
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
// 调用 CommandLineRunner#run 方法
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
/**
* 调用 ApplicationRunner#run 方法
*/
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
/**
* 调用 CommandLineRunner#run 方法
*/
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
这两个方法表示,springboot 为我们提供了两个接口:ApplicationRunner 与 CommandLineRunner,我们可以实现它来完成一些操作,应用示例如下:
/**
* ApplicationRunner 示例
*/
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("MyApplicationRunner: hello world");
}
}
/**
* CommandLineRunner 示例
*/
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner: hello world!");
}
}

SpringBoot启动过程中分多个阶段或者说是多个步骤,每完成一步就会产生一个事件,并调用对应事件的监听器,这是一种标准的观察者模式,这在启动的过程中有很好的扩展性,下面我们来看看SpringBoot的事件推送原理:

先说结论:在启动的准备运行时环境这一步,会加载配置文件。
在SpringBoot2.4.0以前,是通过ConfigFileApplicationListener来加载配置文件的。
在SpringBoot2.4.0以后,配置文件的加载机制进行了重构
再了解Spring Boot自动配置原理之前可以先去下面问题与思考章节,看一下SPI机制。

先说结论:
内部实际上就去加载
META-INF/spring.factories文件的信息,然后筛选出以EnableAutoConfiguration为key的数据,加载到IOC容器中,实现自动配置功能!
自动导入配置,是SpringBoot的关键。点进源码后发现:这个注解集成了@AutoConfigurationPackage和Import两个注解。

@AutoConfigurationPackage 指定了自动装配的包,代码如下:

这个注解的内容非常简单,仅使用 @Import 注解引入了 AutoConfigurationPackages.Registrar,下面看一下Registrar的内容:
将被 @SpringBootApplication 标记的类所在的包,包装成 BasePackages,然后注册到 spring 容器中;

public abstract class AutoConfigurationPackages {
private static final String BEAN = AutoConfigurationPackages.class.getName();
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
/**
* 根据 ImportBeanDefinitionRegistrar 的处理,spring将调用 registerBeanDefinitions() 注册内容
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//new PackageImports(metadata).getPackageNames()`就是当前启动类所在的包。
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
/**
* 处理具体的注册操作
* 1. 如果 beanFacotry 中包含 BEAN,则将传入的包名添加到 BEAN 对应的 BeanDefinition 的构造方法参数值上;
* 2. 如果 beanFacotry 中不包含 BEAN,则创建 beanDefinition,设置参数值,然后将其注册到 beanFacotry。
* 注册到beanFacotry中的bean为BasePackages
*/
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
// bean 是 BasePackages,构造方法是 BasePackages(String... names),这里获取原本的构造参数的值
ConstructorArgumentValues constructorArguments
= beanDefinition.getConstructorArgumentValues();
// 将原本的构造参数值,以及传入的 packageNames 统一添加到构造方法的第0个参数值上
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
// 设置BeanClass为BasePackages.class
beanDefinition.setBeanClass(BasePackages.class);
// 设置构造方法的参数值
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
/**
* packageName 的包装类
* packageName 是传入类所在的包名,在PackageImport的构造方法中获取
*/
private static final class PackageImport {
private final String packageName;
PackageImport(AnnotationMetadata metadata) {
// 获取传入类所在包名
this.packageName = ClassUtils.getPackageName(metadata.getClassName());
}
String getPackageName() {
return this.packageName;
}
// 省略 equals/toString/hashCode 方法
...
}
/**
* 注解到 beanFactory 中的类
* 该类中有一个List结构,用来保存包扫描路径
*/
static final class BasePackages {
// 包扫描路径在这里保存
private final List<String> packages;
private boolean loggedBasePackageInfo;
BasePackages(String... names) {
List<String> packages = new ArrayList<>();
for (String name : names) {
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packages = packages;
}
// 省略了一些代码
...
}
}
代码有点长,但逻辑并不复杂,流程如下:
AutoConfigurationPackages.Registrar 实现了 ImportBeanDefinitionRegistrar,registerBeanDefinitions(...) 方法向 spring 中注册了 BasePackages,注册逻辑在 AutoConfigurationPackages#register 方法中;AutoConfigurationPackages#register 方法的注册逻辑为,先判断是否已注册了 BasePackages,如果注册了,就将当前类所在的包添加到 BasePackages 的构造方法参数值中,否则就创建 BeanDefinition,设置构造方法的参数值,然后注册到 spring 中;自动配置重点
AutoConfigurationImportSelector 开启自动配置类的导包的选择器(导入哪些组件的选择器),DeferedImportSelector处理自动配置。如果需要@EnableAutoConfiguration的自定义变量,也可以对该类进行子类化。

AutoConfigurationImportSelector 实现了 DeferredImportSelector,关于 DeferredImportSelector 的分析,可以参考 ConfigurationClassPostProcessor 之处理 @Import 注解,这里我们直接给出结论:
DeferredImportSelector 是 ImportSelector 的子接口,其内部有一个接口 Group,该接口定义了两个方法:
public interface DeferredImportSelector extends ImportSelector {
...
interface Group {
/**
* 处理导入操作
*/
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
/**
* 返回导入类
*/
Iterable<Entry> selectImports()
}
}
DeferredImportSelector 的导入类时,DeferredImportSelector.Group#process 方法会先调用,然后再调用 DeferredImportSelector.Group#selectImports 返回导入类;DeferredImportSelector 可以指定导入类的分组,在处理时,可以按分组处理导入类;DeferredImportSelector 在处理导入类时,先将导入类按分组放入一个 map 中,在处理完其他配置类(spring 的配置类为 @Component、@ComponentScan、@Import、@Configuration、@Bean 标记的类)后再来处理分组中的导入类,也就是说,DeferredImportSelector 导入的类,会在其他类注册到 beanFactory 中后,再进行注册(注册前还需判断能否注册到 beanFactory,若能才注册)。// 实现了 DeferredImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
/**
* 这里实现了 DeferredImportSelector.Group
*/
private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
/**
* 保存导入的类
*/
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
/**
* 处理导入类
*/
@Override
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 1. 调用 AutoConfigurationImportSelector#getAutoConfigurationEntry(...) 方法,
// 在这个方法里会加载自动装配类
AutoConfigurationEntry autoConfigurationEntry =
((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
// 2. 将获取到的 autoConfigurationEntry 保存起来
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
/**
* 返回导入类
*/
@Override
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 3. 得到过滤类
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream)
.collect(Collectors.toSet());
// 4. 将 autoConfigurationEntries 转换为 LinkedHashSet
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 5. 去除需要过滤的类
processedConfigurations.removeAll(allExclusions);
// 6. 进行排序,这个顺序决定了自动装配类注册到 beanFactory 中的顺序
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata())
.stream().map((importClassName) -> new Entry(
this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
...
}
}
这里我们将 DeferredImportSelector.Group#process 与 DeferredImportSelector.Group#selectImports 两个方法结合起来看,处理步骤总结如下:
AutoConfigurationImportSelector#getAutoConfigurationEntry(...) 方法加载自动装配类;autoConfigurationEntries 中;@EnableAutoConfiguration 的 exclude 或 excludeName 指定的;autoConfigurationEntries 转换为 LinkedHashSet,结果为 processedConfigurations;processedConfigurations 需要过滤的类;springboot 的自动导入类不是在
AutoConfigurationImportSelector#selectImports方法中处理的,而是在AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports方法中处理的。
核心代码:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
.....
//根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//获取注解中属性的值,@EnableAutoConfiguration中的两个属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 所有需要导入的组件全类名,下面有断点图
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//删除重复项
configurations = removeDuplicates(configurations);
// 排除依赖
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查
checkExcludedClasses(configurations, exclusions);
//删除需要排除的依赖
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
// 触发 AutoConfigurationImportEvent 事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 最终返回结果
return new AutoConfigurationEntry(configurations, exclusions);
}
}

这里的意思是将所有需要导入的组件以全类名的方式返回,并添加到容器中,最终会给容器中导入非常多的自动配置类(xxxAutoConfiguration),给容器中导入这个场景需要的所有组件,并配置好这些组件。有了自动配置,就不需要我们自己手写了。
注意最后一行代码:
// 最终返回的值
return new AutoConfigurationEntry(configurations, exclusions);
这里把 configurations 与 exclusions 都传入了 AutoConfigurationEntry 的构造方法,我们来看看 AutoConfigurationEntry:
protected static class AutoConfigurationEntry {
// 自动装配类
private final List<String> configurations;
// 需要排除的自动装配类
private final Set<String> exclusions;
/**
* 构造方法,对再者进行赋值
*/
AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
this.configurations = new ArrayList<>(configurations);
this.exclusions = new HashSet<>(exclusions);
}
...
}
由些可见,最终返回的 AutoConfigurationEntry 包含两大内容:
configurations:自动装配类,已经去除了需要排除的类exclusions:通过 @EnableAutoConfiguration 指定的需要排除的类整个自动装配类的获取就是这样了,下面我们来看看加载候选的自动装配类的流程。
核心代码:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you " + "are using a custom packaging, make sure that file is correct.");
return configurations;
}
这里有个loadFactoryNames 方法:
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
两个参数:
getBeanClassLoader() 是类加载器
getSpringFactoriesLoaderFactoryClass()
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
我们发现这里传入的是最外层的EnableAutoConfiguration.class,作为加载的key。
注意:在spring.factories中还有其它属性可以作为key,但是自动配置的key只是EnableAutoConfiguration
getCandidateConfigurations()中还有一个断言,大致意思是:在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。
再探究一下 loadFactoryNames() 方法带着它去做了什么处理:
/**
* 使用给定的类加载器从“META-INF/spring.factory”加载给定类型的工厂实现的完全限定类名。
* @param factoryType 需要被加载的工厂类的class
* @param classLoader 用于加载资源的类加载器;可以为 null 以使用默认值
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//Map.getOrDefault(A,B): A为Key,从Map中获取Value,若Value为Null,则返回B 当作返回值
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
打断点后也可以发现这里是EnableAutoConfiguration

拓展:Spring.factories中,key除了EnableAutoConfiguration还有许多其它的key,例如ApplicationListener用于监听Spring事件。属性源加载器、运行监听器、应用上下文初始器以及应用监听器… 在Spring启动过程中,也会从META-INF/spring.factories获取很多其它key的全类名。
再来看看是如何从META-INF/spring.factory加载的:
/**
* 该方法是最核心部分:
* 1.从项目中找到所有META-INF/spring.factories
* 2.解析所有spring.factories文件,生成 Map<一个接口类--->对应所有具体实现类集合>,即Map>
*/
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// Map>> cache:
// 该缓存存储的是:一个类加载器对应 加载spring.factories的结果集
Map<String, List<String>> result = cache.get(classLoader);
// 有则说明已经加载过了,直接返回
if (result != null) {
return result;
}
result = new HashMap<>();
try {
//从FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"路径加载资源
//Enumeration:可以认为是旧版的迭代器,目前被Iterator取代
// 加载spring.factories资源并封装为URL对象
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// UrlResource 类是spring 对URL资源一种封装而已
UrlResource resource = new UrlResource(url);
// 解析properties文件,因为spring.factories 本身就是以key-->value来书写的
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// key:接口全限定名字
String factoryTypeName = ((String) entry.getKey()).trim();
// 因为value:是可以写多个,并且要求之间 用,\ 来隔开。可以看下图的示例
// commaDelimitedListToStringArray方法就是把value的值,解析并封装为数组
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
//computeIfAbsent(key,function)作用:
//若key 找不到则执行function方法得到value,并put(key,value),若key 找的到则直接返回get(key)的值
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// 将所有列表替换为包含唯一元素的不可修改列表
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 放入缓存,防止多次执行上述操作,提高性能
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
到这里,基本也就明了了,Spring是从"META-INF/spring.factories"获取到 所需组件的全类名的,这个文件一般格式为:

自动配置会取key为EnableAutoConfiguration的所有value,一个项目中还会有很多其它的spring.factories,依旧要加载里面key为EnableAutoConfiguration的value。
拓展:如何获项目中所有的spring.factories
classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
- 1
启动类中有一个 @SpringBootApplication 注解,包含了 @SpringBootConfiguration、 @EnableAutoConfiguration
@EnableAutoConfiguration 代表开启自动装配,注解会去 spring-boot-autoconfigure 工程下寻找 META-INF/spring.factories 文件,此文件中列举了所有能够自动装配类的清单,然后自动读取里面的自动装配配置类清单。@ConditionalOn 条件注解,满足一定条件配置才会生效,否则不生效。 如: @ConditionalOnClass(某类.class) 工程中必须包含一些相关的类时,配置才会生效。
在上面的自动配置原理中可以知道,
spring.factories中有那么多的配置,每次启动并不是全部加载,而是根据@ConditionalOnXXX来判断。我们随便点开一个类,都有这个@ConditionalOnXXX注解,@Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么整个配置类里边的配置才会生效。

点开任意一个注解,发现这些注解上都有相同的元注解:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
重点在于@Conditional注解中的参数。点进对应的类中,我们可以看到一些自定义的实现。从上面的注解也基本上都能看出判断的逻辑。
下面说几个常用的条件:
@ConditionalOnProperty 检查是否在环境中定义了属性。
@Configuration
@ConditionalOnProperty(prefix = "email", name = "enabled", havingValue = "true")
@ConditionalOnClass({EmailProperties.class})
@EnableConfigurationProperties({EmailProperties.class})
public class EmailAutoConfiguration {
private final EmailProperties emailProperties;
public EmailAutoConfiguration(EmailProperties emailProperties) {
this.emailProperties = emailProperties;
}
@Bean
public EmailFactoryBean emailFactoryBean() {
return new EmailFactoryBean(emailProperties);
}
}
只有 application.properties 或 application.yml 文件中 email.enable 为 true 才会加载 EmailAutoConfiguration这个 Bean。
还有个属性:matchIfMissing默认为false, 如果设置为true 没有匹配上也会加载。
@ConditionalOnBean 和 ConditionalOnMissingBean
有时候我们需要某个 Bean 已经存在应用上下文时才会加载,那么我们会用到 @ConditionalOnBean 注解:
@ConditionOnBean(OtherBean.class)
与之相反,有时候我们需要某个 Bean 不存在于应用上下文时才会加载,那么我们会用到 @ConditionalOnMissingBean 注解
@ConditionalOnClass 和 @ConditionalOnMissingClass
某个 CLASS 位于类路径上,才会实例化一个 Bean
复杂的组合校验@ConditionalOnExpression
当表达式为 true 的时候,才会实例化一个 Bean
| 注解类型 | 注解类型 | 功能说明 |
|---|---|---|
| class 条件注解 | @ConditionalOnClass/@ConditionalOnMissingClass | 当指定的类存在 / 缺失时初始化该 bean |
| bean 条件注解 | @ConditionalOnBean/@ConditionalOnMissingBean | 当指定的 bean 存在 / 缺失时初始化该 bean |
| 属性条件注解 | @ConditionalOnProperty | 当指定的属性存在初始化该 bean |
| Resource 条件注解 | @ConditionalOnResource | 当指定的资源存在初始化该 bean |
| Web 应用条件注解 | @ConditionalOnWebApplication / @ConditionalOnNotWebApplication | 当前应用为 / 不为 web 应用时初始化该 bean |
| spring 表达式条件注解 | @ConditionalOnExpression | 当表达式结果为 true 时初始化该 bean |
AutoConfigurationImportSelector.AutoConfigurationGroup#sortAutoConfigurations@AutoConfigureBefore / @AutoConfigureAfter 或 @AutoConfigureOrderString 提供排序规则@AutoConfigureOrder 指定的值进行排序,由 Integer 提供排序规则@AutoConfigureBefore / @AutoConfigureAfter 进行排序 需要注意的是,以上三种排序方式先后进行,以最后排序完的结果为最终顺序@ConditionalOnBean/@ConditionalOnMissingBean避坑指南:
bean 都是自动装配类:避坑方式是,使用 @AutoConfigureBefore / @AutoConfigureAfter 或 @AutoConfigureOrder 指定条件顺序,保证条件注解中的 bean 先装配即可;spring bean,一个是自动装配类:条件注解中的 bean 必须为普通的 spring bean;@Import 为 spring 的注解,导入一个配置文件,在 springboot 中为给容器导入一个组件,而导入的组件由 参数类 执行逻辑来决定的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
@Import 里有一个方法:value(),支持的类型是 Class,从上面的文档来看,可以支持类型有 4 种:
@Configuration 注解标记的类ImportSelector 的类ImportBeanDefinitionRegistrar 的类例如:@Import可以创建对象和多配置bean的整合:

spring探秘之ConfigurationClassPostProcessor(三):处理@Import注解
SPI 全称 Service Provider Interface,是 Java 提供的,旨在由第三方实现或扩展的 API,它是一种用于动态加载服务的机制。Java 中 SPI 机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。
Java SPI 有四个要素:
1️⃣ SPI接口定义
package com.wz.spi;
public interface DataStorage{
String selectbyKey(String key);
}
2️⃣ SPI实现类
package com.wz.spi;
public class MysqlStorage implements DataStorage{
@Override
public String selectbyKey(String key) {
return "【Mysql】搜索" + key + ",结果:Hello SPI Mysql";
}
}
package com.wz.spi;
public class RedisStorage implements DataStorage{
@Override
public String selectbyKey(String key) {
return "【Redis】搜索" + key + ",结果:Hello SPI Redis";
}
}
3️⃣ SPI 配置
如果想通过 Java SPI 机制来发现服务,就需要在 SPI 配置中约定好发现服务的逻辑。配置文件必须置于 META-INF/services 目录中,并且,文件名应与服务提供者接口的完全限定名保持一致。文件中的每一行都有一个实现服务类的详细信息,同样是服务提供者类的完全限定名称。
文件名:com.wz.spi.DataStorage
com.wz.spi.MysqlStorage
com.wz.spi.RedisStorage
4️⃣ ServiceLoader
通过ServiceLoader来加载服务:
public class SpiDemo {
public static void main(String[] args) {
ServiceLoader<DataStorage> serviceLoader = ServiceLoader.load(DataStorage.class);
System.out.println("============ Java SPI 测试============");
serviceLoader.forEach(loader -> System.out.println(loader.search("wz")));
}
}
============ Java SPI 测试============
【Mysql】搜索wz,结果:Hello SPI Mysql
【Redis】搜索wz,结果:Hello SPI Redis
Java SPI 机制依赖于 ServiceLoader 类去解析、加载服务。因此,掌握了 ServiceLoader 的工作流程,就掌握了 SPI 的原理。
ServiceLoader 的成员变量:
public final class ServiceLoader<S> implements Iterable<S> {
// SPI 配置文件目录
private static final String PREFIX = "META-INF/services/";
// 将要被加载的 SPI 服务
private final Class<S> service;
// 用于加载 SPI 服务的类加载器
private final ClassLoader loader;
// ServiceLoader 创建时的访问控制上下文
private final AccessControlContext acc;
// SPI 服务缓存,按实例化的顺序排列
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒查询迭代器
private LazyIterator lookupIterator;
// ...
}
ServiceLoader 的工作流程:
(1)ServiceLoader.load 静态方法
应用程序加载 Java SPI 服务,都是先调用 ServiceLoader.load 静态方法。
ServiceLoader.load 静态方法的作用是:
① 指定类加载 ClassLoader 和访问控制上下文;
② 然后,重新加载 SPI 服务
这里,摘录 ServiceLoader.load 相关源码,如下:
// service 传入的是期望加载的 SPI 接口类型
// loader 是用于加载 SPI 服务的类加载器
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
public void reload() {
// 清空缓存中所有已实例化的 SPI 服务
providers.clear();
// 根据 ClassLoader 和 SPI 类型,创建懒加载迭代器
lookupIterator = new LazyIterator(service, loader);
}
// 私有构造方法
// 重新加载 SPI 服务
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 指定类加载 ClassLoader 和访问控制上下文
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// 然后,重新加载 SPI 服务
reload();
}
(2)应用程序通过 ServiceLoader 的 iterator 方法遍历 SPI 实例
ServiceLoader 的类定义,明确了 ServiceLoader 类实现了 Iterable 接口,所以,它是可以迭代遍历的。实际上,ServiceLoader 类维护了一个缓存 providers( LinkedHashMap 对象),缓存 providers 中保存了已经被成功加载的 SPI 实例,这个 Map 的 key 是 SPI 接口实现类的全限定名,value 是该实现类的一个实例对象。
当应用程序调用 ServiceLoader 的 iterator 方法时,ServiceLoader 会先判断缓存 providers 中是否有数据:如果有,则直接返回缓存 providers 的迭代器;如果没有,则返回懒加载迭代器的迭代器。
public Iterator<S> iterator() {
return new Iterator<S>() {
// 缓存 SPI providers
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
// lookupIterator 是 LazyIterator 实例,用于懒加载 SPI 实例
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
(3)懒加载迭代器的工作流程
上面的源码中提到了,lookupIterator 是 LazyIterator 实例,而 LazyIterator 用于懒加载 SPI 实例。那么, LazyIterator 是如何工作的呢?
这里,摘取 LazyIterator 关键代码
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 1.拼接 META-INF/services/ + SPI 接口全限定名
// 2.通过类加载器,尝试加载资源文件
// 3.解析资源文件中的内容
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a s");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
hasNextService 方法:
nextService 方法:
什么是类加载机制
当编译器将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析、运行等整个过程。我们将这个过程称为 Java 虚拟机的「类加载机制」。
类加载机制中,通过类加载器(classloader)来完成类加载的过程。
通过一个类全限定名称来获取其二进制文件(.class)流的工具,被称为类加载器(classloader)。

如上图所示,Java 支持 4 种 classloader
Bootstrap ClassLoader)
Bootstrap ClassLoader 的 parent 属性为 nullExtention ClassLoader)
sun.misc.Launcher$ExtClassLoader 实现JAVA_HOME 下 libext 目录下的或者被 java.ext.dirs 系统变量所指定的路径中的所有类库Application ClassLoader)
sun.misc.Launcher$AppClassLoader 实现User ClassLoader)
java.lang.ClassLoader 类。如果不想打破双亲委派模型,那么只需要重写 findClass 方法即可;如果想打破双亲委派模型,则需要重写 loadClass 方法前 3 种 classloader 均继承了抽象类 ClassLoader,其源码如下,该抽象类拥有一个 parent 属性,用于指定其父类的加载器。
类加载机制的特点
「类加载机制」中,通过**类加载器(classloader)**来完成类加载的过程。Java 中的类加载机制,有如下 3 个特点
JVM 中,类加载器默认使用双亲委派原则。
双亲委派机制是一种任务委派模式,是 Java 中通过加载工具(classloader)加载类文件的一种具体方式。 具体表现为
BootstrapClassLoader。AppClassLoader)也无法加载此类,则抛出异常。双亲委派的优点
使得 Java 类伴随着它的类加载器,天然具备一种带有优先级的层次关系,从而使得类加载得到统一,不会出现重复加载的问题:
双亲委派的缺点
子类加载器可以使用父类加载器已经加载的类,而父类加载器无法使用子类加载器已经加载的。—— 这就导致了双亲委派模型并不能解决所有的类加载器问题。Java SPI 就面临着这样的问题:
如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是 AppClassLoader。在核心类库使用 SPI 接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。
通常可以通过 Thread.currentThread ().getClassLoader () 和 Thread.currentThread ().getContextClassLoader () 获取线程上下文类加载器。
Java SPI 存在一些不足:
在 Spring Boot 中好多配置和实现都有默认的实现,我们如果想要修改某些配置,我们只需要在配置文件中写上对应的配置,项目应用的便是我们定义的配置内容。这正是通过 SPI 机制实现的。
META-INF/spring.factories 文件中SpringFactoriesLoader 进行加载,扫描每个 jar 包 class-path 目录下的 META-INF/spring.factories 配置文件,然后解析 properties 文件,找到指定名称的配置SpringFactoriesLoader.loadFactoryNames 方法核心源码:
// spring.factories 文件的格式为:key=value1,value2,value3
// 遍历所有 META-INF/spring.factories 文件
// 解析文件,获得 key=factoryClass 的类名称
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 尝试获取缓存,如果缓存中有数据,直接返回
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 获取资源文件路径
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 遍历所有路径
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 解析文件,得到对应的一组 Properties
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 遍历解析出的 properties,组装数据
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
ServiceLoader,而 Spring 使用的是 SpringFactoriesLoader。META-INF/services/提供方接口全类名,而 Spring 使用的是 META-INF/spring-factories。