一、前沿
Spring Boot 是 Spring 的衍生产品,是基于Spring4的条件注册的一套快速开发整合包,它实现了自动配置,降低了项目搭建的复杂度。但是Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序,是用于提升Spring开发者体验的工具。
Spring Boot 集成了大量常用的第三方库配置(例如Jackson、JDBC、Mongo、Redis、MQ、Mail等等),Spring Boot应用中这些第三方库几乎可以做到零配置的开箱即用(out-of-the-box),大部分的Spring Boot应用都只需要非常少量的配置代码即可,开发者能够更加专注于业务逻辑。
Spring Boot 集成了 tomcat 和 jetty WEB 服务器,Spring Boot 默认使用 tomcat 做 WEB 服务器,如果你使用的是 tomcat,则不需要单独配置 tocmat 了
Spring Boot 和 Spring 的区别
1)、Spring Boot 实现了自动配置,例如 Mybatis、Redis、JDBC、MQ、Mongo等
2)、Spring Boot 集成了 WEB 服务器,例如 Tomcat 和 Jetty
3)、Spring Boot 实现了配置文件的环境隔离,通过 spring.profiles.active 属性可以指定加载某个环境的下的配置文件
接下来我们探究一下 Spring Boot 的启动流程
二、启动流程图
Spring Boot 启动流程图如下:
详细的流程图如下:
从上图中可知,Spring Boot 启动共分为三个部分
1)、SpringApplication 的初始化模块,即配置了 source、web环境、初始化构造器、应用监听器和main方法所在类
2)、SpringApplication 的启动模块,即启动了流程的监听器、加载环境配置和创建上下文 ApplicationContext
3)、Spring Boot 的自动化配置模块,即调用 ApplicationContext 的 refresh 方法刷新上下文,完成了所有非lazy的Singleton Bean 的加载,其中包括最核心的 Spring Boot 的各种自动化配置类的加载
下面从源码角度分别对这三个部分展开详细分析
三、Spring Boot 启动
Spring Boot 启动分为初始化模块、启动模块和自动化配置模块三部分
3.1 初始化模块
Spring Boot 的入口在带有 @SpringBootApplication 注解的 main 方法中,在 main 方法中主要做了两件事
1)、构造 SpringApplication,即 new SpringApplication(),进行初始化模块操作
2)、调用 SpringApplication 的 run 方法完成 Spring Boot 的启动
代码如下:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* springBoot主入口
*
* @create 2019-12-31 11:36
**/
@SpringBootApplication(scanBasePackages = "com.springboot.demo")
@MapperScan("com.springboot.db.mapper")
@ImportResource("classpath:config/application.xml")
public class MainApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MainApplication.class);
ConfigurableApplicationContext ctx = application.run(args);
}
}
从启动类中可知 @SpringBootApplication 注解很关键,下面看一下其作用,源码如下:
@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
// 排除特定的自动配置类
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 排除特定的自动配置类的名称
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 扫描 scanBasePackages 配置的包下的组件,比如 @Service、@Component 等
// 默认扫描主类所在包下的所有组件
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 扫描 scanBasePackageClasses 配置的类所在的包组件
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
// 是否代理 bean,默认是 true
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootApplication 注解中有包含三个核心注解,分别如下:
@SpringBootConfiguration(内部是 @Configuration 注解) : 这个注解和 @Configuration 注解的作用一样,用来表示被标注的类是一个配置类,会将被标注的类中的所有带 @Bean 注解修饰的方法添加到 Spring 容器中,实例的名称默认是方法名,Spring 会创建出对应的实例 Bean
@EnableAutoConfiguration:开启了 SpringBoot 自动配置,在程序启动时会自动加载 SpringBoot 的自动默认配置,如果有对一些参数进行配置(比如在配置文件中修改了配置属性值),则会在程序启动时或调用时进行追加或者覆盖
@ComponentScan:包扫描注解,默认扫描主类包路径下的组件类,所以最好将启动类 MainApplication 存放在根路径下,这里可以配置自己想要扫描的任何包的路径
@ImportResource:引入某路径下的资源文件
下面看构造 SpringApplication 的具体过程,入口在 SpringApplication 的构造方法,源码如下:
// 1、 SpringApplication 的构造方法
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
// 2、 SpringApplication 的构造方法
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 配置resource
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 配置是否是web环境,web环境的话后续需要启动web服务器,比如 tomcat
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 创建初始化构造器,加载所有 MATE-INF/spring.factories 文件中的配置,所有配置缓存到 cache 对象中
// 根据 ApplicationContextInitializer 类型从 cache 中获取所有的初始化器,通过反射创建所有初始化器实例
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 根据 ApplicationListener 类型从 cache 中获取所有的应用监听器,通过反射创建所有应用监听器实例
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 设置应用main方法所在的主类
this.mainApplicationClass = deduceMainApplicationClass();
}
// 3、SpringApplication 的 getSpringFactoriesInstances 方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
// 从 MATE-INF/Spring.factories 文件中加载指定 type 类型的 Bean
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 4、SpringApplication 的 getSpringFactoriesInstances 方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
// 默认是当前线程上下文的 ClassLoader,resourceLoader 可以指定 ClassLoader
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// SpringFactoriesLoader 加载所有 MATE-INF/Spring.factories 文件中的配置
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 反射创建指定 Type 类型的所有 Bean
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
MATE-INF/Spring.factories 文件中的配置内容如下:
上述代码逻辑相对简单,代码注释已经讲得非常清楚了,这里不在做过多赘述了
3.2 启动模块
Spring Boot 的启动从 SpringApplication 的 run 方法开始,源码如下:
// SpringApplication 的 run 方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
// 构建 StopWatch,记录启动时间
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 Headless ,默认是 true
configureHeadlessProperty();
// 通过读取缓存 cache 创建 SpringApplicationRunListener 实例
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动流程的监听器,即发布 ApplicationStartingEvent 事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 准备环境 ConfigurableEnvironment,即配置环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 配置 spring.beaninfo.ignore 属性值,默认是 true
configureIgnoreBeanInfo(environment);
// 配置 Banner,这里可以自定义 Spring Boot 启动时输出的 banner 信息
Banner printedBanner = printBanner(environment);
// 创建 ApplicationContext 上下文,默认是 AnnotationConfigApplicationContext
context = createApplicationContext();
// 创建 SpringBootExceptionReporter 实例
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文,即配置上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新上下文,加载所有非lazy的Singleton Bean,Spring Boot 的自动配置就是在这里完成的
refreshContext(context);
// 刷新上下文后的操作,这里是空实现,可以作为扩展
afterRefresh(context, applicationArguments);
// 记录总启动时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 发布 ApplicationStartedEvent 事件
listeners.started(context);
// 调用命令的回调方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 发布 ApplicationReadyEvent 事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
run 方法中主要实现了以下逻辑:
1)、准备环境 ConfigurableEnvironment,即配置环境
2)、创建 ApplicationContext 上下文
3)、准备上下文,即配置上下文
4)、刷新上下文,加载所有非lazy的Singleton Bean,Spring Boot 的自动配置就是在这里完成的
下面着重分析前三步骤,因为第四个步骤在自动配置模块讲解
3.2.1 配置环境 ConfigurableEnvironment
配置环境入口在 SpringApplication 的 prepareEnvironment 方法,源码如下:
// 1、SpringApplication 的 prepareEnvironment 方法
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建配置环境,默认是 StandardEnvironment,
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 设置环境配置,即设置 ConversionService、PropertySources 和 Profiles(spring.profiles.active 指定的环境文件)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
// 绑定环境到 SpringApplication 应用上
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
// 非自定义环境时,使用环境转换器转换环境
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// PropertySource 设置 SpringConfigurationPropertySources
ConfigurationPropertySources.attach(environment);
return environment;
}
// 2、SpringApplication 的 getOrCreateEnvironment 方法
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
// 根据 webApplicationType 配置决定环境对象,默认是 StandardEnvironment
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
在加载配置环境的过程中会判断是否是 web 容器启动,如果是容器启动会加载 StandardServletEnvironment,其继承了 ConfigurableEnvironment,结构图如下:
从上图可以看出,xxxEnvironment 最终都实现了PropertyResolver 接口,而 PropertyResolver 接口是用于解析任何基础源的属性的接口
我们平时通过 Environment 对象获取配置文件中指定 Key 对应的 value 时,就是调用了 PropertyResolver 接口的 getProperty 方法
3.2.2 创建上下文 ApplicationContext
创建上下文 ApplicationContext 入口在 SpringApplication 的 createApplicationContext 方法,源码如下:
// SpringApplication 的 createApplicationContext 方法
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 根据 webApplicationType 决定实例上下文对象,默认是 AnnotationConfigApplicationContext
switch (this.webApplicationType) {
case SERVLET:
// web 环境对应的是 AnnotationConfigServletWebServerApplicationContext 上下文
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
// 使用构造反射法创建 ApplicationContext 实例
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
3.2.3 配置上下文
配置上下文入口在 SpringApplication 的 prepareContext 方法,源码如下:
// SpringApplication 的 prepareContext 方法
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 上下文配置环境
context.setEnvironment(environment);
// BeanFactory 设置 beanNameGenerator 和 ConversionService,context 设置 ResourceLoader 或者 ClassLoader
postProcessApplicationContext(context);
// 调用所有 ApplicationContextInitializer 的 initialize 方法
applyInitializers(context);
// 发布 ApplicationContextInitializedEvent 事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 获取 BeanFactory 并注册 ApplicationArguments、Banner等
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");
// 加载所有source下的所有 Bean 定义 BeanDefinition
load(context, sources.toArray(new Object[0]));
// 实现了 ApplicationContextAware 的设置 ApplicationContext,并发布 ApplicationPreparedEvent 事件
listeners.contextLoaded(context);
}
配置上下文主要实现了以下逻辑:
1)、上下文配置环境
2)、BeanFactory 的一些常规属性设置
3)、调用所有初始化器 ApplicationContextInitializer 的 initialize 方法
4)、加载所有source下的所有 Bean 定义 BeanDefinition,即配置文件中的所有bean配置都转化为 BeanDefinition
5)、设置 ApplicationContext,并发布 ApplicationPreparedEvent 事件
3.3 刷新上下文 refresh
Spring Boot 完成了创建上下文 ApplicationContext 和 配置上下文之后,接下来的工作就是刷新上下文了,Spring Boot 的刷新上下文除了兼具Spring所有功能之外,主要完成了以下工作:
1)、创建了web服务器
2)、加载所有非lazy的Singleton Bean
3)、加载自动化配置
4)、启动 WEB 服务器
下面我们以 WEB 环境为例,通过源码来探讨其过程,刷新上下文入口在 SpringApplication 的 refresh 方法,源码如下:
// 1、SpringApplication 的 refresh 方法
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// web环境的话,这里调用的是 ServletWebServerApplicationContext 的 refresh 方法
((AbstractApplicationContext) applicationContext).refresh();
}
// 2、ServletWebServerApplicationContext 的 refresh 方法
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
// 调用 AbstractApplicationContext 的 refresh 方法
super.refresh();
}
catch (RuntimeException ex) {
stopAndReleaseWebServer();
throw ex;
}
}
AbstractApplicationContext 的 refresh 方法实现的所有逻辑我们已经在 Spring源码系列之ApplicationContext refresh 刷新 中讲解过了,这里不在重复分析和Spring做了同样事情的步骤,只分析 Spring Boot 主要实现的自己关键业务步骤,即 onRefresh 方法、finishBeanFactoryInitialization 方法 和 finishRefresh 方法,下面分别介绍
3.3.1 onRefresh 方法
Spring Boot 的 onRefresh 方法主要实现了创建WEB服务器并启动Server和Service功能,入口在 ServletWebServerApplicationContext 的 onRefresh 方法,源码如下:
// 1、ServletWebServerApplicationContext 的 onRefresh 方法
@Override
protected void onRefresh() {
// 初始化上下文主题资源
super.onRefresh();
try {
// 创建WEB服务器
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
// 2、ServletWebServerApplicationContext 的 createWebServer 方法
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 获取 ServletWebServerFactory 实例
ServletWebServerFactory factory = getWebServerFactory();
// getSelfInitializer 方法初始化 ServletContext
// ServletWebServerFactory(TomcatServletWebServerFactory) 的 getWebServer 方法获取 web 服务器
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
// 初始化属性资源
initPropertySources();
}
// 3、ServletContextInitializer 的 getSelfInitializer 方法
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
// 4、ServletContextInitializer 的 getSelfInitializer 方法
private void selfInitialize(ServletContext servletContext) throws ServletException {
// ServletContext 属性设置
prepareWebApplicationContext(servletContext);
// BeanFactory 中注册 application 类型的 scope
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
// ServletContextInitializer 的 onStartup 方法做初始化操作,
// 例如 DispatcherServletRegistrationBean 的 onStartup 方法实现了 ServletContext 增加 DispatcherServlet,
// TomcatEmbeddedContext 增加 dispatcherServlet 中配置的 url 与 Wrapper 的映射关系
// FilterRegistrationBean 的 onStartup 方法实现了 ServletContext 增加 CharacterEncodingFilter、HiddenHttpMethodFilter、FormContentFilter
// 和 RequestContextFilter,TomcatEmbeddedContext 增加 characterEncodingFilter、hiddenHttpMethodFilter、formContentFilter 和 requestContextFilter
// 中配置的 url 组成的FilterMap
// 总结:ServletContext 绑定 Servlet 与 Filter 的实现
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
// 5、TomcatServletWebServerFactory 的 getWebServer 方法
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
// Service 与 Connector 绑定,此时 Connector 不会启动
tomcat.getService().addConnector(connector);
// Connector 的 bindOnInit 属性值决定了是否立即打开 Socket 绑定套接字,如果是应用启动较慢,这里建议设置为 false,即应用启动之后再打开 Socket 绑定套接字监听
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
// Engine 中的 Pipeline 与 Valve 关系绑定
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
// Service 与 Connector 绑定
tomcat.getService().addConnector(additionalConnector);
}
// 创建并初始化设置 TomcatEmbeddedContext
prepareContext(tomcat.getHost(), initializers);
// 初始化 tomcat web 服务器,并返回 TomcatWebServer
return getTomcatWebServer(tomcat);
}
// 6、TomcatServletWebServerFactory 的 getTomcatWebServer 方法
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
// 7、TomcatWebServer 的构造方法
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// 启动 tomcat 服务
initialize();
}
// 8、TomcatWebServer 的 initialize 方法
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
// 这里将 Service 和 Connector 关系移除了,防止启动 Service 时将 Connector 启动,这里就是将tomcat的真正启动放在了应用启动之后,提高性能
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
// 启动 tomcat 服务器,并触发初始化监听器
// 注意这里只启动了 Server 和 Service,而没有启动 Connector 和打开 Socket 套接字,等到应用启动完成之后再启动 Connector 和 Socket
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
onRefresh 方法主要实现了以下逻辑:
1)、创建 ServletContext ,并绑定 DispatcherServlet 和 Filter
2)、创建 tomcat web 服务器并启动 tomcat,这里为了性能优化,通过设置 Connector 的 bindOnInit 属性为false来禁止完全启动 tomcat,这里只启动了 Tomcat 的 Server 和 Service,待应用启动好之后再启动 tomcat,即启动 Connector 和 Socket 套接字
3.3.2 finishBeanFactoryInitialization 方法
finishBeanFactoryInitialization 方法同样加载了所有非lazy的Singleton Bean,Spring Boot 正是在此过程中实现了 自动配置类的加载,这个是 Spring Boot 的核心,我们在下一节对其单独分析,这里不在赘述了
3.3.3 finishRefresh 方法
Spring Boot 的 finishRefresh 方法除了实现Spring原有方法的逻辑之外,还实现了完全启动WEB服务器(启动 Tomcat 的 Connector 和 Socket 套接字)功能,入口在 ServletWebServerApplicationContext 的 finishRefresh 方法,源码如下:
// 1、ServletWebServerApplicationContext 的 finishRefresh 方法
@Override
protected void finishRefresh() {
// Spring 原有方法的逻辑,这里不在分析
super.finishRefresh();
// 启动web服务器
WebServer webServer = startWebServer();
if (webServer != null) {
// 发布 ServletWebServerInitializedEvent 事件,即宣告web应用启动完成
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
// 2、ServletWebServerApplicationContext 的 startWebServer 方法
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
// 调用 WebServer(TomcatWebServer) 的 start 方法启动服务
webServer.start();
}
return webServer;
}
// 3、TomcatWebServer 的 start 方法
@Override
public void start() throws WebServerException {
// 加对象锁,防止重复启动
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
// connector 与 service 重新绑定,并启动 Connector,从而打开 Socket 套接字 完成 tomcat 的整个启动
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
// 加载 Servlet
performDeferredLoadOnStartup();
}
checkThatConnectorsHaveStarted();
this.started = true;
logger.info("Tomcat started on port(s): " + getPortsDescription(true)
+ " with context path '" + getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new WebServerException("Unable to start embedded Tomcat server",
ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
}
}
// 4、TomcatWebServer 的 addPreviouslyRemovedConnectors 方法
private void addPreviouslyRemovedConnectors() {
Service[] services = this.tomcat.getServer().findServices();
for (Service service : services) {
Connector[] connectors = this.serviceConnectors.get(service);
if (connectors != null) {
for (Connector connector : connectors) {
// connector 与 service 绑定,并启动 Connector,从而打开 Socket 套接字 完成 tomcat 的整个启动
service.addConnector(connector);
if (!this.autoStart) {
stopProtocolHandler(connector);
}
}
// 启动 tomcat 完成之后,移除缓存
this.serviceConnectors.remove(service);
}
}
}
finishRefresh 方法主要实现了完全启动 Tomcat 服务器的功能,具体启动的是 Connector,进而打开 Socket 套接字来完成 tomcat 的整个启动
四、总结
Spring Boot 的启动过程不是特别复杂,其中最难理解的就是 Tomcat 的启动,Tomcat 的启动分为了两部分,在 onRefresh 方法时启动了 Tomcat 的 Server 和 Service,而在 finishRefresh 方法中启动了 Tomcat 的 Connector,从而达到了待应用启动之后再完全启动 Tomcat 服务器的目的,这样做是为了防止应用启动过慢导致整个服务启动慢而优化的
如果大家想学习 tomcat 的启动过程,请查看 tomcat9源码 写篇文章