Guava中EventBus使用和实现简单的EventBus

868 阅读3分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

使用背景

某某服务商通过nlp识别用户的发送消息判断是不是具有特别含义的信息,比如是一条询价的信息,打上标签,推送过来我们这边,然后做一些业务处理。当然消息类型是有多种,不单单只有询价类型。处理多种不同的推送信息,考虑使用观察者模式。刚好Guava中EventBus符合要求,而且使用简单。

使用方法

首先是事件处理中心

很简单,就实例了一个EventBus

public class EventBusCenter {
    public static final EventBus eventBus = new EventBus();
}
定义不同的事件类型
@Data
@Builder
public class AEvent {
    private String name;
    private Integer id;
}
@Data
@Builder
public class BEvent {
    private String name;
    private Integer id;
}
@Data
@Builder
public class CEvent {
    private String name;
    private Integer id;
}
定义每个事件类型对应的监听器

注意,每个监听器的处理事件方法必须加上注解**@Subscribe,并且是public void,只有一个参数**。

public class AEventListener {

    @Subscribe
    public void handlerAEvent(AEvent aEvent) {
        System.out.println("handle A Event");
    }
}
public class BEventListener {

    @Subscribe
    public void handlerAEvent(BEvent bEvent) {
        System.out.println("handle B Event");
    }
}
public class CEventListener {

    @Subscribe
    public void handlerCEvent(CEvent cEvent) {
        System.out.println("handle C Event");
    }
}
测试

register方法注册监听器,post方法推送具体事件到eventbus上。

public class MainTest {
    public static void main(String[] args) {
        EventBusCenter.eventBus.register(new AEventListener());
        EventBusCenter.eventBus.register(new BEventListener());
        EventBusCenter.eventBus.register(new CEventListener());
        EventBusCenter.eventBus.post(AEvent.builder().id(1).name("A").build());
        EventBusCenter.eventBus.post(BEvent.builder().id(2).name("B").build());
        EventBusCenter.eventBus.post(CEvent.builder().id(3).name("C").build());
    }
}

image.png

使用方法很简单。但是应用到具体场景上还是不太合理。比如处理A事件的时候发生异常

@Subscribe
public void handlerAEvent(AEvent aEvent) {
    System.out.println("handle A Event");
    throw new RuntimeException();
}

image.png 异常会往上抛,最终被Subcriber catch。但是应用到具体场景中,发生异常后不用抛出,我有一个兜底的处理。可以使用eventbus中的事件异常处理SubscriberExceptionHandler。具体使用方法只需要创建eventbus的时候加入参数即可。

public class EventBusCenter {
    public static final EventBus eventBus = new EventBus(((exception, context) -> {
        System.out.println("error do something");
        System.out.println(context.getEvent());
        System.out.println(context.getEventBus());
        System.out.println(context.getSubscriber());
    }));
}

image.png 还有一个问题就是事件处理完了,该如何通知前端做相应的处理。我这边的处理专门写一个订阅事件完成通知的监听器,因为现有的架构是可以通过websocket与前端通信,不需要通过返回值通知前端。

