SpringBoot 核心原理

生命周期

创建生命周期调用方法类

通过源码可知,SpringBoot 在调用生命周期时,是通过查找包的“META-INF\spring.factories”文件中找到对应的需要加载的Listener类

其中SpringBoot的Listener类在 META-INF\spring.factories 中如下:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

那么,我们也可以在自己的包下创建“META-INF\spring.factories”文件,并指定 SpringApplicationRunListener 的生命周期类:

 

# 自定义让SpringBoot 扫描到的 Listener
org.springframework.boot.SpringApplicationRunListener=cn.unsoft.learn.listener.MyListener

MyListener 类需要实现 SpringApplicationRunListener 接口,如下

public class MyListener implements SpringApplicationRunListener {

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        SpringApplicationRunListener.super.starting(bootstrapContext);
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        SpringApplicationRunListener.super.contextPrepared(context);
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        SpringApplicationRunListener.super.contextLoaded(context);
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        SpringApplicationRunListener.super.started(context, timeTaken);
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        SpringApplicationRunListener.super.ready(context, timeTaken);
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        SpringApplicationRunListener.super.failed(context, exception);
    }
}

 

 

生命周期说明

运行图如下:

周期说明:

  • 1.引导时机:利用 BootstrapContext 引导整个项目启动
  •  starting:
    • 应用开始,SpringApplication 的 run 方法一调用,只要有了 BootstrapContext 就执行
  • environmentPrepared:
    • 环境准备好(把启动参数等绑定到环境变量中),但是ioc容器还没有创建【调用一次】
  • 2.启动时机
  • contextPrepared:
    • ioc容器创建并前备好,但是 sources(主配置类)没加载。完成后并关闭引导BootstrapContext ,组件还没有创建【调用一次】
  • contextLoaded:
    • ioc容器加载。主配置类加载进去了,但是ioc容器还没有刷新(bean还没创建),后面会说刷新机制
  • started:
    • ioc容器刷新了(即所有bean都造好了),但是 runner 还没调用,后面会说到runner机制
  • ready:
    • ioc容器刷新了,所有runner都调用了
  • 3.运行时机
  • 以前步骤都正确执行,代表容器running。
  • 4.失败时机
  • failed:
    • 在 starting 到 contextLoaded 都被包裹在一个 try中,只要其中一个地方出错,就会抛出异常,执行失败时机 failed

 

9大事件与探针

SpringBoot 中除了感知全阶段的 SpringApplicationRunListener 生命周期外,还会在每一个周期启动完成时,推送一共9个Event事件,在ioc刷新完成后,还会有2个探针来测定SpringBoot程序己确保无因 Running 过程卡死而使程序卡死的情况。

包含事件的生命周期流程图:

 

9大事件如下:

  • 引导时机
    • BootstrapRegistryInitializer: 感知特定阶段,只在引导初始化中进行
    • 推送 ApplicationStartingEvent 事件
    • 执行 starting 周期
    • 推送 ApplicationEnvironmentPreparedEvent: 处于环境准备好阶段,未创建ioc
    • 执行 environmentPrepared 周期
  • 启动时机
    • 推送 ApplicationContextInitializedEvent 事件: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
    • 执行 contextPrepared 周期
    • 推送 ApplicationPreparedEvent 事件:容器刷新之前,bean定义信息加载
    • 执行 contextLoaded 周期
    • 推送 ApplicationStartedEvent 事件:容器刷新完成, Runner未调用
    • 执行 started 周期
    • 运行探针 AvailabilityChangeEvent 事件:LivenessState.CORRECT 应用存活;存活探针
    • 推送 ApplicationReadyEvent 事件
    • 运行 Runner 调用【ApplicationRunner】
      • 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
    • 运行 Runner 调用【CommandLineRunner】
      • 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
    • 运行探针 AvailabilityChangeEvent 事件:证明所有Runner 都能被正确调用不卡死,程序可以接受请求
    • 执行 ready 周期
  • 失败时机
    • 推送 ApplicationFailedEvent 事件
    • 执行 failed 周期

 

应用事件发送顺序图如下:

 

基于事件驱动开发

事件是一个非常好的东西,它可以解决很多代码解耦上的问题,比如下面的例子,非常适合使用事件开发

  • 当用户请求登陆,并登陆成功时
    • 1.AccountService 登记登陆记录
    • 2.CouponService 给用户随件赠送优惠
    • 3.自动给用户签到
    • 4.....等等

从上面的需求来看,如果按照以往的方式,我们需要在Controller中注入对应的service对象,并依次调用相应方法。

如果在以后,有其它需求添加进来,那么就需要在Controller中修改原有的代码,并且Controller和各个Service有比较大的耦合性

使用事件驱动,Controller 只需要向系统中提供一个事件,所有需要的Service可以对这个事件进行订阅,收到事件时即可各自处理对应的业务

具体概念如下图:

 

前置知识

SpringBoot 内部也使用事件驱动,

  • 提供 ApplicationEventPublisherAware 作为获得 【发布者】的对象,用于发布事件
  • 提供 ApplicationEvent 类,用于定义发布事件的父类型,所有事件都需要创建 ApplicationEvent 类的子类才能发出
  • 提供 ApplicationListener<E> 接口给订阅者对象接收事件,E为关心并接收的哪个发布者的事件,取决于发布事件的 ApplicationEvent 子类
  • 提供 @EventListener 注解来快速注解方法为订阅者接收事件
  • 订阅者事件执行顺序由方法名的字母顺序来执行,如果想优先,可以使用 @Order() 注解来提前优先

 

