一、事件总线管理的“原材料”
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()就可以了!
说明:本文是学习冯永吉老师的设计模式之观察者模式时实践结果,补齐了一些缺失信息,供大家参考!