public class MainTest {
    public static void main(String[] args) {
        EventBusCenter.eventBus.register(new AEventListener(EventBusCenter.eventBus));
        EventBusCenter.eventBus.register(new NoticeListener());
        EventBusCenter.eventBus.register(new BEventListener());
        EventBusCenter.eventBus.register(new CEventListener());
        EventBusCenter.eventBus.post(AEvent.builder().id(1).name("A").build());
        EventBusCenter.eventBus.post(BEvent.builder().id(2).name("B").build());
        EventBusCenter.eventBus.post(CEvent.builder().id(3).name("C").build());
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Notice {
    String content;
}
public class NoticeListener {


    @Subscribe
    public void handleNotice(Notice notice) {
        System.out.println(notice.getContent());
        //通过webcocket通知前端
    }
}
public class AEventListener {

    private EventBus eventBus;

    public AEventListener(EventBus eventBus) {
        this.eventBus = eventBus;
    }


    @Subscribe
    public void handlerAEvent(AEvent aEvent) {
        System.out.println("handle A Event");
        //事件处理完成
        eventBus.post(new Notice("handle A Event success"));
    }
}

image.png

参考某视频实现的一个简单eventbus

工程目录

image.png

MyBus
public interface MyBus {
    
    void register(Object subscriber);

    void post(Object event,String topic);

    void post(Object event);

    String getBusName();
}
MyEventBus
public class MyEventBus implements MyBus {

    private String busName;

    private MyEventExceptionHandler handler;

    private MyDispatcher dispatcher;

    private Executor executor;

    public MyEventBus(String busName) {
        this.busName = busName;
    }

    public MyEventBus(String busName, MyEventExceptionHandler handler) {
        this(busName);
        this.handler = handler;
    }

    public MyEventBus() {
        this(Constant.DEFAULT_BUS_NAME, null);
    }

    private final MyRegister register = new MyRegister();


    @Override
    public void register(Object subscriber) {
        this.register.bind(subscriber);
    }

    @Override
    public void post(Object event, String topic) {
        this.dispatcher.dispatcher(ExecutorParam.builder()
                .event(event)
                .bus(this)
                .topic(topic)
                .handler(handler)
                .build());
    }

    @Override
    public void post(Object event) {
        this.post(event, Constant.DEFAULT_TOPIC_NAME);
    }

    @Override
    public String getBusName() {
        return this.busName;
    }
}
MyRegister
/**
 * 用于保存订阅者
 */
class MyRegister {

    private final ConcurrentHashMap<String, ConcurrentLinkedQueue<MySubscriber>> registerTable = new ConcurrentHashMap<>();


    public ConcurrentLinkedQueue<MySubscriber> getMySubscriberQueue(String topic) {
        return registerTable.get(topic);
    }

    public void bind(Object subscribe) {
        List<Method> subscriberMethod = this.getSubscriberMethod(subscribe);
        subscriberMethod.forEach(v -> this.helper(v, subscribe));
    }

    public void unbind(Object subscribe) {
        registerTable.forEach((k, q) -> {
            q.forEach(v -> {
                if (v.getSubcriber() == subscribe) {
                    v.setDisable(true);
                }
            });
        });
    }

    private void helper(Method method, Object subscribe) {
        MySubscribe mySubscribe = method.getDeclaredAnnotation(MySubscribe.class);
        String topic = mySubscribe.topic();
        registerTable.computeIfAbsent(topic, key -> new ConcurrentLinkedQueue<>());
        ConcurrentLinkedQueue<MySubscriber> mySubscribers = registerTable.get(topic);
        mySubscribers.add(new MySubscriber(subscribe, method, false));

    }

    private List<Method> getSubscriberMethod(Object subscribe) {
        Class<?> aClass = subscribe.getClass();
        Method[] methods = aClass.getDeclaredMethods();
        List<Method> res = Arrays.stream(methods).filter(v -> {
            return v.isAnnotationPresent(MySubscribe.class) &&
                    v.getParameterCount() == 1 && v.getModifiers() == Modifier.PUBLIC;
        }).collect(Collectors.toList());
        return res;
    }
}
MyDispatcher
public class MyDispatcher {

    private final MyRegister register = new MyRegister();

    public void dispatcher(ExecutorParam param) {
        ConcurrentLinkedQueue<MySubscriber> mySubscriberQueue = register.getMySubscriberQueue(param.getTopic());
        if (Objects.isNull(mySubscriberQueue)) {
            if (Objects.isNull(param.getHandler())) {
                param.getHandler().handle(new IllegalArgumentException("topic非法"), MyEventBusContext.builder()
                        .event(param.getEvent())
                        .myEventBus(param.getBus())
                        .eventBusName(param.getBus().getBusName())
                        .build());
                return;
            } else {
                throw new IllegalArgumentException();
            }
        }
        mySubscriberQueue.stream().filter(v -> {
            return !v.getDisable();
        }).filter(s -> {
            Method method = s.getSubcriberMethods();
            Class<?> classz = method.getParameterTypes()[0];
            return classz.isAssignableFrom(param.getEvent().getClass());
        }).forEach(v -> {
            this.executorMethod(v, param);
        });

    }

    private void executorMethod(MySubscriber mySubscriber, ExecutorParam param) {
        Object subscriber = mySubscriber.getSubcriber();
        Method method = mySubscriber.getSubcriberMethods();
        param.getExecutor().execute(() -> {
            try {
                method.invoke(subscriber, param.getEvent());
            } catch (Exception e) {
                if (Objects.nonNull(param.getHandler())) {
                    param.getHandler().handle(new Exception("failed"), MyEventBusContext.builder()
                            .event(param.getEvent())
                            .myEventBus(param.getBus())
                            .eventBusName(param.getBus().getBusName())
                            .build());
                } else {
                    e.printStackTrace();
                }
            }
        });
    }
}
MySubscriber
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MySubscriber {

    private Object subcriber;

    private Method subcriberMethods;

    private Boolean disable;
}
MyEventExceptionHandler
/**
 * 事件异常处理
 */
public interface MyEventExceptionHandler {
    void handle(Throwable e, MyEventBusContext context);
}
ExecutorParam
@Data
@Builder
public class ExecutorParam {

    private Executor executor = DefaultExecutor.DEFAULT_EXECUTOR;
    private MyEventExceptionHandler handler;
    private MyRegister register;
    private MyBus bus;
    private Object event;
    private String topic;


    private static class DefaultExecutor implements Executor {
        private static final DefaultExecutor DEFAULT_EXECUTOR = new DefaultExecutor();

        @Override
        public void execute(Runnable command) {
            command.run();
        }
    }

}
注解MySubscribe
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MySubscribe {
    String topic() default "default_topic";
}
Constant
public class Constant {
    public final static String DEFAULT_BUS_NAME = "default";

    public final static String DEFAULT_TOPIC_NAME = "default_topic";
}