Java SpringBoot

1、前言

在使用 SpringBoot搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求,针对这种需求 SpringBoot(Spring) 提供了以下几种方案:

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

    2、ApplicationRunnerCommandLineRunner

    如果需要在SpringApplication启动时执行一些特殊的代码,可以实现ApplicationRunnerCommandLineRunner 接口,这两个接口工作方式相同,都只提供单一的 run() 方法,而且该方法仅在SpringApplication.run(…)完成之前调用,更准确的说是在构造 SpringApplication 实例完成之后调用 run() 的时候,具体分析见后文,所以这里将他们分为一类。

    ApplicationRunner

    构造一个类实现ApplicationRunner接口

    1. @Component
    2. public class ApplicationRunnerTest implements ApplicationRunner {
    3. @Override
    4. public void run(ApplicationArguments args) throws Exception {
    5. System.out.println("ApplicationRunner");
    6. }
    7. }

    CommandLineRunner

    对于这两个接口而言,可以通过 Order 注解或者使用 Ordered 接口来指定调用顺序,@Order() 中的值越小,优先级越高

    1. @Component
    2. @Order(1)
    3. public class CommandLineRunnerTest implements CommandLineRunner {
    4. @Override
    5. public void run(String... args) throws Exception {
    6. System.out.println("CommandLineRunner...");
    7. }
    8. }

    当然也可以同时使用 ApplicationRunnerCommandLineRunner,默认情况下前者比后者先执行,但是这没有必要,使用一个就可以了。

    两者的联系与区别

    这两个接口都有 run() 方法,只不过它们的参数不一样,CommandLineRunner的参数是最原始的参数,没有进行任何处理,ApplicationRunner 的参数是ApplicationArguments,是对原始参数的进一步封装。
    简要跟踪一下源码看 ApplicationRunner(CommandLineRunner) 是如何被调用的。
    SpringBoot在启动的时候,都会构造一个 SpringApplication 实例,至于这个实例怎么构造的,这里不去探究了,有感兴趣的可以去看下源码。这里主要看ApplicationRunner 是如何被调用的,而它的调用就是在SpringApplication这个实例调用run方法中。

    1. @SpringBootApplication
    2. public class Application {
    3. public static void main(String[] args) {
    4. SpringApplication.run(Application.class, args);
    5. }
    6. }

    进入run方法

    1. public static ConfigurableApplicationContext run(Class<?> primarySource,
    2. String... args) {
    3. return run(new Class<?>[] { primarySource }, args);
    4. }

    执行 SpringApplicationrun 方法

    1. public static ConfigurableApplicationContext run(Class<?>[] primarySources,
    2. String[] args) {
    3. return new SpringApplication(primarySources).run(args);
    4. }

    一路点击 run() 来,发现对 ApplicationRunner 的调用实际上在 callRunners 方法中
    对于 CommandLineRunner 或者 ApplicationRunner 来说,需要注意的两点:

  • 所有 CommandLineRunner / ApplicationRunner 的执行时点是在 SpringBoot 应用的 ApplicationContext 完全初始化开始工作之后,callRunners() 可以看出是 run 方法内部最后一个调用的方法(可以认为是main方法执行完成之前最后一步)

  • 只要存在于当前 SpringBoot 应用的 ApplicationContext 中的任何CommandLineRunner / ApplicationRunner,都会被加载执行(不管是手动注册还是自动扫描去Ioc容器)

    3、Spring Bean初始化的InitializingBean,init-method和PostConstruct

    InitializingBean接口

    InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet()方法。
    在 spring 初始化 bean 的时候,如果bean实现了 InitializingBean 接口,在对象的所有属性被初始化后之后才会调用 afterPropertiesSet() 方法

    1. @Component
    2. public class InitialingzingBeanTest implements InitializingBean {
    3. @Override
    4. public void afterPropertiesSet() throws Exception {
    5. System.out.println("InitializingBean..");
    6. }
    7. }

    当然,可以看出Spring初始化bean肯定会在 ApplicationRunnerCommandLineRunner 接口调用之前。
    当然有一点要注意的是,尽管使用 InitialingBean 接口可以实现初始化动作,但是官方并不建议使用 InitializingBean 接口,因为它将代码耦合在Spring代码中,官方的建议是在 bean 的配置文件指定 init-method 方法,或者在 @Bean 中设置 init-method 属性

    init-method和@PostConstruct

    前面就说过官方文档上不建议使用 InitializingBean 接口,但是可以在 <bean> 元素的 init-method 属性指定 bean 初始化之后的操作方法,或者在指定方法上加上 @PostConstruct 注解来制定该方法在初始化之后调用

    1. @SpringBootApplication
    2. public class Application {
    3. public static void main(String[] args) {
    4. SpringApplication.run(Application.class, args);
    5. }
    6. @PostConstruct
    7. public void init() {
    8. System.out.println("init...");
    9. }
    10. }

    更多关于Spring Bean的生命周期的内容,请参阅Spring相关书籍或博客Spring Bean的生命周期

    4、Spring的事件机制

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

  • 事件: ApplicationEvent,继承自JDK的EventObject,所有事件都要继承它,也就是被观察者

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

