事件监听机制我理解的应该是类似于一种观察者模式。利用事件的监听机制可以实现业务的发起与业务的处理解耦。说白了更能体现 单依职责原则 想象一下如果一个用户触发了某个动作需要发送邮件, 那么发出动作的一方一定是在 用户相关的类, 发送邮件的动作一定是在邮件相关的类, 这种情况下用户类就需要依赖邮件类, 形成耦合. 如果用事件来解决这个问题, 用户只需要发起一个相关的事件, 至于谁来处理这个事件, 用户类不需要关心, 而邮件类只需要监听这个事件, 至于谁发起的这个事件, 邮件类也不需要关心. 单依职责原则, 最少知识原则
基于 Spring 实现事件发布与监听需要三步:
- 定义事件
- 发布事件
- 监听事件
定义事件
- 继承
ApplicationEvent
定义事件类
新建一个类,随便起个名字,最好是 xxxEvent
这种命名。然后继承 ApplicationEvent
这个类即可
public class CrewEvent extends ApplicationEvent {
@Getter
private final String name;
/**
* 创建一个新的 {@code ApplicationEvent}.
*
* @param source 事件源
* @param name 自定义的属性,根据自己的业务需要添加
*/
public CrewEvent(Object source, String name) {
super(source);
this.name = name;
}
}
- 在
Spring 4.2.x
及以后的版本可以不用继承ApplicationEvent
类, 任何一个普通的Java Bean
都可以当做 事件类
public class CrewEvent {
@Getter
private final String name;
@Getter
private final Object source;
/**
* 创建一个新的 {@code ApplicationEvent}.
*
* @param source 事件源
* @param name 自定义的属性,根据自己的业务需要添加
*/
public CrewEvent(Object source, String name) {
this.name = name;
this.source = source;
}
}
发布事件
发布事件只需要调用 ApplicationEventPublisher
的 publishEvent
方法即可
@RestController
@AllArgsConstructor
@Slf4j
public class ReportController {
private final ApplicationEventPublisher applicationEventPublisher;
@GetMapping("/report")
public String getView() {
applicationEventPublisher.publishEvent(new CrewEvent(this, "report"));
return "事件已经触发";
}
}
事件监听
- 基于注解监听
Spring 提供了基于注解的事件监听方法只需要在 Spring Bean
的方法上加上 @EventListener
即可.
EventListener
可以传入参数来判断被修饰的方法监听哪个事件类
还可以添加 @Async
注解, 使其可以被异步的执行
@Component
@Slf4j
public class CrewEventListener {
@Async
@EventListener(CrewEvent.class)
public void crewEventHandle(CrewEvent event) {
log.info("event.name: {}", event.getName());
log.info("event.source: {}", event.getSource());
}
}
- 基于接口监听
基于接口的监听需要实现ApplicationListener
接口在泛型参数中填入要监听的事件类, 并实现 onApplicationEvent
方法即可
接口泛型中的时间类会被当做参数传递给 onApplicationEvent
方法
@Component
@Slf4j
public class CrewEventListener implements ApplicationListener<CrewEvent> {
@Override
public void onApplicationEvent(CrewEvent event) {
log.info("event.name: {}", event.getName());
log.info("event.source: {}", event.getSource());
System.out.println(event.getTimestamp());
}
}
Spring Boot 的启动事件
Spring Boot 在启动的过程中也会发布一系列的事件, 例如: 在应用启动且未作任何处理时会发布ApplicationStartingEvent
事件, 在确定Spring Boot
应用使用的 Environment 且 context 创建之前发送ApplicationEnvironmentPreparedEvent
事件. 对这些事件进行监听可以实现一些特殊的效果, 比如在程序启动时做一些启动一些工作线程, 加载一些系统参数等.
下面进行详细说明
Spring Boot 启动会触发哪些事件
ApplicationStartingEvent :应用启动且未作任何处理(除listener注册和初始化)
ApplicationEnvironmentPreparedEvent: 确定springboot应用使用的Environment且context创建之前
ApplicationPreparedEvent:context已经创建且没有refresh发送个事件
ApplicationStartedEvent: context已经refresh且application and command-line runners(如果有)调用之前发送这个事件
ApplicationReadyEvent: pplication and command-line runners (如果有)执行完后发送这个事件,此时应用已经启动完毕
ApplicationFailedEvent: 应用启动失败后产生这个事件
监听Spring Boot 启动事件
public class CrewSystemEventListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
//springboot应用启动且未作任何处理(除listener注册和初始化)的时候发送ApplicationStartingEvent
if (event instanceof ApplicationStartingEvent) {
System.out.println("ApplicationStarting");
}
//确定springboot应用使用的Environment且context创建之前发送这个事件
if (event instanceof ApplicationEnvironmentPreparedEvent) {
System.out.println("ApplicationEnvironmentPrepared");
}
//context已经创建且没有refresh发送个事件
if (event instanceof ApplicationPreparedEvent) {
System.out.println("ApplicationPrepared");
}
//context已经refresh且application and command-line runners(如果有) 调用之前发送这个事件
if (event instanceof ApplicationStartedEvent) {
System.out.println("ApplicationStarted");
}
//application and command-line runners (如果有)执行完后发送这个事件,此时应用已经启动完毕
if (event instanceof ApplicationReadyEvent) {
// 此时,Spring 容器构建完成了,已经可以从中拿到 Bean 了
ApplicationContext context = ((ApplicationReadyEvent) event).getApplicationContext();
SystemInitManager initService = context.getBean(SystemInitManager.class);
initService.doSystemInit();
}
//应用启动失败后产生这个事件
if (event instanceof ApplicationFailedEvent) {
System.out.println("ApplicationFailed");
}
}
}
由于我们要监听 Spring Boot 的启动过程, 那就需要在 Spring Boot
程序刚启动时就执行, 那么就两种方式了
- main函数
@SpringBootApplication
public class CrewWarApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(CrewWarApplication.class)
.listeners(new CrewSystemEventListener())
.run(args);
}
}
- spring.factories
在resources 下新建 META-INF 文件夹,在里面新建 spring.factories
文件, 在里面写入下面的代码段
众所周知, Spring Boot 程序一旦启动最先扫描的就是这玩意儿.
org.springframework.context.ApplicationListener=\
com.captain.springboot.war.event.CrewSystemEventListener
ps. 上面的示例代码为了验证在最后阶段 Spring 容器中的内容,用到了 SystemInitManager
这玩意儿是我自己写的,在线面贴出来
public interface SystemInitManager {
/**
* 系统初始化
*/
void doSystemInit();
}
@Component
@Slf4j
public class CrewSystemInitManager implements SystemInitManager {
@Override
public void doSystemInit() {
log.info("系统初始化完成,你现在可以从 springContext 中获取 Bean");
}
}