使用 java 手写事件驱动模型

1 介绍

  • 本文将手写一个事件驱动的模型案例。
  • 事件驱动:
    • EventManager 事件管理者
      • 单独运行一个线程,循环遍历事件队列(阻塞队列)。内部有一个Map集合,用于存放事件和用户之间的关系。当事件产生后,事件会被放置到阻塞队列中,而EventManager的子线程就会获取到该事件的相关信息,并提醒与之相关的用户,执行响应的方法
    • Event 事件
      • 事件的具体内容,可以有四部分组成。事件的来源、事件的目标、事件的种类、事件的消息。 对一个事件而言,知晓事件的种类是必要的,其他数据可以不给定。
    • EventHandler 事件处理接口
      • 需要User提供,当产生对应的事件后,该接口中的方法会被执行
    • User 事件订阅者(用户)
      • 告知 EventManager ,需要注册的EventHandler以及对应的Event。
    • Publisher 事件发布者
      • 发布Event
  • 整体结构如下图所示
    在这里插入图片描述

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
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值