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

news2024/11/25 4:45:34

前言

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

【1】创建项目并打War包

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

② 打包方式选择"war"

③ 选择添加的模块

④ 创建的项目图示

有三个地方需要注意:

  • 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.如下图所示,点击项目结构图标

2.创建src/main/webapp

3.创建web.xml

此时项目结构图如下:

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

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

② 设置Tomcat、JDK和端口

③ 部署项目

④ 启动项目

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

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

index.html显示如下图:

【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的实现类的全类名;

如下图所示:

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

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

ServletContainerInitializer 接口如下:

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

② 步骤分析如下

第一步,Tomcat启动

第二步,根据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

第三步,创建实例

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);
}
}

如上所示,在 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类型的类如下图:

可以看到,将会创建我们的 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();
}

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

设置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启动:

Set<String> names如下:

获取了6个instance:

设置监听–ApplicationListener类型

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

至此SpringApplicationBuilder创建完毕。

② 添加 ServletContextApplicationContextInitializer

builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));

此时SpringApplication Initializers和Listener如下:

③ 设置 application.setApplicationContextClass

④ builder = configure(builder);

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

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

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

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

接下来该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 框架中获取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中:

重点来看一下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中。

继承关系如下:

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

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会在运行过程中的不同阶段,发送各种事件,来执行对应监听器的对应方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/719138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宝塔Panel搭建Python环境

服务器安装python环境 找到软件商店 应用搜索 输入&#xff1a;python 安装Python项目管理器2.4 开启首页显示 回到首页 找到python管理器并点击进入 安装对应的python版本 到这里 服务器就可以告一段落了 在本地开发服务端应用并上传服务器 将写好的python应用 导出依赖…

蓝桥杯专题-真题版含答案-【世纪末的星期】【猜年龄】【组素数】【第39级台阶】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

antv-x6在vue中使用:拖拽 Dnd、Stencil——以小诺管理平台为例

1、说明 由于antv-x6刚刚开放不久,一方面网上资料很少,此外antv目前官方的实例基本都是以react作为demo进行演示,所以vue的示例几乎没有,自己按照官方文档的react写了一个vue版本,仅供参考。 2、效果 先看一下demo的效果,如下所示 这是官方网文档的效果,同时官方也给…

【Spark】SparkCore

文章目录 RDD特点&#xff1a;弹性分布式数据集数据抽象不可变可分区、并行计算分区列表分区计算函数RDD 之间的依赖关系分区器&#xff08;可选&#xff09;首选位置&#xff08;可选&#xff09; 执行原理启动 Yarn 集群环境Spark 通过申请资源创建调度节点和计算节点Spark 框…

ue4:Dota总结_BP_CameraPawn篇

设计wasd移动&#xff1a; 鼠标拖动视口&#xff1a; 鼠标滚轮调整远近&#xff1a; Beginplay&#xff1a; qe按键旋转&#xff1a; 变量&#xff1a;

Python练手项目-学生成绩管理系统

之前在最初的学习中&#xff0c;我就写过一个Python的学生管理系统&#xff0c;但是那个很粗糙&#xff0c;很简陋 今天在学习过程中&#xff0c;再次拿出来&#xff0c;重新优化书写 希望对初学者以及需要的朋友有帮助。 文章目录 系统开发环境&#xff1a;1.实现功能2.系统设…

css3动画应用

按钮边框 按钮 首先实现正常边框按钮&#xff0c; 其次按钮相对定位&#xff0c;因为旋转的内容需要绝对定位&#xff0c; 旋转内容做伪元素处理&#xff0c;z-index设置负数是为了把按钮文字显示出来&#xff0c; 定位一半一半是把长方块中心点放按钮正中&#xff0c; 变形原…

照片jpg大小kb如何修改?图片在线压缩大小怎么处理?

最近需要在各种报名平台上传照片的小伙伴比较多&#xff0c;难免会遇到需要压缩jpg图片的情况&#xff0c;那么怎么才能将jpg图片压缩&#xff08;https://www.yasuotu.com/jpg&#xff09;呢&#xff1f;今天介绍一个图片在线压缩大小的方法&#xff0c;不用下载任何软件就可以…

内嵌 iframe 实现PDF预览

效果图如下&#xff1a; 代码如下&#xff1a; <template><div><!-- 控制浮层显示隐藏 --><el-button type"primary" size"small" class"btn" click"dialogVisible true">PDF 预览 (内嵌 iframe)</el-but…

