手把手教你实现一个 EventBus 框架

一、事件总线管理的“原材料”

import com.google.common.base.Preconditions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @program: superman
 * @description: 用来表示Subscribe的方法
 * @author: Miller.FAN   atl + command + L   快速格式化
 * @create: 2022-06-20 16:05
 **/
public class ObserverAction {
    private Object target;  //被通知的观察者
    private Method method;  //注解作用的方法

    //全参数构造器
    public ObserverAction(Object target, Method method) {
        this.target = Preconditions.checkNotNull(target);
        this.method = method;
        this.method.setAccessible(true);
    }
    //
    public void execute(Object event) { // event是method方法的参数
        try {
            method.invoke(target, event);  //反射回调
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

观察者的基础信息,维护的是观察者以及它收到消息后需要执行的方法;

二、事件总线的核心-注册表

import com.google.common.base.Preconditions;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @program: superman
 * @description:
 * @author: Miller.FAN
 * @create: 2022-06-20 16:09
 **/
public class ObserverRegistry {

    private ConcurrentMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>(100);

    //注册,观察者注册到 registry
    public void register(Object observer) {
        //获取所有的观察者的动作---方法
        Map<Class<?>, Collection<ObserverAction>> observerActions = findAllObserverActions(observer);
        //对map的每一个entry进行遍历处理
        for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : observerActions.entrySet()) {
            Class<?> eventType = entry.getKey();
            Collection<ObserverAction> eventActions = entry.getValue();
            //使用CopyOnWriteArraySet 如果结果集是null,就说明没有加入到注册表
            CopyOnWriteArraySet<ObserverAction> registeredEventActions = registry.get(eventType);
            if (registeredEventActions == null) {
                //将eventType, new CopyOnWriteArraySet<>()放进注册表
                registry.putIfAbsent(eventType, new CopyOnWriteArraySet<>());
                //在注册表中找到刚刚放进去的
                registeredEventActions = registry.get(eventType);
            }
            //将ObserverAction的CopyOnWriteArraySet加入
            registeredEventActions.addAll(eventActions);
        }
    }


    //获取匹配的ObserverActions ,框架默认第一个参数传的就是事件本身
    public List<ObserverAction> getMatchedObserverActions(Object event) {
        List<ObserverAction> matchedObservers = new ArrayList<>();
        //获取事件的类的类型
        Class<?> postedEventType = event.getClass();
        //遍历注册表
        for (Map.Entry<Class<?>, CopyOnWriteArraySet<ObserverAction>> entry : registry.entrySet()) {
            Class<?> eventType = entry.getKey();
            Collection<ObserverAction> eventActions = entry.getValue();
            //判断是不是我们要找的事件类类型
            if (postedEventType.isAssignableFrom(eventType)) {
                matchedObservers.addAll(eventActions);
            }
        }
        return matchedObservers;
    }

    获取所有的观察者的动作---方法
    private Map<Class<?>, Collection<ObserverAction>> findAllObserverActions(Object observer) {
        //一个观察者可能会有多个方法被Subscribe注解标注
        Map<Class<?>, Collection<ObserverAction>> observerActions = new HashMap<>();
        //使用对象获取类的类型
        Class<?> clazz = observer.getClass();
        for (Method method : getAnnotatedMethods(clazz)) {  //被注解标注的观察者的方法遍历
            //遍历每一个标注的方法获取方法的参数类型数组
            Class<?>[] parameterTypes = method.getParameterTypes();
            //获取第一个参数的类的类型
            Class<?> eventType = parameterTypes[0];
            //如果参数的类的类型没有被加入结果集,就将<第一个参数的类类型,一个new的list>放进去
            if (!observerActions.containsKey(eventType)) {
                observerActions.put(eventType, new ArrayList<>());
            }
            //Map的key是被标注的方法的第一个参数的类的类型,value是一个ObserverAction(观察者类的类型,被标注的方法)
            observerActions.get(eventType).add(new ObserverAction(observer, method));
        }
        return observerActions;
    }

    //获取所有的被注解标注的方法---反射的应用
    //入参是观察者的类的类型
    private List<Method> getAnnotatedMethods(Class<?> clazz) {
        List<Method> annotatedMethods = new ArrayList<>();
        for (Method method : clazz.getDeclaredMethods()) {
            //判断是不是Subscribe注解标注的方法,如果是就加入到ArrayList
            if (method.isAnnotationPresent(Subscribe.class)) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                Preconditions.checkArgument(parameterTypes.length == 1,
                        "Method %s has @Subscribe annotation but has %s parameters."
                                + "Subscriber methods must have exactly 1 parameter.", method, parameterTypes.length);
                annotatedMethods.add(method);
            }
        }
        return annotatedMethods;
    }

}

注册表示实现事件总线框架时最复杂的部分,我们提供两个基本功能,注册观察者和发消息(执行观察者的逻辑);这个类大量使用了 Java 的反射语法,不过我已经在代码中为大家详细的标明每一行代码的意义,理解起来并不难。

这里使用了CopyOnWriteArraySet,顾名思义,在写入数据的时候,会创建一个新的 set,并且将原始数据 clone 到新的 set 中,在新的 set 中写入数据完成之后,再用新的 set 替换老的 set。这样就能保证在写入数据的时候,不影响数据的读取操作,以此来解决读写并发问 题。除此之外,CopyOnWriteSet 还通过加锁的方式,避免了并发写冲突。

三、自定义一个注解---订阅者

import com.google.common.annotations.Beta;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Beta
public @interface Subscribe {
}

四、封装注册表和执行器--对外提供功能的EventBus

import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * @program: superman
 * @description:
 * @author: Miller.FAN
 * @create: 2022-06-20 17:29
 **/
public class EventBus {
    //封装执行器
    private Executor executor;
    //封装注册表
    private ObserverRegistry registry = new ObserverRegistry();

    public EventBus() {
        this(MoreExecutors.directExecutor());
    }

    protected EventBus(Executor executor) {
        this.executor = executor;
    }

    //注册观察者
    public void register(Object object) {
        registry.register(object);
    }

    //发送消息给观察者
    public void post(Object event) {
        //获取匹配的observerActions
        List<ObserverAction> observerActions = registry.getMatchedObserverActions(event);
        //循环执行
        for (ObserverAction observerAction : observerActions) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    observerAction.execute(event);
                }
            });
        }
    }
}

五、使用EventBus

当我们需要实现观察者模式的时候就可以引入EventBus,然后调用注册函数register将观察者注册到事件总线,而后调用Post函数发送消息给观察者。

我们实现了一个基本的观察者模式的框架,基本可以用来做一些观察者和被观察者的解耦工作。

用以解耦的正是一个集合--我们设计的注册表。藕断丝连,这里观察者和被观察者就是我们的两节藕,而注册表就是我们的丝,本框架实现的是一个异步阻塞的事件总线。如果要实现异步非阻塞我们可以在EventBus的基础上进一步封装。

import java.util.concurrent.Executor;

/**
 * @program: superman
 * @description:
 * @author: Miller.FAN
 * @create: 2022-06-20 17:33
 **/

public class AsyncEventBus extends EventBus {
    public AsyncEventBus(Executor executor) {
        super(executor);
    }
}

传入一个Executor,不再使用

MoreExecutors.directExecutor()就可以了!

说明:本文是学习冯永吉老师的设计模式之观察者模式时实践结果,补齐了一些缺失信息,供大家参考!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值