1.创建事件发布者

  • 1.工具类与事件发布对象的获取
    • 我们可以定义一个类,用于作为发布事件,如 EventPublisher,它要获得系统的【发布者】对象,需要实现 ApplicationEventPublisherAware
    • 实现方法 setApplicationEventPublisher 来获得发布对象,并长期持有。因为 setApplicationEventPublisher 在运行时就只执行一次了
  • 2.订阅者注册监听功能
    • 订阅者需要实现接口 ApplicationListener 或 在需要接收执行的方法中加入 @EventListener 注解
    • ApplicationListener<E> 接口提供一个泛型,意指只关心属于那个发布者发布的事件,我们可以在后面定义不同的发布者
  • 3.定义一个包装事件类
    • 我们不能直接把要发送的数据,用发布者发出去,我们需要把数据进一步包装成 ApplicationEvent 类,所以我们要先创建一个类继承 ApplicationEvent,用于包装 ApplicationEvent 类型数据
    • 建议另外创建一个 ApplicationEvent 子类,因为如果真接使用 ApplicationEvent 来创建的话,这个事件可能让所有订阅者都会收到。
  • 4.构造事件并发布
    • 创建一个事件包装类
    • 使用工具类把这个事件包装类发布出去
    • 通过2的操作,订阅者会收到事件并处理相应操作

具体代码如下:

1.工具类与事件发布对象的获取

@Service
public class EventPublisher implements ApplicationEventPublisherAware {


    /**
     * 底层发送事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口
     * 自动注入给我们
     * 事件是广播出去的。所有监听这个事件的监听器都可以收到
     */
    ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }


    /**
     * 定义一个发送事件的方法,用于其它方法调用发送事件
     * @param event
     */
    public void sendEvent(ApplicationEvent event){

        // 调用事件发送
        this.applicationEventPublisher.publishEvent(event);
    }
}

 

2.订阅者注册监听功能

/**
 * 用于处理 Account 的登陆成功的事件的订阅者
 */
@Service
public class AccountServiceListener implements ApplicationListener<AccountLoginSuccessEvent> {
    /**
     * 接收事件的接口实现
     * @param event
     */
    @Override
    public void onApplicationEvent(AccountLoginSuccessEvent event) {
        String source = (String) event.getSource();
        doEvent(source);
    }

    // 进行逻辑处理
    public void doEvent(String data) {
        System.out.println("事件接收到数据:" + data);
    }
}

 

3.定义一个包装事件类

public class AccountLoginSuccessEvent extends ApplicationEvent {
    // 创建一个事件包装类,source 是我们传递给订阅者的数据,包装到 ApplicationEvent 子类中,以备订阅者获得
    public AccountLoginSuccessEvent(String source) {
        super(source);
    }
}

 

4.构造事件并发布

    @Resource
    private EventPublisher eventPublisher;

    @GetMapping("/event")
    public String event(@RequestParam("name") String name){
        // 创建一个用于包装特定事件的类,用于发布,以这个子类发布的事件,只有接收这个子类的订阅者才会收到
        AccountLoginSuccessEvent event = new AccountLoginSuccessEvent(name);
       eventPublisher.sendEvent(event);
        return "ok";
    }

 

自定义 Starter 包并设计自动配置类

通过官方的Starter的自动配置方式,我们可以知道以下前置知识:

  • 1.创建 xxxAutoConfiguration.class 类,并在这个类中对包中的初始化启动对象进行创建,程序使用 @Import() 注解引入 xxxAutoConfiguration.class
    • 也可以在 AutoConfiguration.class 类中加入 @Import() 注解来直接顺带其它类进来
  • 2.使用 @AutoConfiguration 注解 + META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中加入自己的AutoConfiguration.class 类,来让 SpringBoot 启动时自动通过 SPI 方式加载
  • 3.使用自建 @EnableXXX 这样的注解,并在注解中加入 @Import() 注解来引入自己的 AutoConfiguration.class 类,并在程序包中使用 @EnableXXX 注解来带入自定义starter包的AutoConfiguration.class类
  • AutoConfiguration.class 类中可定义 @Import() 注解引入 ConfigProperties 的配置文件属性类,在 ConfigProperties 的配置文件属性类中使用 @ConfigurationProperties(prefix = "") 注解来引入配置文件中的配置信息。
    • 也可以使用 @Value() 注解来注入

通过以上的方式,我们有三种配置

  • 1.让程序包使用 @Import(xxxAutoConfiguration.class) 来引入
  • 2.让程序包使用 @EnableXXX 注解来引入
  • 3.使用SPI,程序包不需要做任何处理,自动引入

 

自定义starter 不需要使用 properties 配置文件,和 Main 入口文件,因为程序包不会使用 starter 包中的 properties 配置,也不会用作程序启动使用。

 

关于 Properties、yaml 配置文件不提示的问题

官方提示,需要引入以下包,就可以解决 配置文件不提示的问题,主要是自定义的属性值不提示。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

 

 

完整的生命周期启动流程

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
SpringBoot 核心原理
生命周期 创建生命周期调用方法类 通过源码可知,SpringBoot 在调用生命周期时,是通过查找包的“META-INF\spring.factories”文件中找到对应的需要加载的Listener类 其中SpringBoot的Listener……
<<上一篇
下一篇>>