Springboot事件机制整合EventBus应用(事件驱动模型)

6,437 阅读9分钟

事件监听机制

熟悉Spring的同学,spring提供了一套完整的事件监听机制。要了解spring不妨先熟悉一下,观察者模式。Java 从1.0版本,已经有了观察者模式的设计。下面通过一个案例来了解一下Java所提供的观察者模式。

观察者模式

观察者模式为,一个被观察者Observable(被观察主题)和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,会通知其所有的观察者。其实就是一个主题改变,订阅它的观察者都会收到相关的消息。(也可以叫做,发布订阅模式) JDK 观察者模式 JDK 以上是观察者模式的类图。 观察者(Obsever)、被观察者(Observable)。通过源码可以看出被观察者中会维护观察者对象,观察者注册到被观察者列表后,被观察者发出的通知观察者都将收到改变。而观察者中的unpdate(Observable,Object)方法。就是通过监听被观察者发生的改变,来触发观察者的响应。 模拟老师和学生的一个场景:被观察者(老师)、观察者(学生)。

案例1.

观察者

public class MyObserver implements Observer {
    /**
     * 观察者(学生)name
     */
    private String name;
    public MyObserver(Observable o, String name) {
        o.addObserver(this);
        this.name = name;
    }
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("观察者(学生)" + name + "收到作业!《" + arg + "》"+"目标的观察者数量=" + o.countObservers());
    }
}

被观察者

public class MyObserverable extends Observable {
    //被观察者数据
    private String data;
    public String getData() {
        return data;
    }
    /**
     * 如果有如果改变
     * @param data
     */
    public void setData(String data) {
         //更改变化状态
         setChanged();
        //通知注册的观察者
        notifyObservers(data);
    }
}

测试

public static void main(String[] args) {
        //1.构造被观察目标。(假如现实场景中的老师)
        MyObserverable observerable = new MyObserverable();
        //2.构造2个观察者实现类:添加观察者进观察目标  (现实场景中的学生,每来一个新学生,要加入老师的观察者名录中)
        MyObserver observer1 = new MyObserver(observerable, "tom");
        MyObserver observer2 = new MyObserver(observerable, "jerry");
        //3.被观察者(老师)发布今天的作业任务。其注册的观察者们(学生们)响应。
        observerable.setData("Java从入门到放弃");
    }

结果:

被观察者已上线....
观察者(学生)jerry收到作业!《Java从入门到放弃》目标的观察者数量=2
观察者(学生)tom收到作业!《Java从入门到放弃》目标的观察者数量=2

通过代码看到,被观察者的 ==通知注册的观察者 notifyObservers(data);==,触发消息的通知给观察者们。观测者观察到有变化后,做出改变。==update(Observable o, Object arg) {}==.

Spring事件机制(事件监听机制)

通过上面的观察者模式,我们能够了解到,其中的设计模式及思想。监听者模式有异曲同工之处,监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后做出相应的处理。事件监听机制,其实从JDK 1.1开始有的设计模式,其主要的几个基类为 事件源EventObject 、监听者EventListener、发布者(Spring)ApplicationEventPublisher spring事件驱动模型 下面通过案例演示事件发布订阅。

案例2.

事件源: Spring的事件源为ApplicationEvent,继承至JDK提供的EventObject 基类。

public class MyContextEvent extends ApplicationEvent {
    public MyContextEvent(Object source) {
        super(source);
        System.out.println("source message->"+source.toString());
    }
}

监听者: Spring的监听者为ApplicationListener,继承至JDK提供的EventListener 接口。其实EventListener 中没有任何方法定义,只是作为监听者标识。

public class MyContextListener implements ApplicationListener<MyContextEvent> {
    @Override
    public void onApplicationEvent(MyContextEvent myContextEvent) {
        System.out.println("listener this MyContextEvent....");
    }
}

这里我们通过Spring容器的事件发布功能来实现,自动以事件的注册发布及监听。 在spring容器事件中AbstractApplicationContext继承至ConfigurableApplicationContext, ConfigurableApplicationContext类继承至ApplicationContext。IOC容器的核心接口ApplicationContext中继承了,事件发布ApplicationEventPublisher. 其子类AbstractApplicationContext中实现了父接口ApplicationEventPublisher中的publishEvent(ApplicationEvent event)方法。

