1 介绍
- 本文将手写一个事件驱动的模型案例。
- 事件驱动:
- EventManager 事件管理者
- 单独运行一个线程,循环遍历事件队列(阻塞队列)。内部有一个Map集合,用于存放事件和用户之间的关系。当事件产生后,事件会被放置到阻塞队列中,而EventManager的子线程就会获取到该事件的相关信息,并提醒与之相关的用户,执行响应的方法
- Event 事件
- 事件的具体内容,可以有四部分组成。事件的来源、事件的目标、事件的种类、事件的消息。 对一个事件而言,知晓事件的种类是必要的,其他数据可以不给定。
- EventHandler 事件处理接口
- 需要User提供,当产生对应的事件后,该接口中的方法会被执行
- User 事件订阅者(用户)
- 告知 EventManager ,需要注册的EventHandler以及对应的Event。
- Publisher 事件发布者
- 发布Event
- EventManager 事件管理者
- 整体结构如下图所示
2 代码
代码已提交到gitee,地址
2.1 事件Event
- 必须给定事件的种类,其他的成员变量可以不给定
package com.wu.event;
/**
* 事件
* 必须给定事件的种类,其他的成员变量可以不给定
* @Author :吴用
* @Date :2021-01-07 21:36
* @Version :1.0
*/
public class Event {
/**
* 事件的来源
*/
private Object src;
/**
* 事件的目标
*/
private Object target;
/**
* 事件的种类
*/
private int type;
/**
* 事件附带的消息
*/
private Object msg;
/**
* 只有事件的种类
* @param type
*/
public Event(int type) {
this.type = type;
}
/**
* 完整的事件
* @param src
* @param target
* @param type
* @param msg
*/
public Event(Object src, Object target, int type, Object msg) {
this.src = src;
this.target = target;
this.type = type;
this.msg = msg;
}
public Event(Object src, int type) {
this.src = src;
this.type = type;
}
public Event(int type, Object msg) {
this.type = type;
this.msg = msg;
}
public Event(Object src, Object target, int type) {
this.src = src;
this.target = target;
this.type = type;
}
public Event(Object src, int type, Object msg) {
this.src = src;
this.type = type;
this.msg = msg;
}
public int getType() {
return type;
}
public Object getSrc() {
return src;
}
public void setSrc(Object src) {
this.src = src;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Object getMsg() {
return msg;
}
public void setMsg(Object msg) {
this.msg = msg;
}
}
2.2 事件处理EventHandler
package com.wu.event;
/**
* 事件处理者
* @Author :吴用
* @Date :2021-01-07 21:41
* @Version :1.0
*/
public interface EventHandler {
void onEvent(Event e);
}
2.3 事件管理者EventManager
- 基于多线程的考虑,使用单例模式,你也可以变一下代码,把它直接注入到Spring容器中。
- ConcurrentHashMap是JUC的集合,该集合提供原子操作的方法,来防止并发访问错误
- 事件管理者将提供注册事件、发布事件的方法
package com.wu.event;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author :吴用
* @Date :2021-01-07 21:40
* @Version :1.0
*/
public class EventManager {
/**
* 用户注册后,将登记在这个集合里面,集合为Set类型,防止重复注册
* ConcurrentHashMap用来预防线程安全问题
*/
private ConcurrentHashMap<Integer,Set<EventHandler>> map;
/**
* 事件队列,当发布事件的时候,会加入到该队列中
*/
private BlockingQueue<Event> queue;
/**
* 线程任务是否需要继续运行,当关闭线程的时候,将它置为false
*/
private volatile boolean isNeedContinue=true;
/**
* 循环处理消息的线程
*/
private Thread task;
/**
* 等待事件的最长个数
*/
private static final int MAX_WAITED_TASK = 20;
/**
* DCL单例模式,如果使用Spring的话,这里直接将它注入到spring容器就好了
*/
private static volatile EventManager e;
private EventManager() { }
public static EventManager getEventManager(){
if(e==null){
synchronized (EventManager.class){
if(e==null){
e = new EventManager();
}
}
}
return e;
}
private volatile boolean isStart = false;
/**
* 执行任务
*/
public void startManage() {
if(isStart){
return;
}
synchronized (EventManager.class){
if(isStart){
return;
}
map = new ConcurrentHashMap<>(5);
// 待处理的事件的最大长度
queue = new ArrayBlockingQueue<>(MAX_WAITED_TASK);
// 由于只有一个线程,并且线程长期存在,就不建立线程池了
task = new Thread(() -> {
// 循环执行,直至关闭
while (isNeedContinue) {
try{
// 阻塞获取队列事件
// 该阻塞方法在关闭线程的时候需要interrupt方法打断。
Event take = queue.take();
// 获取事件类型
int type = take.getType();
map.computeIfPresent(type, (type2,set)->{
// 遍历事件队列,执行事件的内容
for(EventHandler eh :set){
eh.onEvent(take);
}
return set;
});
}catch (Throwable t){
t.printStackTrace();
}
}
});
task.start();
}
}
/**
* 注册事件
* @param e
*/
public void registEvent(Event e,EventHandler handler){
map.computeIfAbsent(e.getType(), type->{
// 如果该类型的事件是第一次注册
// 创建一个Set
Set<EventHandler> set = new HashSet<>();
set.add(handler);
return set;
});
map.computeIfPresent(e.getType(),(type,set)->{
// 如果已经有该类事件了,直接像内部添加
set.add(handler);
return set;
});
}
/**
* 发布事件
* @param e
*/
public void publishEvent(Event e){
queue.add(e);
}
/**
* 关闭线程
*/
public void close() {
isNeedContinue = false;
task.interrupt();
}
}
2.4 用户User
- User 将订阅事件
- 由于想要统一管理事件这里的以下常量我给它抽出去,做成一个接口了,接口在下文中
package com.wu;
import com.wu.event.Event;
import com.wu.event.EventConfig;
import com.wu.event.EventManager;
/**
* 消息订阅者
* @Author :吴用
* @Date :2021-01-07 22:51
* @Version :1.0
*/
public class User {
/**
* 订阅消息
*/
public void registEvent(){
EventManager eventManager = EventManager.getEventManager();
eventManager.registEvent(new Event(EventConfig.FIRST_EVENT), e->{
EventConfig.USER_EVENT_HANDLER.apply("我是user");
});
}
}
2.5 事件发布者Publisher
- Publisher 将发布事件
- 由于想要统一管理事件这里的以下常量我给它抽出去,做成一个接口了,接口在下文中
package com.wu;
import com.wu.event.Event;
import com.wu.event.EventConfig;
import com.wu.event.EventManager;
/**
* 事件发布者
* @Author :吴用
* @Date :2021-01-07 22:51
* @Version :1.0
*/
public class Publisher {
/**
* 发布事件
*/
public void publishEvent(){
Event event = new Event(EventConfig.FIRST_EVENT);
EventManager eventManager = EventManager.getEventManager();
eventManager.publishEvent(event);
}
}
2.6 统一管理配置信息
- 将2.4,2.5中的某些常量和方法提取成接口,统一管理
package com.wu.event;
import java.util.function.Function;
/**
* @Author :吴用
* @Date :2021-01-07 22:52
* @Version :1.0
*/
public interface EventConfig {
/**
* 事件的种类
*/
int FIRST_EVENT = 1;
/**
* 事件处理方法,统一放到这里,好进行管理
*/
Function<String,Void> USER_EVENT_HANDLER = msg->{
System.out.println("事件产生了!"+msg);
return null;
};
}
2.7 测试
package com.wu;
import com.wu.event.EventManager;
/**
* 手写一个事件驱动框架
*
* @Author :吴用
* @Date :2021-01-07 21:34
* @Version :1.0
*/
public class MyApplication {
static public void main(String[] args) {
// 启动事件的管理者
EventManager eventManager = EventManager.getEventManager();
eventManager.startManage();
// 事件的订阅者,订阅事件
User user = new User();
user.registEvent();
// 事件的提供者在其他线程发布了事件
Publisher publisher = new Publisher();
new Thread(()->{
// 发布了10个事件
for (int i = 0; i < 10; i++) {
publisher.publishEvent();
}
}).start();
try {
Thread.sleep(2000);
// 关闭eventManager
eventManager.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 结束时抛出异常。异常的原因是BlockQueue集合的take方法是阻塞获取,当线程使用thread.interrupt()的时候,将会抛出异常。
结果如下:
"C:\Program Files\Java\jdk1.8.0_241\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:5587,suspend=y,server=n -javaagent:C:\Users\wuhui\.IntelliJIdea2018.1\system\captureAgent\debugger-agent.jar=file:/C:/Users/wuhui/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\RXTXcomm.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;F:\Learning information\JAVA\maven\event\target\classes;D:\software\IntelliJ IDEA 2018.1.8\lib\idea_rt.jar" com.wu.MyApplication
Connected to the target VM, address: '127.0.0.1:5587', transport: 'socket'
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
事件产生了!我是user
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
at com.wu.event.EventManager.lambda$startManage$1(EventManager.java:76)
Disconnected from the target VM, address: '127.0.0.1:5587', transport: 'socket'
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0