5

SpringBoot配置外部Tomcat项目启动流程源码分析(一)

 2 years ago
source link: https://blog.51cto.com/u_15047392/5689080
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

SpringBoot应用默认以Jar包方式并且使用内置Servlet容器(默认Tomcat),该种方式虽然简单但是默认不支持JSP并且优化容器比较复杂。故而我们可以使用习惯的外置Tomcat方式并将项目打War包。

【1】创建项目并打War包

① 同样使用Spring Initializer方式创建项目

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_tomcat

② 打包方式选择"war"

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_spring_02

③ 选择添加的模块

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_spring_03

④ 创建的项目图示

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_04

有三个地方需要注意:

  • pom中打包方式已经为war;
  • 对比默认为jar的项目多了ServletInitializer类;
  • 项目结构没有src/main/webapp,且没有WEB/INF web.xml。

ServletInitializer类如下:

public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootwebprojectApplication.class);
}
}

pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.web</groupId>
<artifactId>springbootwebproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>springbootwebproject</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--这里修改了内置Tomcat的作用域-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

⑤ 补全项目结构

第一种方式,手动创建src/main/webapp, WEB/INF以及web.xml。

第二种方式,使用idea创建,步骤如下:

1.如下图所示,点击项目结构图标

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_spring_05

2.创建src/main/webapp

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_tomcat_06

3.创建web.xml

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_07
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_08

此时项目结构图如下:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_09

【2】使用外部配置的Tomcat启动项目

① 点击"Edit Configurations…"添加Tomcat。

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_10

② 设置Tomcat、JDK和端口

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_11

③ 部署项目

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_12
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_13

④ 启动项目

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_spring_14

此时如果webapp 下有index.html,index.jsp,则会默认访问index.html。

如果只有index.jsp,则会访问index.jsp;如果webapp下无index.html或index.jsp,则从静态资源文件夹寻找index.html;如果静态资源文件夹下找不到index.html且项目没有对"/"进行额外拦截处理,则将会返回默认错误页面。

index.html显示如下图:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_15

【3】SpringBoot 使用外部Tomcat启动原理

① 首先看Servlet3.0中的规范

  • javax.servlet.ServletContainerInitializer(其是一个接口) 类是通过JAR服务API查找的。对于每个应用程序,ServletContainerInitializer的一个实例是由容器在应用程序启动时创建。
  • 提供servletcontainerinitializer实现的框架必须将名为javax.servlet的文件捆绑到jar文件的META-INF/services目录中。根据JAR服务API,找到指向ServletContainerInitializer的实现类。
  • 除了ServletContainerInitializer 之外,还有一个注解–@HandlesTypes。ServletContainerInitializer 实现上的handlesTypes注解用于寻找感兴趣的类–要么是@HandlesTypes注解指定的类,要么是其子类。
  • 不管元数据完成的设置如何,都将应用handlesTypes注解。
  • ServletContainerInitializer实例的onStartup 方法将在应用程序启动时且任何servlet侦听器事件被激发之前被调用。
  • ServletContainerInitializer 的onStartup 方法调用是伴随着一组类的(Set<Class<?>> webAppInitializerClasses),这些类要么是initializer的扩展类,要么是添加了@HandlesTypes注解的类。将会依次调用webAppInitializerClasses实例的onStartup方法。

总结以下几点:

1)服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面
ServletContainerInitializer实例;

2)jar包的META-INF/services文件夹下,有一个名为
javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;

如下图所示:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_16

3)还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

4)容器启动过程中首先调用
ServletContainerInitializer 实例的onStartup方法。

ServletContainerInitializer 接口如下:

public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

② 步骤分析如下

第一步,Tomcat启动

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_17

第二步,根据Servlet3.0规范,找到
ServletContainerInitializer ,进行实例化

jar包路径:

org\springframework\spring-web\4.3.14.RELEASE\
spring-web-4.3.14.RELEASE.jar!\METAINF\services\
javax.servlet.ServletContainerInitializer:

Spring的web模块里面有这个文件:

org.springframework.web.SpringServletContainerInitializer
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_spring_18

第三步,创建实例

SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set集合,为这些WebApplicationInitializer类型的类创建实例并遍历调用其onStartup方法。

SpringServletContainerInitializer 源码如下(调用其onStartup方法):

//感兴趣的类为WebApplicationInitializer及其子类
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//先调用onStartup方法,会传入一系列webAppInitializerClasses
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
//遍历感兴趣的类
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
//判断是不是接口,是不是抽象类,是不是该类型
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//实例化每个initializer并添加到initializers中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
//依次调用initializer的onStartup方法。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_19

如上所示,在
SpringServletContainerInitializer方法中又调用每一个initializer的onStartup方法。即先调用SpringServletContainerInitializer实例的onStartup方法,在onStartup()方法内部又遍历每一个WebApplicationInitializer类型的实例,调用其onStartup()方法。

