前言

将一个监听器 (listener) 与特定的控件(如按钮等)绑定起来,当发生用户点击等事件 (Event) 时,调用监听器的处理方法,从而响应用户的动作,就叫做事件/监听器模式。


Java 监听机制

SpringBoot 的监听机制,其实是对 Java 提供的时间监听机制的封装。
Java 中的时间监听机制定义了以下几个角色:

  • 事件:Event,继承 java.util.EventObject 类的对象。
  • 事件源:Source,任意对象 Object。
  • 监听器:Listener,实现 java.util.EventListener 接口的对象。

SpringBoot 监听机制

SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。
监听器接口:

  • ApplicationContextInitializer
  • SpringApplicationRunListener
  • CommandLineRunner
  • ApplicationRunner

演示

创建一个 springboot-listener 模块:
在这里插入图片描述

创建接口实现类

我们先分别创建上面四个接口的实现类:

  1. ApplicationContextInitializer
    找到 ApplicationContextInitializer 接口:
    在这里插入图片描述
    可以看到接口中有只有一个 initialize 方法:
    在这里插入图片描述
    下面我们写一下这个接口的实现类:
    创建 listener.MyApplicationContextInitializer,实现 ApplicationContextInitializer 接口并复写接口中的方法:

    package com.xh.springbootlistener.listener;
    
    import org.springframework.context.ApplicationContextInitializer;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            System.out.println("ApplicationContextInitializer...initialize");
        }
    }
    
  2. SpringApplicationRunListener
    可以看到 SpringApplicationRunListener 接口中定义了很多方法,根据方法名可以判断出这些是与生命周期相关的。
    在这里插入图片描述
    创建 listener.MySpringApplicationRunListener,实现 SpringApplicationRunListener 接口并复写接口中的方法:

    package com.xh.springbootlistener.listener;
    
    import org.springframework.boot.ConfigurableBootstrapContext;
    import org.springframework.boot.SpringApplicationRunListener;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MySpringApplicationRunListener implements SpringApplicationRunListener {
    
        @Override
        public void starting(ConfigurableBootstrapContext bootstrapContext) {
            System.out.println("starting...项目启动中");
        }
    
        @Override
        public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
            System.out.println("environmentPrepared...环境对象开始准备");
        }
    
        @Override
        public void contextPrepared(ConfigurableApplicationContext context) {
            System.out.println("contextPrepared...上下文对象开始准备");
    
        }
    
        @Override
        public void contextLoaded(ConfigurableApplicationContext context) {
            System.out.println("contextLoaded...上下文对象开始加载");
        }
    
        @Override
        public void started(ConfigurableApplicationContext context) {
            System.out.println("started...上下文对象加载完成");
        }
    
        @Override
        public void running(ConfigurableApplicationContext context) {
            System.out.println("running...项目启动完成,开始运行");
        }
    
        @Override
        public void failed(ConfigurableApplicationContext context, Throwable exception) {
            System.out.println("failed...项目启动失败");
        }
    }
    
    
  3. CommandLineRunner
    CommandLineRunner 接口中定义了一个参数为 String… args 的 run 方法。
    在这里插入图片描述
    创建 listener.MyCommandLineRunner,实现 CommandLineRunner 接口并复写接口中的方法:

    package com.xh.springbootlistener.listener;
    
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyCommandLineRunner implements CommandLineRunner {
    
        @Override
        public void run(String... args) throws Exception {
            System.out.println("CommandLineRunner...run");
        }
    }
    
    
  4. ApplicationRunner
    ApplicationRunner 接口中定义了一个参数为 ApplicationArguments args 的 run 方法
    在这里插入图片描述
    创建 listener.MyApplicationRunner,实现 ApplicationRunner 接口并复写接口中的方法:

package com.xh.springbootlistener.listener;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner...run");
    }
}

实现类创建完成后,我们启动项目,可以看到,只有 CommandLineRunner 和 ApplicationRunner 的输出执行了,而 ApplicationContextInitializer 和 SpringApplicationRunListener 没有被执行:
在这里插入图片描述
我们先说被执行的两个方法,他们都是 run 方法,只是参数不一样,通过上面启动项目的测试我们可以判断出他们是在项目启动时被自动调用,执行 run 方法,那么我们就可以在这里做一些事情,比如为了防止前期用户访问时没有数据,我们期望 Redis 在项目启动时能够把数据库的一些信息提前加载进来作为缓存,就可以把代码放在这里执行,也就是缓存预热。
下面我们可以修改实现类的代码打印一下这两个方法的参数:

	@Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...run");
        System.out.println(Arrays.asList(args));
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner...run");
        System.out.println(Arrays.asList(args.getSourceArgs()));
    }

输出打印,可以看到现在数组中是没有值的:
在这里插入图片描述
这个 args 其实就是我们传递的一些参数,我们可以去配置一下:
在这里插入图片描述
再次启动,成功输出我们传的参数,这两个方法其实基本都一样,都会在启动时调用,我们实际应用中可以根据具体业务选择不同的方式。
在这里插入图片描述
而 ApplicationContextInitializer 和 SpringApplicationRunListener 想要被执行,需要我们进行配置。
创建 META-INF/spring.factories,这个文件在 SpringBoot 启动时会自动被扫描到,它是一种键值对的方式。
配置的方式也很简单,key 为接口的全路径名,value 为实现类的限定名:
先配置 ApplicationContextInitializer:

org.springframework.context.ApplicationContextInitializer=com.xh.springbootlistener.listener.MyApplicationContextInitializer

启动项目,可以看到 initialize 输出了,那么他输出的位置在图标之后,项目准备 IOC 容器之前,我们可以在后期使用中去检测项目的一些资源是否存在。
在这里插入图片描述
配置 SpringApplicationRunListener:

org.springframework.boot.SpringApplicationRunListener=com.xh.springbootlistener.listener.MySpringApplicationRunListener

启动项目,可以看到报错了,提示一个非法的参数异常,从 Caused by 中可以看到他说没有一个匹配的方法异常,在 MySpringApplicationRunListener 中没有一个需要两个参数的构造方法(参数1 org.springframework.boot.SpringApplication, 参数2 [Ljava.lang.String;):
在这里插入图片描述
我们可以去接口中看一下他的实现类:
在这里插入图片描述
可以看到在实现类中定义了一个构造方法:
方法中有两个参数:
application 就是我们项目启动时的那个事件源,将来在项目启动时会在这个事件源上产生很多的生命周期相关的事件。
args 就是一些接收的参数。
在这里插入图片描述
那么既然他提示我们需要这么一个构造方法,我们可以给他提供一个,修改 MySpringApplicationRunListener:

package com.xh.springbootlistener.listener;

import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    public MySpringApplicationRunListener(SpringApplication application, String[] args) {}

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("starting...项目启动中");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("environmentPrepared...环境对象开始准备");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("contextPrepared...上下文对象开始准备");

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("contextLoaded...上下文对象开始加载");
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("started...上下文对象加载完成");
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("running...项目启动完成,开始运行");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("failed...项目启动失败");
    }
}

修改完之后我们再次启动项目,可以看到我们的打印正常输出了,在后期开发过程中我们就可以根据具体需求在不同的时机完成不同的需求:
在这里插入图片描述
其实在 SpringBoot 内部定义了很多生命周期相关的事件,感兴趣的朋友可以去 org.springframework.boot.context.event 包中去看一下:
在这里插入图片描述


总结

本章简单介绍并实现了 SpringBoot 的监听机制。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