SpringBoot源码解析(三)ApplicationStartingEvent

上篇文章分析了SpringBoot事件发布机制的大致原理,并且提到了SpringBoot启动过程中的第一个事件ApplicationStartingEvent,以及监听了该事件的四个监听器:

  • DelegatingApplicationListener
  • LoggingApplicationListener
  • LiquibaseServiceLocatorApplicationListener
  • BackgroundPreinitializer

首先还是先简单说下为什么这四个监听器能监听到ApplicationStartingEvent事件,权当是对上篇文章的一个回顾
监听器能够监听到当前事件,分三种情况:

  1. 监听器实现了GenericApplicationListener接口,并且在supportsEventType方法中指定了当前事件的类型
  2. 监听器实现了SmartApplicationListener接口,并且在supportsEventType方法中指定了当前事件的类型
  3. 监听器没有实现上述接口,但其上层接口ApplicationListener< T >中的事件类型 T 是当前事件的父类

在上述四个监听器中,LoggingApplicationListener属于类型1,它实现了GenericApplicationListener接口

public class LoggingApplicationListener implements GenericApplicationListener {

其supportsEventType方法如下

    public boolean supportsEventType(ResolvableType resolvableType) {
        return this.isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
    }

EVENT_TYPES在静态代码块中做了初始化,其中指定了ApplicationStartingEvent

    static {
    	......
    	......
        EVENT_TYPES = new Class[]{ApplicationStartingEvent.class, ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class, ContextClosedEvent.class, ApplicationFailedEvent.class};
        ......
        ......
    }

实际上LoggingApplicationListener还重写了supportsSourceType方法,只不过其中指定了关注的事件源包含了SpringApplication,之前有提到过这个方法默认是true,大多数类是没有重写的,这里就一笔带过了

    public boolean supportsSourceType(Class<?> sourceType) {
        return this.isAssignableFrom(sourceType, SOURCE_TYPES);
    }
    ......
    ......
    SOURCE_TYPES = new Class[]{SpringApplication.class, ApplicationContext.class};

另外三个监听器都属于类型3

public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {
public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener<ApplicationStartingEvent> {
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

ApplicationStartingEvent的类型参数为ApplicationEvent
BackgroundPreinitializer 的类型参数为SpringApplicationEvent
它们都是ApplicationStartingEvent的父类
而LiquibaseServiceLocatorApplicationListener 的类型参数正是ApplicationStartingEvent

ApplicationStartingEvent的继承结构:
在这里插入图片描述
接下来逐个分析下这四个监听器在这个启动事件中作了什么处理,看下他们的onApplicationEvent方法

DelegatingApplicationListener

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            List<ApplicationListener<ApplicationEvent>> delegates = this.getListeners(((ApplicationEnvironmentPreparedEvent)event).getEnvironment());
            if (delegates.isEmpty()) {
                return;
            }
            this.multicaster = new SimpleApplicationEventMulticaster();
            Iterator var3 = delegates.iterator();
            while(var3.hasNext()) {
                ApplicationListener<ApplicationEvent> listener = (ApplicationListener)var3.next();
                this.multicaster.addApplicationListener(listener);
            }
        }
        if (this.multicaster != null) {
            this.multicaster.multicastEvent(event);
        }

    }

这个方法中,先判断事件是否是ApplicationEnvironmentPreparedEvent,是的话才会做处理,并且将自己内部的事件多播器初始化
ApplicationEnvironmentPreparedEvent和ApplicationStartingEvent都派生于SpringApplicationEvent
在这里插入图片描述

所以此时并不会进它的if,那么下面的multicaster也是null的,虽然此时接收到了事件,但是它并没有做任何处理

既然这个监听器只处理ApplicationEnvironmentPreparedEvent,为什么不让它像其他监听器一样,派生自
GenericApplicationListener或者SmartApplicationListener接口,然后在supportsEventType方法中指定监听的事件类型呢?
因为它需要在ApplicationEnvironmentPreparedEvent事件到达时做一个初始化,当初始化完成后,也就是内部的事件多播器初始化完成后,后续就可能会处理其他类型的事件了

LoggingApplicationListener

熟悉SpringBoot的朋友可能都知道,SpringBoot的默认日志体系是locback,而这个Listener就是确定要使用的日志体系

它的onApplicationEvent监听了很多事件,我们进入第一个分支onApplicationStartingEvent

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartingEvent) {
            this.onApplicationStartingEvent((ApplicationStartingEvent)event);
        } else if (event instanceof ApplicationEnvironmentPreparedEvent) {
            this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
        } else if (event instanceof ApplicationPreparedEvent) {
            this.onApplicationPreparedEvent((ApplicationPreparedEvent)event);
        } else if (event instanceof ContextClosedEvent && ((ContextClosedEvent)event).getApplicationContext().getParent() == null) {
            this.onContextClosedEvent();
        } else if (event instanceof ApplicationFailedEvent) {
            this.onApplicationFailedEvent();
        }
    }
    private void onApplicationStartingEvent(ApplicationStartingEvent event) {
        this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
        this.loggingSystem.beforeInitialize();
    }