WebApplicationInitializer(Web应用初始化器)是什么?

在Servlet 3.0+环境中提供的一个接口,以便编程式配置ServletContext而非传统的xml配置。该接口的实例被
SpringServletContainerInitializer自动检测(@HandlesTypes(WebApplicationInitializer.class)这种方式)。而SpringServletContainerInitializer是Servlet 3.0+容器自动引导的。通过WebApplicationInitializer,以往在xml中配置的DispatcherServlet、Filter等都可以通过代码注入。你可以不用直接实现WebApplicationInitializer,而选择继承AbstractDispatcherServletInitializer。

WebApplicationInitializer类型的类如下图:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_springboot_20

可以看到,将会创建我们的
com.web.ServletInitializer(继承自SpringBootServletInitializer)实例,并调用onStartup方法。

第四步:我们的
SpringBootServletInitializer的实例(com.web.ServletInitializer)会被创建对象,并执行onStartup方法(com.web.ServletInitializer继承自SpringBootServletInitializer,故而会调用SpringBootServletInitializer的onStartup方法)

SpringBootServletInitializer源码如下:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
//创建WebApplicationContext
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
//如果根容器不为null,则添加监听--注意这里的ContextLoaderListener,
//contextInitialized方法为空,因为默认application context已经被初始化
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}

可以看到做了两件事:创建RootAppContext 和为容器添加监听。

创建WebApplicationContext 源码如下:

protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//创建SpringApplicationBuilder --这一步很关键
SpringApplicationBuilder builder = createSpringApplicationBuilder();
//设置应用主启动类--本文这里为com.web.ServletInitializer
builder.main(getClass());

*/从servletContext中获取servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)作为parent。第一次获取肯定为null
*/
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
//以将ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE重置为null
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
//注册一个新的ParentContextApplicationContextInitializer--包含parent
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
//注册ServletContextApplicationContextInitializer--包含servletContext
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
//设置applicationContextClass为AnnotationConfigServletWebServerApplicationContext
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder = configure(builder);
//添加监听器
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
//返回一个准备好的SpringApplication ,准备run-很关键
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
//启动应用
return run(application);
}

【4】createRootApplicationContext详细流程源码分析

① createRootApplicationContext().createSpringApplicationBuilder()

跟踪代码到:

public SpringApplicationBuilder(Class<?>... sources) {
this.application = createSpringApplication(sources);
}

此时的Sources为空,继续跟踪代码:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//web应用类型--Servlet
this.webApplicationType = deduceWebApplicationType();
获取ApplicationContextInitializer,也是在这里开始首次加载spring.factories文件
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
这里是第二次加载spring.factories文件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_21

ApplicationContextInitializer是spring组件spring-context组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处于自定义逻辑。

ApplicationContextInitializer(应用上下文初始化器)是什么?


ConfigurableApplicationContext-Spring IOC容器称为“已经被刷新”状态前的一个回调接口去初始化ConfigurableApplicationContext。通常用于需要对应用程序上下文进行某些编程初始化的Web应用程序中。例如,与ConfigurableApplicationContext#getEnvironment() 对比,注册property sources或激活配置文件。另外ApplicationContextInitializer(和子类)相关处理器实例被鼓励使用去检测org.springframework.core.Ordered接口是否被实现或是否存在org.springframework.core.annotation.Order注解,如果存在,则在调用之前对实例进行相应排序。

spring.factories文件中的实现类:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# 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.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_springboot_22

设置WebApplicationType

private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

这里主要是通过判断REACTIVE相关的字节码是否存在,如果不存在,则web环境即为SERVLET类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。

设置Initializer–
ApplicationContextInitializer类型

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//线程上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

这里ClassLoader 获取的是线程上下文类加载器,这里使用的是Tomcat启动:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_23

Set<String> names如下:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_24

获取了6个instance:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_springboot_25

设置监听–ApplicationListener类型

此时的type为ApplicationListener,Set<String> names如下:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_html_26

至此SpringApplicationBuilder创建完毕。

② 添加
ServletContextApplicationContextInitializer

builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));

此时SpringApplication Initializers和Listener如下:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_27

③ 设置
application.setApplicationContextClass

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_springboot_28

④ builder = configure(builder);

此时调用我们的ServletInitializer的configure方法:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_springboot_29
SpringBoot配置外部Tomcat项目启动流程源码分析(一)_springboot_30

⑤ SpringApplication application = builder.build()创建应用

把我们的主类添加到application 中:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_spring_31

⑥ 将
ErrorPageFilterConfiguration添加到Set<Class<?>> primarySources

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_32

接下来该run(application)了注意直到此时,我们让没有创建我们想要的容器,容器将会在run(application)中创建。

SpringApplication.run源码如下所示:

