Spring Event(第一篇)
一、为什么需要面向事件编程
面向事件编程:所有模块或者对象都是通过事件来进行交互,区别于传统编程不同模块直接相互调用,模块间不需要进行强依赖。
传统编程模式如下:
面向事件编程模式如下:
面向事件编程的优势:
- 系统模块之间耦合度变低,各模块的变更不会产生相互影响。
- 事件比方法更贴近业务,更加形而上。
劣势:
- 系统的复杂性会有所增强。
- 新手理解成本会有所增加。
二、Spring Event来源于Java事件
Java事件源于监听器编程模型。标准化接口如下:
- 事件对象 - java.util.EventObject
- 事件监听器 - java.util.EventListener
事件的设计模式:观察者模式扩展:
- 消息发送者 - java.util.Observable
- 观察者 - java.util.Observer
import java.util.EventListener;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;
public class ObserverDemo {
public static void main(String[] args) {
EventObservable observable = new EventObservable();
// 添加观察者(监听者)
observable.addObserver(new EventObserver());
// 发布消息(事件)
observable.notifyObservers("Hello,World");
}
static class EventObservable extends Observable {
public void setChanged() {
super.setChanged();
}
public void notifyObservers(Object arg) {
setChanged();
super.notifyObservers(new EventObject(arg));
clearChanged();
}
}
static class EventObserver implements Observer, EventListener {
@Override
public void update(Observable o, Object event) {
EventObject eventObject = (EventObject) event;
System.out.println("收到事件 :" + eventObject);
}
}
}
输出结果如下:
收到事件 :java.util.EventObject[source=Hello,World]
面向接口的事件-监听器设计模式
Java技术规范 | 事件接口 | 监听器接口 |
---|---|---|
JavaBeans | java.beans.PropertyChangeEvent | java.beans.PropertyChangeListener |
Java AWT | java.awt.event.MouseEvent | java.awt.event.MouseListener |
Java Swing | java.swing.event.MenuEvent | java.swing.event.MenuListener |
Java Preference | java.util.prefs.PreferenceChangeEvent | java.util.prefs.PreferenceChangeListener |
面向注解的事件-监听器设计模式
- Servlet 3.0+ : @javax.servlet.annotation.WebListener (监听器)
- JPA 1.0+ : @javax.persistence.PostPersist (事件)
- Java Common : @PostConstruct (事件)
三、Spring标准事件-ApplicationEvent
ApplicationEvent基于Java标准事件(java.util.EventObject)进行扩展,增加了事件发生时间戳。
package org.springframework.context;
import java.util.EventObject;
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp = System.currentTimeMillis();
public ApplicationEvent(Object source) {
super(source);
}
public final long getTimestamp() {
return this.timestamp;
}
}
Spring应用上下文基于Spring标准事件(ApplicationEvent)扩展 - ApplicationContextEvent
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext)this.getSource();
}
}
- Spring应用上下文ApplicationContext作为事件源
- 实现类:
- org.springframework.context.event.ContextClosedEvent
- org.springframework.context.event.ContextRefreshedEvent
- org.springframework.context.event.ContextStartedEvent
- org.springframework.context.event.ContextStoppedEvent
import org.springframework.context.ApplicationEvent;
public class MySpringEvent extends ApplicationEvent {
public MySpringEvent(String message) {
super(message);
}
@Override
public String getSource() {
return (String) super.getSource();
}
public String getMessage() {
return getSource();
}
}
public class MySpringEvent2 extends MySpringEvent {
public MySpringEvent2(String message) {
super(message);
}
@Override
public String getSource() {
return super.getSource();
}
@Override
public String getMessage() {
return getSource();
}
}
import org.springframework.context.ApplicationListener;
public class MySpringEventListener implements ApplicationListener<MySpringEvent> {
@Override
public void onApplicationEvent(MySpringEvent event) {
System.out.printf("[线程 : %s] 监听到事件 : %s\n", Thread.currentThread().getName(), event);
}
}
import org.springframework.context.support.GenericApplicationContext;
public class CustomizedSpringEventDemo {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 1.添加自定义 Spring 事件监听器
context.addApplicationListener(new MySpringEventListener());
context.addApplicationListener(event -> System.out.println("Event : " + event));
// 2.启动 Spring 应用上下文
context.refresh();
// 3. 发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("Hello,World"));
context.publishEvent(new MySpringEvent2("2020"));
// 4. 关闭 Spring 应用上下文
context.close();
}
}
输出结果如下:
Event : org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.GenericApplicationContext@254989ff, started on Sat Jan 14 15:00:24 CST 2023]
[线程 : main] 监听到事件 : org.geekbang.thinking.in.spring.event.MySpringEvent[source=Hello,World]
Event : org.geekbang.thinking.in.spring.event.MySpringEvent[source=Hello,World]
[线程 : main] 监听到事件 : org.geekbang.thinking.in.spring.event.MySpringEvent2[source=2020]
Event : org.geekbang.thinking.in.spring.event.MySpringEvent2[source=2020]
Event : org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.support.GenericApplicationContext@254989ff, started on Sat Jan 14 15:00:24 CST 2023]
四、Spring ApplicationListener
4.1、基于接口的Spring事件监听器
扩展Java标准事件监听器 java.util.EventListener
- 扩展接口:org.springframework.context.ApplicationListener
- 设计特点:单一类型事件处理
- 处理方法:onApplicationEvent(ApplicationEvent)
- 事件类型:org.springframework.context.ApplicationEvent
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
4.2、基于注解的Spring事件监听器
@org.springframework.context.event.EventListener 特点如下:
- 支持多ApplicationEvent类型,无需接口约束
- 在方法上支持注解
- 支持异步执行
- 支持泛型类型事件
- 支持顺序控制 @Order进行控制
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import java.util.concurrent.Executor;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
@EnableAsync // 激活 Spring 异步特性
public class AnnotatedAsyncEventHandlerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 1. 注册当前类作为 Configuration Class
context.register(AnnotatedAsyncEventHandlerDemo.class);
// 2.启动 Spring 应用上下文
context.refresh();
// 3. 发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("Hello,World"));
// 4. 关闭 Spring 应用上下文(ContextClosedEvent)
context.close();
}
@Async // 同步 -> 异步
@EventListener
public void onEvent(MySpringEvent event) {
System.out.printf("[线程 : %s] onEvent方法监听到事件 : %s\n", Thread.currentThread().getName(), event);
}
@Bean
public Executor taskExecutor() {
return newSingleThreadExecutor(new CustomizableThreadFactory("my-spring-event-thread-pool-a"));
}
}
输出结果如下:
[线程 : my-spring-event-thread-pool-a1] onEvent方法监听到事件 : org.geekbang.thinking.in.spring.event.MySpringEvent[source=Hello,World]
4.3、注册Spring ApplicationListener
主要分为如下两种注册方式:
- ApplicationListener 作为Spring Bean 注册
- 通过ConfigurableApplicationContext API注册
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
public class ApplicationListenerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 将引导类 ApplicationListenerDemo 作为 Configuration Class
context.register(ApplicationListenerDemo.class);
// 方法一:基于 Spring 接口:向 Spring 应用上下文注册事件
// a 方法:基于 ConfigurableApplicationContext API 实现
context.addApplicationListener(event -> println("ApplicationListener - 接收到 Spring 事件:" + event));
// b 方法:基于 ApplicationListener 注册为 Spring Bean
// 通过 Configuration Class 来注册
context.register(MyApplicationListener.class);
// 启动 Spring 应用上下文
context.refresh(); // ContextRefreshedEvent
// 关闭 Spring 应用上下文
context.close(); // ContextClosedEvent
}
static class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
println("MyApplicationListener - 接收到 Spring 事件:" + event);
}
}
private static void println(Object printable) {
System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
}
}
结果输出为:
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 15:21:54 CST 2023]
[线程:main] : MyApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 15:21:54 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 15:21:54 CST 2023]
五、Spring Event控制
5.1、Spring Event发布器
- 通过ApplicationEventPublisher 发布Spring事件,通过依赖注入获取ApplicationEventPublisher。
- 通过ApplicationEventMulticaster 发布Spring事件,通过依赖注入和依赖查找获取ApplicationEventMulticaster。
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ApplicationListenerDemo implements ApplicationEventPublisherAware {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 将引导类 ApplicationListenerDemo 作为 Configuration Class
context.register(ApplicationListenerDemo.class);
// 方法一:基于 Spring 接口:向 Spring 应用上下文注册事件
context.addApplicationListener(event -> println("ApplicationListener - 接收到 Spring 事件:" + event));
// 启动 Spring 应用上下文
context.refresh(); // ContextRefreshedEvent
// 关闭 Spring 应用上下文
context.close(); // ContextClosedEvent
}
/**
* 通过依赖注入获取ApplicationEventPublisher, 发布Spring事件
* @param applicationEventPublisher applicationEventPublisher
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
applicationEventPublisher.publishEvent(new ApplicationEvent("Hello,World") {
private static final long serialVersionUID = -5821621139228213756L;
});
// 发送 PayloadApplicationEvent
applicationEventPublisher.publishEvent("Hello,World");
}
private static void println(Object printable) {
System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
}
}
输出结果如下:
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.geekbang.thinking.in.spring.event.ApplicationListenerDemo$1[source=Hello,World]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.PayloadApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@439f5b3d, started on Sat Jan 14 15:39:41 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@439f5b3d, started on Sat Jan 14 15:39:41 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@439f5b3d, started on Sat Jan 14 15:39:41 CST 2023]
5.2、Spring Event传播
当Spring 应用出现多层次Spring 应用上下文(ApplicationContext)时,如Spring WebMVC、Spring Boot、Spring Cloud 场景下,从子ApplicationContext 发起Spring 事件可能会传递到其parent ApplicationContext 直到root的过程。
那如何避免Spring 层次性上下文事件的传播呢?
- 定位Spring 事件源(ApplicationContext), 需要对其进行过滤处理。
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
import java.util.LinkedHashSet;
import java.util.Set;
public class HierarchicalSpringEventPropagateDemo {
public static void main(String[] args) {
// 1. 创建 parent Spring 应用上下文
AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
parentContext.setId("parent-context");
// 注册 MyListener 到 parent Spring 应用上下文
parentContext.register(MyListener.class);
// 2. 创建 current Spring 应用上下文
AnnotationConfigApplicationContext currentContext = new AnnotationConfigApplicationContext();
currentContext.setId("current-context");
// 3. current -> parent
currentContext.setParent(parentContext);
// 注册 MyListener 到 current Spring 应用上下文
currentContext.register(MyListener.class);
// 4.启动 parent Spring 应用上下文
parentContext.refresh();
// 5.启动 current Spring 应用上下文
currentContext.refresh();
// 关闭所有 Spring 应用上下文
currentContext.close();
parentContext.close();
}
static class MyListener implements ApplicationListener<ApplicationContextEvent> {
/*
* 定位Spring事件源,防止其进行层次性传播,对其进行过滤处理
*/
private static final Set<ApplicationContextEvent> PROCESSED_EVENTS = new LinkedHashSet<>();
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (PROCESSED_EVENTS.add(event)) {
System.out.printf("监听到 Spring 应用上下文[ ID : %s ] 事件 :%s\n", event.getApplicationContext().getId(),
event.getClass().getSimpleName());
}
}
}
}
输出结果如下:
监听到 Spring 应用上下文[ ID : parent-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextClosedEvent
监听到 Spring 应用上下文[ ID : parent-context ] 事件 :ContextClosedEvent
5.3、Spring 内建 Event
Spring 内建派生事件主要有四种:
- ContextRefreshedEvent: Spring 应用上下文就绪事件
- ContextStartedEvent: Spring 应用上下文启动事件
- ContextStoppedEvent: Spring 应用上下文停止事件
- ContextClosedEvent: Spring 应用上下文关闭事件
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ApplicationListenerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 将引导类 ApplicationListenerDemo 作为 Configuration Class
context.register(ApplicationListenerDemo.class);
// 方法一:基于 Spring 接口:向 Spring 应用上下文注册事件
context.addApplicationListener(event -> println("ApplicationListener - 接收到 Spring 事件:" + event));
// 启动 Spring 应用上下文
context.refresh(); // ContextRefreshedEvent
// 启动 Spring 上下文
context.start(); // ContextStartedEvent
// 停止 Spring 上下文
context.stop(); // ContextStoppedEvent
// 关闭 Spring 应用上下文
context.close(); // ContextClosedEvent
}
private static void println(Object printable) {
System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
}
}
结果输出如下:
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextStartedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
六、总结
Spring Event作为Spring一个基础能力,为Spring后续能力的扩展提供了极大的帮助,也为Spring变成业界通用框架奠定了坚固的基石。
本篇主要介绍了
- 事件编程的缘由
- Java事件的设计
- Spring Event扩展Java事件
- 基于接口和注解的Spring 事件监听器
- 注册Spring ApplicationListener的方式
- Spring 两种事件发布器
- Spring 层次性上下文事件传播以及如何解决
- Spring 四种内建事件
后续会进一步介绍Spring Event:
- Payload Event
- 自定义Spring 事件
- ApplicationEventPublisher、ApplicationEventMulticaster的相关处理
- 同步和异步Spring 事件广播
- 事件异常处理
- 事件和监听器底层实现原理
转载自:https://juejin.cn/post/7188425898561896506