从get方法中获取了一个日志体系

    public static LoggingSystem get(ClassLoader classLoader) {
        String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
        if (StringUtils.hasLength(loggingSystem)) {
            return (LoggingSystem)("none".equals(loggingSystem) ? new LoggingSystem.NoOpLoggingSystem() : get(classLoader, loggingSystem));
        } else {
            return (LoggingSystem)SYSTEMS.entrySet().stream().filter((entry) -> {
                return ClassUtils.isPresent((String)entry.getKey(), classLoader);
            }).map((entry) -> {
                return get(classLoader, (String)entry.getValue());
            }).findFirst().orElseThrow(() -> {
                return new IllegalStateException("No suitable logging system located");
            });
        }
    }

SYSTEM_PROPERTY是取的自身的全限定名,即org.springframework.boot.logging.LoggingSystem

public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();

一般是不配置这个属性的,进入else分支
SYSTEMS是一个map,在静态代码块中做了初始化

	private static final Map<String, String> SYSTEMS;
	......
	......
    static {
        Map<String, String> systems = new LinkedHashMap();
        systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
        systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
        systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
        SYSTEMS = Collections.unmodifiableMap(systems);
    }

大概逻辑就是遍历这个map,过滤出存在于classpath下的key,然后实例化value,返回找到的第一个作为系统的日志策略

ch.qos.logback.core.Appender是logback的类,在SpringBoot中是默认集成进来的,我们SpringBoot项目会引入一个spring-boot-starter

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

其内部引入了一个logging

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
        <version>2.1.4.RELEASE</version>
        <scope>compile</scope>
    </dependency>

而在这个logging内部就引入了logback

	<dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
        <scope>compile</scope>
    </dependency>

第二个org.apache.logging.log4j.core.impl.Log4jContextFactory,需要引入log4j的依赖才会有
第三个java.util.logging.LogManager是JDK中的日志框架

所以此时能加载到的是第一个和第三个,然后最终返回的就是logback,SpringBoot默认使用logback就是在这里确定的
在这里插入图片描述
如果在spring-boot-starter中排除掉logging的依赖,就会采用java自带的java.util.logging

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

在这里插入图片描述
如果想用log4j,就需要引入log4j的依赖,同时在spring-boot-starter中排掉logback

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

在这里插入图片描述
确定好日志框架后,再调用beforeInitialize做一些预初始化工作

总结下SpringBoot的默认日志策略

  1. 如果引入了logback,采用logback
  2. 如果没有logback,但有log4j,采用log4j
  3. 如果以上都没有,就使用jdk自带的log

LiquibaseServiceLocatorApplicationListener

    public void onApplicationEvent(ApplicationStartingEvent event) {
        if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator", event.getSpringApplication().getClassLoader())) {
            (new LiquibaseServiceLocatorApplicationListener.LiquibasePresent()).replaceServiceLocator();
        }
    }

当引入了liquibase的依赖才会触发事件,这是一个管理数据库脚本的开源组件,老实说我从来没有见过哪个项目用它,不是很了解,感兴趣的朋友可以自己找找资料

BackgroundPreinitializer

这个监听器主要在ApplicationStartingEvent 事件中提前做一些后台的初始化工作

    public void onApplicationEvent(SpringApplicationEvent event) {
        if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore") && event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) {
            this.performPreinitialization();
        }
        if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) && preinitializationStarted.get()) {
            try {
                preinitializationComplete.await();
            } catch (InterruptedException var3) {
                Thread.currentThread().interrupt();
            }
        }
    }

先通过Boolean.getBoolean查看是否配置了spring.backgroundpreinitializer.ignore属性

    public static boolean getBoolean(String name) {
        boolean result = false;
        try {
            result = parseBoolean(System.getProperty(name));
        } catch (IllegalArgumentException | NullPointerException e) {
        }
        return result;
    }

所以可以通过在启动参数中添加配置-Dspring.backgroundpreinitializer.ignore=true来关闭这个后台加载的过程
在这里插入图片描述
也可以在启动类的main方法中显示的配置

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        System.setProperty("spring.backgroundpreinitializer.ignore", "true");
        SpringApplication.run(Application.class, args);
    }
}