/**
run Spring application,创建并刷新一个新的ApplicationContext
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//简单的秒表,允许对许多任务计时,显示每个指定任务的总运行时间和运行时间。非线程安全
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//异常报告集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很
//多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
configureHeadlessProperty();

//第一步:获取并启动监听器 SpringApplicationRunListener只有一个实现类EventPublishingRunListener,
//EventPublishingRunListener有一个SimpleApplicationEventMulticaster
//SimpleApplicationEventMulticaster有一个defaultRetriver
//defaultRetriver有个属性为applicationListeners
//每一次listeners.XXX()方法调用,都将会广播对应事件给applicationListeners监听器处理
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();//run方法第一次被调用时,调用listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//第二步:构造容器环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//设置需要忽略的bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//第三步:创建容器
context = createApplicationContext();
//第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
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);
}
//容器已经被刷新,但是CommandLineRunners和ApplicationRunners还没有被调用
listeners.started(context);
//调用CommandLineRunner和ApplicationRunner的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//在run结束前,且调用CommandLineRunner和ApplicationRunner的run方法后,调用
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

【5】SpringApplication.run方法详细分析-获取并启动监听器

① 获取监听器getRunListeners

SpringApplicationRunListeners listeners = getRunListeners(args);

跟进
SpringApplication.getRunListeners方法(返回SpringApplicationRunListeners):

private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}

上面可以看到,args本身默认为空,但是在获取监听器的方法中,
getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)将当前对象作为参数,该方法用来获取spring.factories对应的监听器:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//获取类加载器 WebappClassLoader
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//根据类加载器,获取SpringApplicationRunListener(type)相关的监听器
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//创建factories
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

Set<String> names如下:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_java项目_33

整个 springBoot 框架中获取factories的方式统一如下:

@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
// //装载class文件到内存
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
//主要通过反射创建实例
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}

上面通过反射获取实例时会触发
EventPublishingRunListener的构造函数。如下图所示将会把application的listener添加到SimpleApplicationEventMulticaster initialMulticaster的ListenerRetriever defaultRetriever的Set<ApplicationListener<?>> applicationListeners中:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_tomcat_34

重点来看一下addApplicationListener方法:

public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) {
// Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same listener.
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
if (singletonTarget instanceof ApplicationListener) {
this.defaultRetriever.applicationListeners.remove(singletonTarget);
}
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}

上述方法定义在
SimpleApplicationEventMulticaster父类AbstractApplicationEventMulticaster中。关键代码为this.defaultRetriever.applicationListeners.add(listener);,这是一个内部类,用来保存所有的监听器。也就是在这一步,将spring.factories中的监听器传递到SimpleApplicationEventMulticaster中。

继承关系如下:

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_tomcat_35

② listeners.starting()–
SpringApplicationRunListener启动–监听器第一次处理事件

SpringBoot配置外部Tomcat项目启动流程源码分析(一)_tomcat_36

listeners.starting();,获取的监听器为
EventPublishingRunListener,从名字可以看出是启动事件发布监听器,主要用来发布启动事件。

SpringApplicationRunListener是run()方法的监听器,其只有一个实现类EventPublishingRunListener。SpringApplicationRunListeners是SpringApplicationRunListener的集合类。

也就是说将会调用
EventPublishingRunListener的starting()方法。

public void starting() {
//关键代码,这里是创建application启动事件`ApplicationStartingEvent`
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}

EventPublishingRunListener这个是springBoot框架中最早执行的监听器,在该监听器执行started()方法时,会继续发布事件,也就是事件传递。这种实现主要还是基于spring的事件机制。

继续跟进
SimpleApplicationEventMulticaster,有个核心方法:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// //获取线程池,如果为空则同步处理。这里线程池为空,还未没初始化。
Executor executor = getTaskExecutor();
if (executor != null) {
异步发送事件
executor.execute(() -> invokeListener(listener, event));
}
else {
// //同步发送事件
invokeListener(listener, event);
}
}
}

ApplicationListener<E extends ApplicationEvent>接口有个抽象方法onApplicationEvent(E event)子类必须实现。该方法用来处理对应事件。

其中getApplicationListeners(event, type)主要有四种listener:

  • LoggingApplicationListener(处理日志)
  • BackgroundPreinitializer
  • DelegatingApplicationListener
  • LiquibaseServiceLocatorApplicationListener

这是springBoot启动过程中,第一处根据类型,执行监听器的地方。根据发布的事件类型从上述10种监听器中选择对应的监听器进行事件发布,当然如果继承了 springCloud或者别的框架,就不止10个了。这里选了一个 springBoot 的日志监听器来进行讲解,核心代码如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
//在springboot启动的时候
if (event instanceof ApplicationStartedEvent) {
onApplicationStartedEvent((ApplicationStartedEvent) event);
}
//springboot的Environment环境准备完成的时候
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
//在springboot容器的环境设置完成以后
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
//容器关闭的时候
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
//容器启动失败的时候
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}

因为我们的事件类型为ApplicationEvent,所以会执行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot会在运行过程中的不同阶段,发送各种事件,来执行对应监听器的对应方法。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK