实现Java事件系统

2025-07-16

在开发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

java
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这两个方法讲起

注册方法

java
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,是事件类型对应的所有需要调用的方法,方便在对应的事件在发布时找到所有需要这个事件的方法。

发布事件

java
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得到的原始实例。

注销事件

java
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中分别移除,这样就完成了事件监听器的完整注销。

评论

加载评论中…