springboot启动时资源初始化

69次阅读

共计 3596 个字符,预计需要花费 9 分钟才能阅读完成。

摘要

在实际项目中,我们需要在 springboot 服务启动后做一些初始化工作,例如线程池初始化、文件资源加载、常驻后台任务启动(比如 kafka consumer)等。本文介绍 3 类初始化资源的方法:

  • Spring Bean 初始化的 InitializingBean,init-method 和 PostConstruct
  • ApplicationRunnerCommandLineRunner 接口
  • Spring 的事件机制

方法 1:spring bean 初始化

Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

  • 通过实现 InitializingBean 接口来定制初始化之后的操作方法;
  • 通过 <bean> 元素的 init-method 属性指定初始化之后调用的操作方法;
  • 在指定方法上加上 @PostConstruct 注解来指定该在初始化之后调用的方法

执行的先后顺序:构造方法 –> @PostConstruct注解的方法 –> afterPropertiesSet方法(InitializingBean接口)–> init-method指定的方法。详情参考 Spring Bean 初始化之 InitializingBean, init-method 和 PostConstruct

方法 2:ApplicationRunnerCommandLineRunner 接口

CommandLineRunner/ApplicationRunner 接口的 Component 会在所有 Spring Beans都初始化之后,SpringApplication.run()之前执行,非常适合在应用程序启动之初进行一些数据初始化的工作。CommandLineRunnerApplicationRunner 这两个接口工作方式相同,都只提供单一的 run 方法,唯一的区别是 run 方法的入参类型不同,CommandLineRunner的参数是最原始的参数,没有进行任何处理,ApplicationRunner的参数是ApplicationArguments, 是对原始参数的进一步封装。

runner 定义

以下定义了两个 runner,其中 Order 注解指定了执行顺序,数字越小越先执行

@Order(1)
@Component
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) {log.info("MyCommandLineRunner run...");
    }
}
-------------------------------------------------------------------
@Order(2)
@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {log.info("MyApplicationRunner run...");
    }
}

源码跟踪

跟踪源码,看下 CommandLineRunner/ApplicationRunner 是如何被调用的,Springboot 在启动的时候,都会构造一个 SpringApplication 实例,执行 run 方法,一路点进去后不难发现调用入口是 SpringApplication.run 方法中的callRunners(context, applicationArguments)

public ConfigurableApplicationContext run(String... args) {
        ......
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {handleRunFailure(context, listeners, exceptionReporters, ex);
            throw new IllegalStateException(ex);
        }
        listeners.running(context);
        return context;
    }

总结

  • 所有 CommandLineRunner/ApplicationRunner 的执行时间点是在 SpringBoot 应用的 ApplicationContext 完全初始化开始工作之后,callRunners()可以看出是 run 方法内部最后一个调用的方法(可以认为是 main 方法执行完成之前最后一步)
  • 只要存在于当前 SpringBoot 应用的 ApplicationContext 中的任何 CommandLineRunner/ApplicationRunner,都会被加载执行(不管你是手动注册还是自动扫描去 Ioc 容器)

方法 3:spring 事件机制

Spring 的事件机制实际上是设计模式中观察者模式的典型应用。观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态改变时,需要通知相应的观察者,使这些观察者能够自动更新。

Spring 的事件驱动模型由三部分组成

  • 事件:ApplicationEvent,继承自 JDK 的EventObject,所有事件都要继承它,也就是被观察者
  • 事件发布者: ApplicationEventPublisherApplicationEventMulticaster 接口,使用这个接口,就可以发布事件了
  • 事件监听者::ApplicationListener,继承 JDK 的EventListener,所有监听者都继承它,也就是我们所说的观察者,当然我们也可以使用注解 @EventListener,效果是一样的

built-in 事件

在 Spring 框架中,内置了 4 种事件:

  • ContextStartedEvent:ApplicationContext 启动后触发的事件
  • ContextStoppedEvent:ApplicationContext 停止后触发的事件
  • ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;(容器初始化完成后调用,所以我们可以利用这个事件做一些初始化操作)
  • ContextClosedEvent:ApplicationContext 关闭后触发的事件;(如 web 容器关闭时自动会触发 spring 容器的关闭,如果是普通 java 应用,需要调用 ctx.registerShutdownHook(); 注册虚拟机关闭时的钩子才行)

例子

ContextRefreshedEvent 为例,创建一个 listener 非常简单

@Component
@Slf4j
public class MyContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {log.info("ContextRefreshedEvent listen...");
    }
}

MyContextRefreshListener监听了内置事件 ContextRefreshedEvent,即容器初始化完成后调用,MyContextRefreshListener.onApplicationEvent 会被调用,利用此特性可以做一些初始化工作

注意: 在传统的基于 XML 配置的 Spring 项目中会存在二次调用的问题,即调用两次该方法,原因是在传统的 Spring MVC 项目中,系统存在两个容器,一个 root 容器,一个 project-servlet.xml 对应的子容器,在初始化这两个容器的时候都会调用该方法一次,所以有二次调用的问题,而对于基于 Springboot 的项目不存在这个问题

三种方式的执行顺序

方法 1(spring bean 初始化)–> spring 事件ContextRefreshedEvent–> CommandLineRunner/ApplicationRunner,示例代码下载请戳代码下载地址

参考文档

  • Spring Bean 初始化之 InitializingBean, init-method 和 PostConstruct
  • SpringBoot 中资源初始化加载的几种方式(看这一片就够了)
  • springboot 系列文章之启动时初始化数据
  • SpringBoot 启动初始化资源

正文完
 0