但不能通过配置文件来设置,因为此时配置文件还没有被加载
一般不会去关闭这个流程,看下performPreinitialization方法做了哪些事情

    private void performPreinitialization() {
        try {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    this.runSafely(new BackgroundPreinitializer.ConversionServiceInitializer());
                    this.runSafely(new BackgroundPreinitializer.ValidationInitializer());
                    this.runSafely(new BackgroundPreinitializer.MessageConverterInitializer());
                    this.runSafely(new BackgroundPreinitializer.MBeanFactoryInitializer());
                    this.runSafely(new BackgroundPreinitializer.JacksonInitializer());
                    this.runSafely(new BackgroundPreinitializer.CharsetInitializer());
                    BackgroundPreinitializer.preinitializationComplete.countDown();
                }
                public void runSafely(Runnable runnable) {
                    try {
                        runnable.run();
                    } catch (Throwable var3) {
                        ;
                    }
                }
            }, "background-preinit");
            thread.start();
        } catch (Exception var2) {
            preinitializationComplete.countDown();
        }
    }

启动了一个线程,然后在这个线程里又启动了其他线程去做一些初始化,比如ConversionServiceInitializer

    private static class ConversionServiceInitializer implements Runnable {
        private ConversionServiceInitializer() {
        }

        public void run() {
            new DefaultFormattingConversionService();
        }
    }

run方法中new了一个DefaultFormattingConversionService的实例,这个主要是跟类型转换有关

其他线程也差不多,都是做一些基础的配置,诸如校验、消息转换、默认编码等等

至此对ApplicationStartingEvent的分析就告一段落,接下来就回到SpringApplication的run方法中分析后面的流程

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Springboot源码解析PDF是一本深入解析Springboot框架的技术书籍,涵盖了Springboot的背景、原理、设计思路、运行机制、开发实践等方面。全书主要分为部分:第一部分介绍Springboot的基础知识,包括Spring框架的常用注解、Springboot的配置、自动配置原理等;第二部分深入探讨Springboot的主要功能,如数据访问、Web开发、缓存、消息、安全等;第部分着重介绍开发Springboot应用的最佳实践,包括Springboot与其他框架的结合使用、集成测试、监控与诊断等。 阅读Springboot源码解析PDF可以让开发者更深入理解Springboot的设计理念、技术实现以及应用场景,在实际项目开发中更加灵活、高效地使用Springboot。该书对于有一定JavaSpring框架基础的开发者来说是一本非常优秀的参考资料,也是Java开发者必不可少的技术读物。同时,该书也是借助源码解析的方式,让读者更加系统化地学习Springboot技术,具有很高的实用性和参考价值。总之,阅读Springboot源码解析PDF有助于开发者更好地掌握Springboot技术,提高应用开发效率和代码质量。 ### 回答2: Spring Boot源码解析pdf是一本介绍Spring Boot框架的开源书籍,该书的目的是帮助开发者深入了解Spring Boot的内部工作原理和实现细节。 该书首先从Spring Boot框架的源码结构和核心模块入手,详细介绍了Spring Boot的MVC、ORM、缓存、安全等核心功能的实现原理。同时,该书还介绍了Spring Boot对微服务的支持和整合Spring Cloud的方式,让开发者更深入了解Spring Boot在分布式架构中的应用。 在讲解源码实现原理的同时,该书还指出了一些常见的开发问题和易错点,并提供了相应的解决方案。此外,该书还通过一系列的案例,全面展示了Spring Boot的实际开发应用场景,帮助开发者更好地应用Spring Boot框架。 总的来说,Spring Boot源码解析pdf是一本非常实用的书籍,能够帮助开发者快速掌握Spring Boot框架的内部实现原理,提高开发效率和代码质量。同时,该书还可以作为学习Spring Boot的参考资料,对于想要深入学习和研究Spring Boot的开发者来说是非常有用的。 ### 回答3: Spring Boot 是一个很受欢迎的 Java 框架,它简化了开发者的工作,允许他们更快速地构建和部署应用程序。Spring Boot 的优点包括简洁的配置、内嵌的 Web 容器和现成的插件,让开发者可以更专注于业务逻辑。Spring Boot的源码解析是学习它的关键。 Spring Boot 的源码解析Java初学者来说可能会比较复杂,但是它对于学习框架和原理是非常有益的。一个好的 Spring Boot 项目需要基于很好的基础,这就要求开发者了解其源码源码解析可以帮助开发者了解各种设计模式和组件的原理,有助于解决实际问题。 在 Spring Boot 的源码解析中,我们将会找到很多有用的信息。例如,我们可以看到 Spring Boot 如何通过注解处理器创建并配置 bean,这可以帮助我们更好地理解依赖注入和 IoC 容器。此外,我们还可以了解 Spring Boot 如何处理 HTTP 请求、创建模板、处理安全性等。这些知识可以帮助我们更好地理解 Spring Boot 内部的工作机制。 总之,Spring Boot 的源码解析是必不可少的一部分,它可以帮助开发者更好地了解和使用该框架,掌握在实践中所需的知识和技能。如果您是一名 Spring 开发者,那么深入了解 Spring Boot 的源码将会是一个很好的学习过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值