SpringBoot基本原理

启动流程

启动类代码

1
2
3
4
5
6
@SpringBootApplication
public class SpringBootDemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}

对照上面的典型代码,这个两个元素分别是:

@SpringBootApplication
SpringApplication 以及 run() 方法

SpringApplication 这个类应该算是 SpringBoot 框架 的“创新”产物了,原始的 Spring中并没有这个类,SpringApplication 里面封装了一套 Spring 应用的启动流程,然而这对用户完全透明,因此我们上手 SpringBoot 时感觉简洁、轻量。

一般来说默认的 SpringApplication 执行流程已经可以满足大部分需求,但是 若用户想干预这个过程,则可以通过 SpringApplication 在流程某些地方开启的 扩展点 来完成对流程的扩展,典型的扩展方案那就是使用 set 方法。
我们来举一个栗子,把我们天天司空见惯的 SpringBoot 应用的启动类来拆解一下写出来:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main( String[] args ) {
// 这是传统SpringBoot应用的启动,一行代码搞定,内部默认做了很多事
// SpringApplication.run( SpringBootDemoApplication args );
SpringApplication app = new SpringApplication(SpringBootDemoApplication );
app.setXXX( ... ); // 用户自定的扩展在此 !!!
app.run( args );
}
}

这样一拆解后我们发现,我们也需要先构造 SpringApplication 类对象,然后调用该对象的 run() 方法。那么接下来就讲讲 SpringApplication 的构造过程 以及其 run() 方法的流程,搞清楚了这个,那么也就搞清楚了SpringBoot应用是如何运行起来的!

SpringApplication 实例的初始化

首先看下SpringApplication的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// (1) 推断应用的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// (2) 查找并加载 classpath下 META-INF/spring.factories文件中所有可用的 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// (3) 查找并加载 classpath下 META-INF/spring.factories文件中的所有可用的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// (4) 推断并设置 main方法的定义类
this.mainApplicationClass = deduceMainApplicationClass();
}

详细过程如下:

  • (1)推断应用的类型:根据你classpath 下是否能找到对应的class文件, 推断应用类型, 优先级依次是:REACTIVE 、NONE、SERVLET(默认)。
1
2
3
4
5
6
7
8
9
10
11
12
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
  • (2)使用 SpringFactoriesLoader查找并加载 classpath下 META-INF/spring.factories文件中所有可用的 ApplicationContextInitializer
1
2
3
4
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
  • (3) 使用 SpringFactoriesLoader查找并加载 classpath下 META-INF/spring.factories文件中的所有可用的 ApplicationListener
1
2
3
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
  • (4) 推断并设置main方法的定义类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}

SpringApplication 的run()方法

先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 通过 SpringFactoriesLoader 加载META-INF/spring.factories文件,获取并创建 SpringApplicationRunListener对象
SpringApplicationRunListeners listeners = getRunListeners(args);
// 然后由 SpringApplicationRunListener 来发出 starting 消息
listeners.starting();
try {
// 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印欢迎页信息
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 初始化 ApplicationContext,并设置 Environment,加载相关配置等
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新context,最重要的一步,完成IOC容器注入整个过程
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

流程图如下:

自动装配原理

自动装配过程分析

自动装配原理得从 @SpringbootApplication 入手分析

@SpringbootApplication包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

1
2
3
4
5
6
7
8
9
10
@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 {
  • @ComponentScan如果没有指定扫描包,因此它默认扫描的是与该类同级的类或者同级包下的所有类;

  • @SpringBootConfiguration通过源码得知它是一个@Configuration;

  • @EnableAutoConfiguration 一旦加上此注解,那么将会开启自动装配功能,简单点讲,Spring会试图在你的classpath下找到所有配置的Bean然后进行装配。当然装配Bean时,会根据若干个@Conditional定制规则来进行初始化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
*/
String[] excludeName() default {};

}
  • 根据文档注释的说明它指点我们去看EnableAutoConfigurationImportSelector。但是该类在SpringBoot1.5.X版本已经过时了,因此我们看一下它的父类AutoConfigurationImportSelector;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {

private static final String[] NO_IMPORTS = {};

private static final Log logger = LogFactory
.getLog(AutoConfigurationImportSelector.class);

private ConfigurableListableBeanFactory beanFactory;

private Environment environment;

private ClassLoader beanClassLoader;

private ResourceLoader resourceLoader;

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
// 读取mata-info/spring-autoconfigure-metadata.properties元数据与元数据的相关属性
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 去 mata-info/spring.factories 文件中查询 EnableAutoConfiguration值
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去除重复的配置类,若我们自己写的starter可能存在重复的
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 根据maven导入的启动器过滤出需要导入的配置类
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}

protected boolean isEnabled(AnnotationMetadata metadata) {
return true;
}

}

首先该类实现了DeferredImportSelector接口,这个接口继承了ImportSelector, 该接口主要是为了导入 @Configuration 的配置项,而 DeferredImportSelector 是延期导入,当所有的@Configuration都处理过后才会执行;

  • 回过头来我们看一下 AutoConfigurationImportSelectorselectImport方法, 该方法刚开始会先判断是否进行自动装配,而后会从 META-INF/spring-autoconfigure-metadata.properties 读取元数据与元数据的相关属性,紧接着会调用getCandidateConfigurations方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), 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;
}

/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration candidates.
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

在这里又遇到SpringFactoryiesLoader, 它会读取META-INF/spring.factories下的EnableAutoConfiguration的配置,紧接着在进行排除与过滤,进而得到需要装配的类。最后让所有配置在META-INF/spring.factories下的AutoConfigurationImportListener执行AutoConfigurationImportEvent事件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void fireAutoConfigurationImportEvents(List<String> configurations,
Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}

protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
this.beanClassLoader);
}

总结

1)自动装配还是利用了 SpringFactoriesLoader 来加载META-INF/spring.factoires文件里所有配置的EnableAutoConfgruation,它会经过excludefilter等操作,最终确定要装配的类

2) 处理@Configuration的核心还是ConfigurationClassPostProcessor,这个类实现了BeanFactoryPostProcessor, 因此当AbstractApplicationContext执行refresh()方法里的invokeBeanFactoryPostProcessors(beanFactory)方法时会执行自动装配

-w974
-w595

自定义starter

Tomcat启动流程

-w1222

EmbeddedWebServerFactoryCustomizerAutoConfiguration 内嵌web容器工厂自定义定制器装配类

org.springframework.context.support.AbstractApplicationContext#refresh

如何扫描自定义组件

Conditional注解

常见的注解解释:

  • @ConditionalOnBean
    匹配给定的class类型或者Bean的名字是否在SpringBeanFactory中存在

  • @ConditionalOnClass
    匹配给定的class类型是否在类路径(classpath)中存在

  • @ConditionalOnExpression
    匹配给定springEL表达式的值返回true时

  • @ConditionalOnJava
    匹配JDK的版本,其中range属性是枚举类型有两个值可以选择

    • EQUAL_OR_NEWER 不小于
    • OLDER_THAN 小于

    value属性用于设置jdk版本

  • ConditionalOnMissingBean
    spring上下文中不存在指定bean时

  • ConditionalOnWebApplication
    在web环境下创建

参考

SpringBoot学习之自动装配
深入理解SpringBoot之自动装配