• Guava学习之EventBus


    一、EventBus的使用案例

    EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和接口层次结构。

      Observer模式是比较常用的设计模式之一,虽然有时候在具体代码里,它不一定叫这个名字,比如改头换面叫个Listener,但模式就是这个模式。手工实现一个Observer也不是多复杂的一件事,只是因为这个设计模式实在太常用了,Java就把它放到了JDK里面:Observable和Observer,从JDK 1.0里,它们就一直在那里。从某种程度上说,它简化了Observer模式的开发,至少我们不用再手工维护自己的Observer列表了。不过,如前所述,JDK里的Observer从1.0就在那里了,直到Java 7,它都没有什么改变,就连通知的参数还是Object类型。要知道,Java 5就已经泛型了。Java 5是一次大规模的语法调整,许多程序库从那开始重新设计了API,使其更简洁易用。当然,那些不做应对的程序库,多半也就过时了。这也就是这里要讨论知识更新的原因所在。今天,对于普通的应用,如果要使用Observer模式该如何做呢?答案是Guava的EventBus。

      EventBus基本用法:

      使用Guava之后, 如果要订阅消息, 就不用再继承指定的接口, 只需要在指定的方法上加上@Subscribe注解即可。代码如下:

      消息封装类:

    复制代码
    public class TestEvent {
        private final int message;
        public TestEvent(int message) {        
            this.message = message;
            System.out.println("event message:"+message);
        }
        public int getMessage() {
            return message;
        }
    }
    复制代码

      消息接受类:

    复制代码
    public class EventListener {
        public int lastMessage = 0;
    
        @Subscribe
        public void listen(TestEvent event) {
            lastMessage = event.getMessage();
            System.out.println("Message:"+lastMessage);
        }
    
        public int getLastMessage() {      
            return lastMessage;
        }
    }
    复制代码

      测试类及输出结果:

    复制代码
    public class TestEventBus {
        @Test
        public void testReceiveEvent() throws Exception {
    
            EventBus eventBus = new EventBus("test");
            EventListener listener = new EventListener();
    
            eventBus.register(listener);
    
            eventBus.post(new TestEvent(200));
            eventBus.post(new TestEvent(300));
            eventBus.post(new TestEvent(400));
    
            System.out.println("LastMessage:"+listener.getLastMessage());
            ;
        }
    }
    
    //输出信息
    event message:200
    Message:200
    event message:300
    Message:300
    event message:400
    Message:400
    LastMessage:400
    复制代码

       MultiListener的使用:

      只需要在要订阅消息的方法上加上@Subscribe注解即可实现对多个消息的订阅,代码如下:

    复制代码
    public class MultipleListener {
        public Integer lastInteger;  
        public Long lastLong;  
       
        @Subscribe  
        public void listenInteger(Integer event) {  
            lastInteger = event; 
            System.out.println("event Integer:"+lastInteger);
        }  
       
        @Subscribe  
        public void listenLong(Long event) {  
            lastLong = event; 
            System.out.println("event Long:"+lastLong);
        }  
       
        public Integer getLastInteger() {  
            return lastInteger;  
        }  
       
        public Long getLastLong() {  
            return lastLong;  
        }  
    }
    复制代码

      测试类:

    复制代码
    public class TestMultipleEvents {
        @Test  
        public void testMultipleEvents() throws Exception {  
           
            EventBus eventBus = new EventBus("test");  
            MultipleListener multiListener = new MultipleListener();  
           
            eventBus.register(multiListener);  
           
            eventBus.post(new Integer(100));
            eventBus.post(new Integer(200));  
            eventBus.post(new Integer(300));  
            eventBus.post(new Long(800)); 
            eventBus.post(new Long(800990));  
            eventBus.post(new Long(800882934));  
           
            System.out.println("LastInteger:"+multiListener.getLastInteger());
            System.out.println("LastLong:"+multiListener.getLastLong());
        }   
    }
    
    //输出信息
    event Integer:100
    event Integer:200
    event Integer:300
    event Long:800
    event Long:800990
    event Long:800882934
    LastInteger:300
    LastLong:800882934
    复制代码

      Dead Event:

      如果EventBus发送的消息都不是订阅者关心的称之为Dead Event。实例如下:

    复制代码
    public class DeadEventListener {
        boolean notDelivered = false;  
           
        @Subscribe  
        public void listen(DeadEvent event) {  
            
            notDelivered = true;  
        }  
       
        public boolean isNotDelivered() {  
            return notDelivered;  
        }  
    }
    复制代码

      测试类:

    复制代码
    public class TestDeadEventListeners {
        @Test  
        public void testDeadEventListeners() throws Exception {  
           
            EventBus eventBus = new EventBus("test");               
            DeadEventListener deadEventListener = new DeadEventListener();  
            eventBus.register(deadEventListener);  
    
            eventBus.post(new TestEvent(200));         
            eventBus.post(new TestEvent(300));        
           
            System.out.println("deadEvent:"+deadEventListener.isNotDelivered());
    
        }  
    }
    
    //输出信息
    event message:200
    event message:300
    deadEvent:true
    复制代码

      说明:如果没有消息订阅者监听消息, EventBus将发送DeadEvent消息,这时我们可以通过log的方式来记录这种状态。

      Event的继承:

      如果Listener A监听Event A, 而Event A有一个子类Event B, 此时Listener A将同时接收Event A和B消息,实例如下:

      Listener 类:

    复制代码
    public class NumberListener {  
           
        private Number lastMessage;  
       
        @Subscribe  
        public void listen(Number integer) {  
            lastMessage = integer; 
            System.out.println("Message:"+lastMessage);
        }  
       
        public Number getLastMessage() {  
            return lastMessage;  
        }  
    }  
    
    public class IntegerListener {  
           
        private Integer lastMessage;  
       
        @Subscribe  
        public void listen(Integer integer) {  
            lastMessage = integer; 
            System.out.println("Message:"+lastMessage);
        }  
       
        public Integer getLastMessage() {  
            return lastMessage;  
        }  
    }  
    复制代码

      测试类:

    复制代码
    public class TestEventsFromSubclass {
        @Test  
        public void testEventsFromSubclass() throws Exception {  
           
            EventBus eventBus = new EventBus("test");  
            IntegerListener integerListener = new IntegerListener();  
            NumberListener numberListener = new NumberListener();  
            eventBus.register(integerListener);  
            eventBus.register(numberListener);  
           
            eventBus.post(new Integer(100));  
           
            System.out.println("integerListener message:"+integerListener.getLastMessage());
            System.out.println("numberListener message:"+numberListener.getLastMessage());
                  
            eventBus.post(new Long(200L));  
           
            System.out.println("integerListener message:"+integerListener.getLastMessage());
            System.out.println("numberListener message:"+numberListener.getLastMessage());        
        }  
    }
    
    //输出类
    Message:100
    Message:100
    integerListener message:100
    numberListener message:100
    Message:200
    integerListener message:100
    numberListener message:200
    复制代码

      说明:在这个方法中,我们看到第一个事件(新的整数(100))是收到两个听众,但第二个(新长(200 l))只能到达NumberListener作为整数一不是创建这种类型的事件。可以使用此功能来创建更通用的监听器监听一个广泛的事件和更详细的具体的特殊的事件。

       一个综合实例:

    复制代码
    public class UserThread extends Thread {
        private Socket connection;
        private EventBus channel;
        private BufferedReader in;
        private PrintWriter out;
    
        public UserThread(Socket connection, EventBus channel) {
            this.connection = connection;
            this.channel = channel;
            try {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                out = new PrintWriter(connection.getOutputStream(), true);
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    
        @Subscribe
        public void recieveMessage(String message) {
            if (out != null) {
                out.println(message);
                System.out.println("recieveMessage:"+message);
            }
        }
    
        @Override
        public void run() {
            try {
                String input;
                while ((input = in.readLine()) != null) {
                    channel.post(input);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            //reached eof
            channel.unregister(this);
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            in = null;
            out = null;
        }
    }
    复制代码
    复制代码
    mport java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    import com.google.common.eventbus.EventBus;
    
    public class EventBusChat {
        public static void main(String[] args) {
            EventBus channel = new EventBus();
            ServerSocket socket;
            try {
                socket = new ServerSocket(4444);
                while (true) {
                    Socket connection = socket.accept();
                    UserThread newUser = new UserThread(connection, channel);
                    channel.register(newUser);
                    newUser.start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    复制代码

      说明:用telnet命令登录:telnet 127.0.0.1 4444 ,如果你连接多个实例你会看到任何消息发送被传送到其他实例。

    二、EventBus源码解读

    Register函数

     public void register(Object object) {
        Multimap<Class<?>, EventSubscriber> methodsInListener =
            finder.findAllSubscribers(object);
        subscribersByTypeLock.writeLock().lock();
        try {
          subscribersByType.putAll(methodsInListener);
        } finally {
          subscribersByTypeLock.writeLock().unlock();
        }
      }

    methodsInListener中会根据@Subscribe的注解查找所传入对象的EventHandler,并将事件类型与EventSubscriber的映射保留在subscribersByType对象中。
    再来看AnnotatedSubscriberFinder中的findAllSubscribers方法:

    @Override
      public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
        Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create();
        Class<?> clazz = listener.getClass();
        for (Method method : getAnnotatedMethods(clazz)) {
          Class<?>[] parameterTypes = method.getParameterTypes();
          Class<?> eventType = parameterTypes[0];
          EventSubscriber subscriber = makeSubscriber(listener, method);
          methodsInListener.put(eventType, subscriber);
        }
        return methodsInListener;
      }
    

      上面代码的内容就是通过反射读取Subscriber对象中带有@Subscribe注解的方法,并使用makeSubscriber将Subscriber对象和它的带@Subscribe对象的method对象封装成EventSubscriber标准事件处理对象:

     private static EventSubscriber makeSubscriber(Object listener, Method method) {
        EventSubscriber wrapper;
        if (methodIsDeclaredThreadSafe(method)) {
          wrapper = new EventSubscriber(listener, method);
        } else {
          wrapper = new SynchronizedEventSubscriber(listener, method);
        }
        return wrapper;
      }

    Post函数:

    public void post(Object event) {
            Set<Class<?>> dispatchTypes = this.flattenHierarchy(event.getClass());
            boolean dispatched = false;
            Iterator i$ = dispatchTypes.iterator();
    
            while(i$.hasNext()) {
                Class<?> eventType = (Class)i$.next();
                this.subscribersByTypeLock.readLock().lock();
    
                try {
                    Set<EventSubscriber> wrappers = this.subscribersByType.get(eventType);
                    if (!wrappers.isEmpty()) {
                        dispatched = true;
                        Iterator i$ = wrappers.iterator();
    
                        while(i$.hasNext()) {
                            EventSubscriber wrapper = (EventSubscriber)i$.next();
                            this.enqueueEvent(event, wrapper);
                        }
                    }
                } finally {
                    this.subscribersByTypeLock.readLock().unlock();
                }
            }
    
            if (!dispatched && !(event instanceof DeadEvent)) {
                this.post(new DeadEvent(this, event));
            }
    
            this.dispatchQueuedEvents();
        }
    首先获取事件类型,再将事件和对应的EventSubscriber加入队列:
     
     void enqueueEvent(Object event, EventSubscriber subscriber) {
            ((Queue)this.eventsToDispatch.get()).offer(new EventBus.EventWithSubscriber(event, subscriber));
        }
    最后通过dispatchQueuedEvents()处理队列中的事件:
     
     void dispatchQueuedEvents() {
            if (!(Boolean)this.isDispatching.get()) {
                this.isDispatching.set(true);
    
                try {
                    Queue events = (Queue)this.eventsToDispatch.get();
    
                    EventBus.EventWithSubscriber eventWithSubscriber;
                    while((eventWithSubscriber = (EventBus.EventWithSubscriber)events.poll()) != null) {
                        this.dispatch(eventWithSubscriber.event, eventWithSubscriber.subscriber);
                    }
                } finally {
                    this.isDispatching.remove();
                    this.eventsToDispatch.remove();
                }
    
            }
        }
    dispatch的代码如下:
     void dispatch(Object event, EventSubscriber wrapper) {
            try {
                wrapper.handleEvent(event);
            } catch (InvocationTargetException var6) {
                InvocationTargetException e = var6;
    
                try {
                    this.subscriberExceptionHandler.handleException(e.getCause(), new SubscriberExceptionContext(this, event, wrapper.getSubscriber(), wrapper.getMethod()));
                } catch (Throwable var5) {
                    Logger.getLogger(EventBus.class.getName()).log(Level.SEVERE, String.format("Exception %s thrown while handling exception: %s", var5, var6.getCause()), var5);
                }
            }
    
        }
    wrapper.hanleEvent()反射调用事件处理的方法:
     
    public void handleEvent(Object event) throws InvocationTargetException {
            Preconditions.checkNotNull(event);
    
            String var3;
            try {
                this.method.invoke(this.target, event);
            } catch (IllegalArgumentException var4) {
                var3 = String.valueOf(String.valueOf(event));
                throw new Error((new StringBuilder(33 + var3.length())).append("Method rejected target/argument: ").append(var3).toString(), var4);
            } catch (IllegalAccessException var5) {
                var3 = String.valueOf(String.valueOf(event));
                throw new Error((new StringBuilder(28 + var3.length())).append("Method became inaccessible: ").append(var3).toString(), var5);
            } catch (InvocationTargetException var6) {
                if (var6.getCause() instanceof Error) {
                    throw (Error)var6.getCause();
                } else {
                    throw var6;
                }
            }
        }

    总结一下

    1.首先是Register方法把加了@Subscribe注解方法的listener和方法通过反射方式注册到缓存的map中 格式为,参数类型:EventSubscriber

    EventSubscriber中包含listerner类和方法名。

    2.post时首先根据post中的参数类型找到对应的EventSubscriber,然后把EventSubscriber放入当前线程的对列Queue中,然后去执行当前线程的这个队列

    三、手动实现EventBus和AsyncEventBus案例


    首先导入guava的依赖

    <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.0-jre</version>
    </dependency>

    目录结构介绍:

    event:一个自定义的事件类,类的内容随意定义。

    eventListeners:定义了两个事件监听者类,类里面的方法加@Subscribe注解。

    util:eventBus工具类。

    TestMain类,测试函数。

    本案例在EventBusUtil工具类中声明了EventBus和AsyncEventBus两个变量,因此,在后面演示AsyncEventBus的使用时,只需要更改TestMain中的post()方法即可。
    下面逐一上代码。

    CustomEvent类代码

    public class CustomEvent {
        private int age;
        public CustomEvent(int age){
            this.age = age;
        }
        public int getAge(){
            return this.age;
        }
    }


    EventListener1.java类代码

    public class EventListener1 {
        @Subscribe
        public void test1(CustomEvent event){
            System.out.println(Instant.now() +"监听者1-->订阅者1,收到事件:"+event.getAge()+",线程号为:"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Subscribe
        public void test2(CustomEvent event){
            System.out.println(Instant.now() +"监听者1-->订阅者2,收到事件:"+event.getAge()+",线程号为:"+Thread.currentThread().getName());
        }
    }


    EventListener2.java类代码

    public class EventListener2 {
        @Subscribe
        public void test(CustomEvent event){
            System.out.println(Instant.now() +",监听者2,收到事件:"+event.getAge()+",线程号为:"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    可以看到,两个监听者类,一共定义了三个订阅者,三个订阅者订阅的都是同一个事件对象。待会观察一下EventBus同步的方式下,收到事件之后订阅者们的处理方式。

    工具类代码:

    public class EventBusUtil {
        private static EventBus eventBus;
        private static AsyncEventBus asyncEventBus;
        private static Executor executor = new Executor() {
            public void execute(Runnable command) {
                new Thread(command).start();
            }
        };
        //双重锁单例模式
        private static AsyncEventBus getAsynEventBus(){
            if(asyncEventBus==null){
                synchronized (AsyncEventBus.class){
                    if(asyncEventBus==null){
                        asyncEventBus = new AsyncEventBus(executor);
                    }
                }
            }
            return asyncEventBus;
        }
        //双重锁单例模式
        private static EventBus getEventBus(){
            if(eventBus==null){
                synchronized (EventBus.class){
                    if(eventBus==null){
                        eventBus = new EventBus();
                    }
                }
            }
            return eventBus;
        }
        public static void post(Object event){
            getEventBus().post(event);
        }
        //异步方式发送事件
        public static void asyncPost(Object event){
            getAsynEventBus().post(event);
        }
        public static void register(Object object){
            getEventBus().register(object);
            getAsynEventBus().register(object);
        }
     
    }


    测试类代码:

    public class TestMain {
        public static void main(String[] args) {
            EventListener1 listener1 = new EventListener1();
            EventListener2 listener2 = new EventListener2();
            CustomEvent customEvent = new CustomEvent(23);
            EventBusUtil.register(listener1);
            EventBusUtil.register(listener2);
            EventBusUtil.post(customEvent);
    //        EventBusUtil.asyncPost(customEvent);
    //        try {
    //            Thread.sleep(10*1000);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
            System.out.println(Instant.now() +",主线程执行完毕:"+Thread.currentThread().getName());
        }
    }


    上面是测试类,创建了事件监听者的对象,并且注册给了EventBus,调用EventBus的同步post方法执行。结果如下:

    2019-09-04T09:05:35.420Z,监听者1-->订阅者1,收到事件:23,线程号为:main
    2019-09-04T09:05:38.480Z,监听者1-->订阅者2,收到事件:23,线程号为:main
    2019-09-04T09:05:38.480Z,监听者2,收到事件:23,线程号为:main
    2019-09-04T09:05:41.485Z,主线程执行完毕:main
    同步EventBus总结规律:可以看到每一个事件的消费方在执行时,都是用的调用方的线程,并且同一时间只能同时执行一个订阅者的方法。从Listener1里的方法比Listener2里的方法先执行可以看出,先注册到EventBus的订阅者在收到事件后会先执行。

    那么测试一下异步发送事件的结果,将上面注释打开,切换到EventBusUtil.asyncPost()方法,日志如下:

    2019-09-04T09:12:14.010Z,监听者1-->订阅者2,收到事件:23,线程号为:Thread-2
    2019-09-04T09:12:14.010Z,监听者2,收到事件:23,线程号为:Thread-3
    2019-09-04T09:12:14.010Z,监听者1-->订阅者1,收到事件:23,线程号为:Thread-1
    2019-09-04T09:12:24.014Z,主线程执行完毕:main
    异步执行,三个订阅者同时执行,并且是为事件消费方重新开的一个新的线程去执行自己的任务,互相不等待。

    这里由于并行执行,订阅者的方法中有sleep,因此也让主线程进行了10秒的等待。

    四、EventBus和AsyncEventBus使用区别


    上面的测试案例简单,并且很能说明问题。

    EventBus:同步事件总线

    1.同步执行,事件发送方在发出事件之后,会等待所有的事件消费方执行完毕后,才会回来继续执行自己后面的代码。

    2.事件发送方和事件消费方会在同一个线程中执行,消费方的执行线程取决于发送方。

    3.同一个事件的多个订阅者,在接收到事件的顺序上面有不同。谁先注册到EventBus的,谁先执行,如果是在同一个类中的两个订阅者一起被注册到EventBus的情况,收到事件的顺序跟方法名有关。

    AsyncEventBus:异步事件总线

    1.异步执行,事件发送方异步发出事件,不会等待事件消费方是否收到,直接执行自己后面的代码。

    2.在定义AsyncEventBus时,构造函数中会传入一个线程池。事件消费方收到异步事件时,消费方会从线程池中获取一个新的线程来执行自己的任务。

    3.同一个事件的多个订阅者,它们的注册顺序跟接收到事件的顺序上没有任何联系,都会同时收到事件,并且都是在新的线程中,异步并发的执行自己的任务。

    参考:https://blog.csdn.net/xfdingustc/article/details/43528821

             https://www.cnblogs.com/peida/p/EventBus.html

             https://blog.csdn.net/qq_38345296/article/details/100539989

  • 相关阅读:
    如何使用Arrays工具类操作数组
    Java 内存模型详解
    HashSet源码分析:JDK源码系列
    在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory
    在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求
    在ASP.NET Core中用HttpClient(四)——提高性能和优化内存
    ASP.NET Core与Redis搭建一个简易分布式缓存
    在ASP.NET Core中用HttpClient(三)——发送HTTP PATCH请求
    在ASP.NET Core中用HttpClient(二)——发送POST, PUT和DELETE请求
    在ASP.NET Core中用HttpClient(一)——获取数据和内容
  • 原文地址:https://www.cnblogs.com/guanbin-529/p/13022610.html
Copyright © 2020-2023  润新知