一、为什么需求面向事情编程
面向事情编程:一切模块或者目标都是经过事情来进行交互,差异于传统编程不同模块直接彼此调用,模块间不需求进行强依靠。
传统编程形式如下:
面向事情编程形式如下:
面向事情编程的优势:
- 系统模块之间耦合度变低,各模块的改变不会产生彼此影响。
- 事情比办法更靠近事务,更加形而上。
劣势:
- 系统的复杂性会有所增强。
- 新手理解成本会有所增加。
二、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 事情广播
- 事情反常处理
- 事情和监听器底层完成原理