实现Java事件系统
在开发Java项目时,难免会有功能之间互相引用的需求,直接调用固然是最简单的方法,但是会让代码过于耦合,事件系统是一种非常有效的解耦手段。
市面上有很多现有的事件系统实现,例如GreenBot和Forge EventBus,以及一个比较广泛流传的小型EventBus实现DarkMagician6 EventAPI,本文旨在介绍一个我的简单的(核心代码不超过120行)事件系统,以及你可以根据本文中的原理实现你自己的。
如果你在寻找一个事件系统,可以直接使用我的EventManager系统在你的项目,已经在多个项目中使用,以Unlicense协议开源,并不保证最高性能,完全基于反射,目标为简单,易用以及示范。
直接调用和事件系统
无事件系统
Feature A
|
| onMessage()
v
Feature B有事件系统
Feature A
|
| EventManager.call(new MessageEvent())
v
Event Manager
|
| 调用所有已经注册的监听器
v
+-------------+
| Feature B | <--+
+-------------+ |
| 根据优先级接收MessageEvent
+-------------+ | 并处理事件
| Feature C | <--+
+-------------+ |
|
+-------------+ |
| Feature D | <--+
+-------------+工作原理
首先示范一个伪代码Demo,一个最基础的事件系统系统调用,后续介绍的代码均基于我的事件系统EventManager
public class Main {
public static void main(String[] args){
EventManager eventManager = new EventManager();
eventManager.register(new Feature());
eventManager.call(new MessageEvent("Hello from Main"));
}
}
public class MessageEvent extends Event {
public String message;
public MessageEvent(String message){
this.message = message;
}
}
public class Feature {
@EventTarget
public void onMessage(MessageEvent event){
System.out.println(event.message);
}
}运行这个Demo之后,Message从Feature这个类的println中输出了,这就是一个典型的事件驱动代替直接调用的例子,要探究这背后的原理,要从register,call这两个方法讲起
注册方法
private final Map<Method, Class<?>> registeredMethodMap;
private final Map<Method, Object> methodObjectMap;
private final Map<Class<? extends Event>, List<Method>> priorityMethodMap;
public void register(Object obj) {
Class<?> clazz = obj.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == EventTarget.class && method.getParameterTypes().length == 1) {
registeredMethodMap.put(method, method.getParameterTypes()[0]);
methodObjectMap.put(method, obj);
Class<? extends Event> eventClass = method.getParameterTypes()[0].asSubclass(Event.class);
priorityMethodMap.computeIfAbsent(eventClass, k -> new CopyOnWriteArrayList<>()).add(method);
}
}
}
}逐行开始解释,obj即为你要注册的对象,即Feature的实例,首先我们需要通过反射来扫描所有在事件系统中需要调用的方法,为了避免调用到你不需要的方法,我们需要判断一下有没有某个注解在你需要的方法上。
这个注解没有任何实际作用和影响(在当前代码中),扫描到所有目标方法之后,首先判断第一个参数(需要传入的事件)是否为一个Event,如果是一个Event则将方法加入两个Map,一个用来储存方法需要的事件类型,用于在取消注册这个对象时候查找有哪些方法需要取消注册,一个用来储存对应的对象,方便后续调用方法。
最后将这个方法置入一个<Class<? extends Event>, List<Method>>Map,是事件类型对应的所有需要调用的方法,方便在对应的事件在发布时找到所有需要这个事件的方法。
发布事件
public Event call(Event event) {
Class<? extends Event> eventClass = event.getClass();
List<Method> methods = priorityMethodMap.get(eventClass);
if (methods != null) {
methods.sort(Comparator.comparingInt(method -> {
EventPriority priority = method.getAnnotation(EventPriority.class);
return (priority != null) ? priority.value() : 10;
}));
for (Method method : methods) {
Object obj = methodObjectMap.get(method);
method.setAccessible(true);
try {
method.invoke(obj, event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return event;
}发布一个事件之后,我们需要从priorityMethodMap中获得所有需要这个事件的方法,找到之后,我们要对这个列表进行排序,因为事件方法可能会有优先级需求,例如我就是需要FeatureB的调用比FeatureA更早,我就可以使用EventPriority注解给FeatureB更高的优先级(因为comparingInt是升序排序,所以更低的EventPriority数值则代表更高的优先级,例如5是比10更早执行的,如果你不习惯可以在排序之后调用reversed()来反转,即可实现降序,也就是优先级数值越高,优先级越高),这个排序其实不一定需要写在这里,也可以写在register里,注册之后进行排序。
排序之后即可对所有方法进行反射调用,传入从methodObjectMap得到的原始实例。
注销事件
public void unregister(Object obj) {
Class<?> clazz = obj.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (registeredMethodMap.containsKey(method)) {
registeredMethodMap.remove(method);
methodObjectMap.remove(method);
Class<? extends Event> eventClass = method.getParameterTypes()[0].asSubclass(Event.class);
List<Method> priorityMethods = priorityMethodMap.get(eventClass);
if (priorityMethods != null) {
priorityMethods.remove(method);
}
}
}
}注销事件的过程与注册事件相反,过程就是获取对象的Class实例和所有声明的方法,然后从三个map中分别移除,这样就完成了事件监听器的完整注销。
订阅更新
如果您喜欢我的文章,输入您的邮箱订阅更新,有新的文章会通知您
登录 GitHub 后参与讨论
加载评论中…