文章目录
- 前言
- Java 监听机制
- SpringBoot 监听机制
- 演示
- 创建接口实现类
- 总结
前言
将一个监听器 (listener) 与特定的控件(如按钮等)绑定起来,当发生用户点击等事件 (Event) 时,调用监听器的处理方法,从而响应用户的动作,就叫做事件/监听器模式。
Java 监听机制
SpringBoot 的监听机制,其实是对 Java 提供的时间监听机制的封装。
Java 中的时间监听机制定义了以下几个角色:
- 事件:Event,继承 java.util.EventObject 类的对象。
- 事件源:Source,任意对象 Object。
- 监听器:Listener,实现 java.util.EventListener 接口的对象。
SpringBoot 监听机制
SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。
监听器接口:
- ApplicationContextInitializer
- SpringApplicationRunListener
- CommandLineRunner
- ApplicationRunner
演示
创建一个 springboot-listener 模块:
创建接口实现类
我们先分别创建上面四个接口的实现类:
- 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");
}
}
- 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...项目启动失败");
}
}
- 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");
}
}
- 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 的监听机制。