springboot(一) 启动过程-如何监听启动过程(1)

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…

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页