SpringApplication
了解下Springboot的启动过程, 目标是整明白如何监听启动过程中的各个节点. Springboot启动就是一个main方法
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
那首先看下SpringApplication类. 按惯例先看注释
注释翻译
该类通过main方法引导和启动一个spring应用. 默认情况下该类会通过以下步骤引导你的应用:
a 根据你的classpath创建一个合适的 ApplicationContext;
b 注册一个CommandLinePropertySource实例来暴露命令行参数作为spring配置信息
c 刷新应用上下文, 加载全部单例bean
d 触发任何CommandLineRunner
在大部分情况下, run方法都可以执行运行;
如果为了配置更高级的属性, 可以在运行前自定义SpringApplication实例
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
}
SpringApplication可以从不同的地方读取bean. 通常建议使用一个Configuration类来引导你的应用, 然而你也可以通过设置SpringApplication的属性source, 通过以下方式:
a 全限定名方式, 通过AnnotatedBeanDefinitionReader加载;
b xml配置文件, 通过XMLBeanDefinitionReader加载;
c groovy脚本, 通过GroovyBeanDefinition加载;
d 扫描包路径, 通过ClassPathBeanDefinitionScanner扫描;
配置属性也会绑定到SpringApplication上. 这样使得SpringApplication可以动态增加属性;
启动
大概知道该类的作用后, 我们来看下run方法
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
静态方法run会首先实例化SpringApplication对象, 构造方法中会初始化一些属性
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();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
实例化后执行run方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发送启动消息
listeners.starting();
...
return context;
}
聪明如我, 一下就发现了关键点(注释代码), 看下starting()内部
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
大概意思是专门有个类会发送ApplicationStartingEvent事件到各个listener, 那我只要实现要求的listener不就可以监听到事件了么? so easy~
干~
@Component
public class ApplicationStartListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
System.out.println("starting event: " + event);
} else {
System.out.println("other event: " + event);
}
}
}
打印结果
other event: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5c33f1a9, started on Thu Sep 24 09:02:20 CST 2020]
other event: org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@727320fa]
other event: org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@1f3b992]
other event: org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@1f3b992]
这…事件是收到了, 但是怎么没有预期的ApplicationStartingEvent事件?
监听ApplicationStartingEvent事件
从启动的代码看, 逻辑很简单, 就是将时间发送给各个listener, 至于为什么我们的listener没有收到, 唯一的解释就是我们的listener没有在里边, 所以我们看下这个listener的注册逻辑, 把我们的listener注册进去就好了~ so easy~~
再回到发送方法, 很明显getApplicationListeners方法获取了全部的listener, 一直跟进去发现在retrieveApplicationListeners方法中获取到了listener集合.
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 获取listener并循环发送
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// Quick check for existing entry on ConcurrentHashMap...
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
// Fully synchronized building and caching of a ListenerRetriever
synchronized (this.retrievalMutex) {
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
// 这里获取了listener集合
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.retrievalMutex) {
// 这里给集合赋值
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
}
// 根据事件过滤listener, 只把符合的listener放到allListener中
for (ApplicationListener<?> listener : listeners) {
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
allListeners.add(listener);
}
}
if (!listenerBeans.isEmpty()) {
...
}
// 排序
AnnotationAwareOrderComparator.sort(allListeners);
// 缓存
if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
retriever.applicationListeners.clear();
retriever.applicationListeners.addAll(allListeners);
}
return allListeners;
}
那这个this.defaultRetriever.applicationListeners是从哪来的呢?
在执行starting()方法的类EventPublishingRunListener中, 在初始化的时候有赋值操作
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
// 从application中获取listener
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
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();
}
}
代码中的application就是main方法中启动的SpringApplication啦~ 那下面就看下SpringApplication中的listener是怎么来的.
再回看下SpringApplication的构造方法
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();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 看这里~~
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 这里会找项目下的META-INF/spring.factories文件
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
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<?> 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;
}
上述代码中, getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object… args)方法会从spring.factories文件中读取键值对, key为接口, value为实现类, springboot中的如下, 就像是java中的spi机制
# 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.BeanDefinitionOverrideFailureAnalyzer,\
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.NoSuchMethodFailureAnalyzer,\
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
看到这里才发现EventPublishingRunListener(就是starting方法所在的类)也在其中, 它也是以这样的形式加载进来的.
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 这里, 里面的逻辑和上面一样
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
...
模仿这种形式, 我们在项目中创建spring.factories文件, 并将监听配置进去
org.springframework.context.ApplicationListener=com.togo.common.listener.ApplicationStartListener
再次启动, 结果如下, 可以看到监听到了start事件, 也出现了很多之前没有看到的事件
starting event: org.springframework.boot.context.event.ApplicationStartingEvent[source=org.springframework.boot.SpringApplication@5383967b]
other event: org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplication@5383967b]
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.8.RELEASE)
other event: org.springframework.boot.context.event.ApplicationContextInitializedEvent[source=org.springframework.boot.SpringApplication@5383967b]
2020-09-28 09:22:08.964 INFO 13276 --- [ main] com.togo.WorkerlistApplication : Starting WorkerlistApplication on serpmelondeMacBook-Pro.local with PID 13276 (/Users/serpmelon/IdeaProjects/workerlist/target/classes started by serpmelon in /Users/serpmelon/IdeaProjects/workerlist)
2020-09-28 09:22:08.968 INFO 13276 --- [ main] com.togo.WorkerlistApplication : No active profile set, falling back to default profiles: default
other event: org.springframework.boot.context.event.ApplicationPreparedEvent[source=org.springframework.boot.SpringApplication@5383967b]
2020-09-28 09:22:09.764 INFO 13276 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9024 (http)
2020-09-28 09:22:09.779 INFO 13276 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-09-28 09:22:09.779 INFO 13276 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.24]
2020-09-28 09:22:09.846 INFO 13276 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/wx] : Initializing Spring embedded WebApplicationContext
2020-09-28 09:22:09.847 INFO 13276 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 746 ms
2020-09-28 09:22:10.164 INFO 13276 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2020-09-28 09:22:11.005 INFO 13276 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-09-28 09:22:11.142 INFO 13276 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
other event: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@10e31a9a, started on Mon Sep 28 09:22:09 CST 2020]
other event: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@10e31a9a, started on Mon Sep 28 09:22:09 CST 2020]
2020-09-28 09:22:11.209 INFO 13276 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9024 (http) with context path '/wx'
other event: org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@47ac613b]
other event: org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@47ac613b]
2020-09-28 09:22:11.214 INFO 13276 --- [ main] com.togo.WorkerlistApplication : Started WorkerlistApplication in 17.523 seconds (JVM running for 22.926)
other event: org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@5383967b]
other event: org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@5383967b]
other event: org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@5383967b]
other event: org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@5383967b]
那么springboot是怎么知道要扫描哪个项目下的spring.factories? 既然是加载项目下的资源, 首先想到的应该就是通过ClassLoader, 回顾下代码, 找找ClassLoader相关逻辑.
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 果然有获取ClassLoader的逻辑
ClassLoader classLoader = getClassLoader();
// 这里在loadFactoryNames方法中使用ClassLoader获取资源classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
public ClassLoader getClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
// 获取当前线程的ClassLoader
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
总结
1 springboot在启动的时候会初始化SpringApplication对象
2 SpringApplication对象在初始化时会通过当前线程的ClassLoader加载spring.factories文件中的对象
3 所以用户可以通过配置spring.factories文件对项目进行扩展, 比如实现ApplicationListener接口, 并配置在spring.factories文件中, springboot启动后会发送启动节点消息到自定义Listener对象.
留个小问题, 在配置自定义listener到spring.factories文件前, 我们的listener其实也可以接收到事件(只是不是我们预期的), 那么这些事件又是从哪来的呢? Springboot启动过程到底有多少的事件可以监听?
To Be Continue…