事件
在Spring框架中,默认对ApplicationEvent事件提供了如下支持:

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

    构造一个类继承ApplicationEvent

    ```java public class TestEvent extends ApplicationEvent {

    private String message;

    /**

    • Create a new ApplicationEvent.
    • @param source the object on which the event initially occurred (never {@code null}) */ public TestEvent(Object source) { super(source); }

      public void getMessage() { System.out.println(message); }

      public void setMessage(String message) { this.message = message; }

}

  1. <a name="e2j86"></a>
  2. #### 创建事件监听者
  3. 有两种方法可以创建监听者,一种是直接实现`ApplicationListener`的接口,一种是使用注解 `@EventListener`,注解是添加在监听方法上的,下面的例子是直接实现的接口
  4. ```java
  5. @Component
  6. public class ApplicationListenerTest implements ApplicationListener<TestEvent> {
  7. @Override
  8. public void onApplicationEvent(TestEvent testEvent){
  9. testEvent.getMessage();
  10. }
  11. }

事件发布

对于事件发布,代表者是ApplicationEventPublisherApplicationEventMulticaster,
ApplicationContext接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码,实际执行是委托给ApplicationEventMulticaster(可以认为是多播)
下面是一个事件发布者的测试实例:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class EventTest {
  4. @Autowired
  5. private ApplicationContext applicationContext;
  6. @Test
  7. public void publishTest() {
  8. TestEvent testEvent = new TestEvent("");
  9. testEvent.setMessage("hello world");
  10. applicationContext.publishEvent(testEvent);
  11. }
  12. }
  1. //output:
  2. hello world

利用ContextRefreshedEvent事件进行初始化操作

前面做了这么多铺垫,下面进入今天的主题,利用Spring的事件机制进行初始化一些操作,实际上就是前面提到了,利用ContextRefreshedEvent事件进行初始化,该事件是ApplicationContext初始化完成后调用的事件,所以可以利用这个事件,对应实现一个监听器,在其onApplicationEvent()方法里初始化操作

  1. @Component
  2. public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {
  3. @Override
  4. public void onApplicationEvent(ContextRefreshedEvent event) {
  5. System.out.println("我被调用了..");
  6. }
  7. }

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

小结

以上简要总结了在SpringBoot启动时进行初始化操作的几个方案,这几种方式都可以满足需求,针对具体场景使用对应的方案。但是,CommandLineRunner或者ApplicationRunner不是Spring框架原有的东西,它俩属于SpringBoot应用特定的回调扩展接口,所以很容易进行扩展,在一些微服务应用中使用也较广泛。