//AbstractApplicationContext类中的publishEvent方法。
public void publishEvent(ApplicationEvent event) {
        this.publishEvent(event, (ResolvableType)null);
    }

测试

public static void main(String[] args) {
        //获取IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册监听者
        context.register(MyContextListener.class);
        //刷新容器
        context.refresh();
        //事件发布
        context.publishEvent(new MyContextEvent("publish this event ...."));
    }

结果

source message->publish this event ....
listener this MyContextEvent....

在IOC容器中,添自定义事件的监听者==context.register(MyContextListener.class);==、 事件发布通过发布自定义事件== context.publishEvent(new MyContextEvent("publish this event ...."));==自定义消息。监听者做出响应。

基于Springboot事件监听机制整合EventBus应用

前面将观察者模式以及spring的事件发布机制。通过案例代码的方式,介绍了一下。下面介绍一下基于观察者模式的一个落地实现,Google Guava的EventBus事件处理。EventBus无需实现复杂的事件、监听者、发布者。只需要通过提供的API来实现事件机制。Evnet是根据发布者发布的事件类型,监听者订阅此事件类型,来时间发布订阅模式。在使用EventBus时,我们只需将自己要发布的事件类型通过EventBus API中的public void post(Object event) 方法。监听者通过@Subscribe监听指定类型的事件。 springboot事件机制也是基于spring事件机制实现。spring boot中支持的事件类型定在org.springframework.boot.context.event包中,springboot的项目启动加载过程,是通过springboot事件触发器EventPublishingRunListener来完成。目前支持的事件类型有如下6种。

  1. ApplicationStartedEvent spring boot 启动监听类
  2. ApplicationEnvironmentPreparedEvent 环境事先准备,spring boot中的环境已经准备ok
  3. ApplicationPreparedEvent 上下文准备事件
  4. ApplicationReadyEvent 上下文已经准备ok
  5. ApplicationFailedEvent 该事件为spring boot启动失败时的操作(启动时出现异常事件)
  6. SpringApplicationEvent 获取SpringApplication 每一步加载过程通过触发不同的事件,来完成相应的操作。这里不一一赘述,springboot的事件。感兴趣的同学可以通过翻看启动类SpringApplication.run(CommonApplication.class, args)SpringApplication类中的run方法来研究整个启动过程,做了哪些工作,网上的这部分的讲解也是一大堆,贴一张springboot的启动流程。 springboot启动流程 EventBus API的调用实现起来并不复杂,下面通过一个实际项目中的运用,结合springboot的事件发布机制,来整合基于事件驱动的项目案例。首先要清楚前面所将的Spring的事件发布机制,通过Spring的事件发布机制,整合EventBus。

思想思路: 1.通过Springboot的事件加载机制,监听ApplicationPreparedEvent上下文准备事件,在上下文准备事件时,将监听这一事件的监听者(实现spring中的ApplicationListener),添加至EventBus的事件监听者中(即 Event的监听者注册register(Object handler))。 2.添加订阅者(即实现ApplicationListener监听者)的监听者。通过注解@Subscrib实现监听。 3.事件发布。调用EventBus中的事件发布API post(Object event).发布事件。 4.订阅者响应。

案例3.

Maven 引用相应Jar包 EventBus 事件总线

//api封装
public class MyEventBus {

    /** 事件任务总线 */
    private final static EventBus tiemEventBus = new EventBus();
    /**
     * 触发同步事件
     *
     * @param event
     */
    public static void post(Object event) {
        tiemEventBus.post(event);
    }
    /**
     * 注册事件处理器
     *
     * @param handler
     */
    public static void register(Object handler) {
        tiemEventBus.register(handler);
    }
    /**
     * 注销事件处理器
     *
     * @param handler
     */
    public static void unregister(Object handler) {
        tiemEventBus.unregister(handler);
    }
}

消息实体类

public class Message {
    private MessageType messageType;
    private String messageContent;