pwn(7.4小学期)

Ret2libc 先查一下壳 32位&#xff0c;放入IDA中看看 查看一下 vulnerable_function();这个函数 Read函数&#xff0c;很明显的栈溢出 但是观察半天&#xff0c;发现这里并没有system函数 字符搜索我们看到了libc.so.6 可以想到libc函数库以及 PLT表中的代码会根据函数在…

[已成功破解] 阿里 taobao 滑条验证码 x5sec解密 slidedata参数

[已破解] 阿里 taobao 滑条验证码 x5sec解密 slidedata参数 今天在爬tb数据的时候 发现老是会触发一个滑块验证 只要过了这个滑块将滑块返回的x5secdata 的cookie 带到请求参数里面去 就能完美避开了 然后去抓了下滑块的包 过了这个滑块拿到cookie就能 访问阿里一直获取数据…

FPGA纯verilog实现UDP协议栈 AXIS用户接口,可替代Tri Mode Ethernet MAC,提供三套工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、该UDP协议栈性能4、详细设计方案网络PHYRGMII转GMII模块AXIS FIFOUDP协议栈 5、vivado工程1-->B50610 工程6、vivado工程1-->RTL8211 工程7、vivado工程1-->88E1518 工程8、上板调试验证并演示准备工作查看ARPUDP数据回环测试 9…

1.5 基于MyBatis数据库逆向生成工具,并创建serverice和controller控制层

步骤1&#xff1a;在mybatis-generator中添加要生成的数据库表名 在genratorConfig.xml内容: <!-- 数据库表 --> <table tableName"stu"></table>步骤2&#xff1a;StuMapper.xml和StuMapper拷贝到对应的mapper模块下 步骤3&#xff1a;pojo对应…

计算机视觉颜色校正方法

计算机视觉颜色校正方法 调色和色彩矫正之间的区别直方图均衡化原理实现代码 CCM颜色校正矩阵原理 深度学习Deep_White_Balance什么是sRGB图像问题描述&#xff1a;方法概述&#xff1a;模型架构&#xff1a;训练损失函数实现快速开始 调色和色彩矫正之间的区别 调色是指通过调…

Vue3使用element-plus实现弹窗效果-demo

使用 <ShareDialog v-model"isShow" onChangeDialog"onChangeDialog" /> import ShareDialog from ./ShareDialog.vue; const isShow ref(false); const onShowDialog (show) > {isShow.value show; }; const onChangeDialog (val) > {co…

使用AI人工智能给线稿上色,给漫画上色

【深度学习】AIGC &#xff0c;ControlNet 论文中的图。 一些酷炫的可以做纵深领域的图&#xff0c;这里再放一下。 下图是由手绘草稿给出图&#xff1a; 由线稿得到图&#xff0c;给漫画上色&#xff1a;

简单的PWN学习-ret2shellcode

最近笔者开始钻研pwn的一些知识&#xff0c;发现栈溢出真的非常的有意思&#xff0c;于是经过一个多礼拜的学习&#xff0c;终于是把2016年的一道CTF题给看明白了了&#xff0c;首先我们学习一下前置技能 0x01 shellcode​ 首先简单看一下shellcode是怎么生成的&#xff0c;首…

Linux和Windows两种平台安装Maven

Linux和Windows两种平台安装Maven 0. 写在前面 Linux版本&#xff1a;Centos-7.5Windows版本&#xff1a;Windows10Maven版本&#xff1a;Maven-3.5.4JDK版本&#xff1a;jdk1.8 1. Linux平台安装 1.1 查看Linux内核版本命令 查看Linux内核版本命令 [whybigdatanode02 ~]$ …

AI医疗。

随着技术的发展&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到了我们生活的许多领域&#xff0c;包括凭其强大的预测和分析能力已经走入了医疗卫生领域。特别是在使用OpenAI的GPT-4技术的chatbot&#xff0c;如chatGPT和GPT-4等&#xff0c;已经成为了给医疗行业注…

驾驶舱数据指标体系设计指南

大数据时代下&#xff0c;各行各业面对众多的顾客和复杂多变的市场需求&#xff0c;要想及时适应市场变化&#xff0c;掌握市场动态&#xff0c;就需要对各个环节的数据进行分析&#xff0c;得到科学有效的结论来指导决策&#xff0c;这就离不开领导驾驶舱。 一、领导驾驶舱是什…