SpringBoot启动

Spring、SpringBoot启动属于是一个老生常谈的问题,以前整理过也比较碎片化。现重新进行一个整理。由于这个知识工作中用的不多,稍微深入一点太容易遗忘,作为一个长期更新的文档。有时间会更新一部分。
同时由于两者对于Spring的启动有很大的共通性,所有主要介绍SpringBoot的启动。毕竟现在新项目基本很少采用Spring Framework了。

这里不想贴太多的源码讲解。不然我会觉得显得文档太长了

前言

Spirng、SpringBoot的启动主要做了什么?

这个问题大了说,只考虑Spring的话(如果包含容器部分,比如tomcat,还可能设计到Servlet等知识),其实最主要就是加载Spring Bean。不管是xml、JavaConfig或者是直接申明的Bean,使用FactoryBean加载的Bean。所有的一切都是为了初始化Bean,初始化Bean的时候也就包括了我们启动需要的资源。

Servlet

SPI 机制

在讲解SpringBoot的启动时,首先要了解SPI模式使用。这里我就不详细讲解,主要是一种服务发现机制。可以看看dubbo的开发者文档,因为dubbo中的很多实现也是基于这个来做的。

只不过SpringBoot的SPI有自己的实现和使用规则,原理都是JAVA自带的SPI类似的。

SpringBoot是在META-INF的spring.factories文件。

1
2
3
4
5
6
7
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

格式就是上诉的这种方式。ApplicationContextInitializer的6个实现类,注意最后不能加,\

SpringBoot在使用SPI的时候一般还会对其进行重排序,我们将接口实现Ordered或者使用注解@Order来决定排序。因为在使用的时候是按顺序调用的。

SpringApplication

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

上面这段代码就是SpringBoot常用的启动类,按方法分解为两部分,构造函数和执行函数。 先看下构造函数

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 这里使用SPI获取这两个接口的实现
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

这里就使用SPI来获取ApplicationContextInitializerApplicationListener的实现。以便后面可以使用。除了SPI其实你可以在启动类中用代码去addXXX自己的实现类。也是可以的,毕竟SpringBootApplication也是将它存在一个私有变量中。

run方法

接下来就是重点了,开始执行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
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// SPI获取SpringApplicationRunListener接口的实现类
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(); // 用户自定义
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 初始化environment,也就是配置文件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建BeanFactory,这里使用了 AnnotationConfigServletWebServerApplicationContext 这个子类
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文
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;
}

首先这里又来一个新的监听器SpringApplicationRunListener,也是通过SPI获取的。他其实是SpringBoot提供的一个接口,会在启动的调用该接口。我们可以通过该接口来在启动的各个阶段加入我们自己的逻辑。这个后面讲。

这里就没撒特别的了。

  • 初始化environment
  • 创建上下文AnnotationConfigServletWebServerApplicationContext
  • 准备上下文
  • 刷新上下文

中间穿插了SpringApplicationRunListener的影子

prepareContext 准备上下文

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
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context); // 对应用上下文进行用户配置的额外处理
applyInitializers(context); // 执行ApplicationContextInitializer的初始化方法
listeners.contextPrepared(context); // 用户自定义
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 根据用户配置,对beanFactory设置一些参数
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context); // 用户自定义
}

准备上下文,就是在整个上下文(ApplicationContext)、配置文件(Enviroment)等资源文件。

这里有个applyInitializers(context)就是执行最开始构造函数通过SPI拿到的ApplicationContextInitializer实现类。主要就是在刷新上下文指定一些自定义的代码。比如对配置文件的处理。

刷新上下文

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
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}finally {
resetCommonCaches();
}
}
}

这里基本就是Bean的加载了。也就是IOC的主要部分。 找到一篇比较详细的文章推荐下! Spring-IOC

ApplicationListener

ApplicationListener是Spring Framework中的类。典型的观察者模式,我们可以在实现他对在Spring的启动,关闭等各个环节开启一个监听器。在设计模式中作为例子讲解一下

SpringApplicationRunListeners

SpringApplicationRunListeners是SpringBoot中的类。它相当于SpringBoot开放的一个钩子函数。我们实现它,SpringBoot在启动的各个环节都会去执行该接口。我们可以在SpringBoot启动中加入自己的逻辑

实际应用

其实看完上面的简单启动版本。其实有时候会在想实际开发中怎么用? 下面简单说明一下

@EnableAutoConfiguration、Condition接口

这两个其实才是实际框架集成到SpringBoot开发中用的最多的。

EnableAutoConfiguration具体实现可以看这篇文章,很不错的。EnableAutoConfiguration

套用里面的一句话:@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!

这才是我们需要的重点将我们定义的Bean加载到Spring

那Condition是干什么的了?

1
2
3
4
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition是Spring Framework中一直都存在的一个接口(具体哪个版本开始就不清楚了)。SpringBoot将它发扬光大了对其做了很多实现

该接口和@Bean,@Component配合使用来决定Bean是否需要加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 当配置 com.myConfig.one 有 true时则启动该Bean
**/
@Configuration
@ConditionalOnProperty(value = "com.myConfig.one",havingValue = "true")
public class Config {

/**
* 存在当 OtherBean 存在Spring中就加载我们的Bean
*/
@Bean
@ConditionalOnBean(OtherBean.class)
public MyConfigBean myConfigBean() {
return new MyConfigBean();
}
}

上面就是一个简单的例子。该注解就可以灵活的控制我们的Bean的加载情况。

到此SpringBoot启动先做了一个简单的介绍整理。主要是因为在我查看了自己以前写的文档后。发现过于碎片化和无脑贴代码。觉得没什么意义,还不如很多网上已有的文档好。所以就直接剔除了。文档中也贴了两个写的可以的文档。后面有时间在整理这部分!