    public Message(MessageType messageType, String messageContent) {
        this.messageType = messageType;
        this.messageContent = messageContent;
    }
    public MessageType getMessageType() {
        return messageType;
    }
    public void setMessageType(MessageType messageType) {
        this.messageType = messageType;
    }
    public String getMessageContent() {
        return messageContent;
    }
    public void setMessageContent(String messageContent) {
        this.messageContent = messageContent;
    }

    public enum MessageType {
        OPENDOOR(1, "openDoor"),
        CLOSEDOOR(2,"closeDoor");
        private int code;
        private String value;

        MessageType(int code, String value) {
            this.code = code;
            this.value = value;
        }
        public int getCode() {
            return code;
        }
        public void setCode(int code) {
            this.code = code;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        }
}

事件监听者 监听ApplicationPreparedEvent上下文准备事件,即springboot加载至这一步时,将此监听者注册到EventBus中。通过抽象封装的方式,后继承的子类通过实现此类,实现订阅者的eventBus注册。

@Component
abstract class MyApplicationListener implements ApplicationListener<ApplicationPreparedEvent> {
    /**
     *  ApplicationPreparedEvent 上下文准备事件
     * @param applicationPreparedEvent
     */
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {
        ConfigurableApplicationContext applicationContext = applicationPreparedEvent.getApplicationContext();
        MyApplicationListener bean = applicationContext.getBean(this.getClass());
        System.out.println("regist listener to eventBus...."+bean);
        MyEventBus.register(bean);
    }
}

订阅者(也即监听者)继承至MyApplicationListener。

@Component
public class MyListentenerSubscribe extends MyApplicationListener{
    @Subscribe
    public void on(Message message){
        System.out.println("subscribe message->  messgeType:"+message.getMessageType()+"\n messageContent:"+message.getMessageContent());
    }
}

测试 这里因为是案例实现,直接在Controller实现时间的发布。真实的业务中,通业务层,具体业务触发的事件的发布。 我通过发布User

@RestController
public class EventPublishCtrl extends LogBase {
    @GetMapping("/publish")
    public void publishEvent() {
        log.info("this publish method...");
        MyEventBus.post(new Message(Message.MessageType.OPENDOOR,"芝麻开门!"));
    }
}

启动输出

2019-08-22 14:39:15.811 WARN 73026 --- [ main] com.netflix.discovery.DiscoveryClient : Using default backup registry implementation which does not do anything. 2019-08-22 14:39:15.813 INFO 73026 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30 2019-08-22 14:39:15.814 INFO 73026 --- [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4 2019-08-22 14:39:15.818 INFO 73026 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1566455955817 with initial instances count: 0 2019-08-22 14:39:15.820 INFO 73026 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application UNKNOWN with eureka with status UP 2019-08-22 14:39:15.820 INFO 73026 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1566455955820, current=UP, previous=STARTING] 2019-08-22 14:39:15.822 INFO 73026 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/kafka1:9090: registering service... regist listener to eventBus....com.uniubi.springcloud.practice.eventBus.MyListentenerSubscribe@329a1f8d 2019-08-22 14:39:15.914 INFO 73026 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path '' 2019-08-22 14:39:15.915 INFO 73026 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 9090 2019-08-22 14:39:15.917 INFO 73026 --- [ main] c.u.s.practice.PracticeApplication : Started PracticeApplication in 4.101 seconds (JVM running for 4.895) 2019-08-22 14:39:16.761 INFO 73026 --- [-192.168.63.121] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'

项目的启动加载,注册相应的监听者至eventBus中。 测试事件发布

2019-08-22 14:43:44.399  INFO 73026 --- [nio-9090-exec-1] c.u.springcloud.practice.common.LogBase  : this publish method...
subscribe message->  messgeType:OPENDOOR
 messageContent:芝麻开门!

订阅者收到具体的消息类型,以及消息内容。 总结

springBoot的底层实现,很多都是基于事件的发布订阅模式来做的。我们日常的开发过程中,做到业务的解耦,消息的异步传输。都可以通过实现发布订阅的模式来实现。这里也是实现项目中基于事件驱动,来完成核心业务的解耦。

  • 文中的图片来自网络。如有侵权行为联系作者。