likes
comments
collection
share

【重写SpringFramework】Spring事件机制上(chapter 3-11)观察者模式是常用的设计模式之一,

作者站长头像
站长
· 阅读数 18

1. 前言

上一节我们讨论了生命周期管理的问题,无论是 Bean 的生命周期还是 Lifecycle 机制都是由 Spring 容器驱动的。它们有三个特点,一是在特定时间点执行具有一定语义的操作,比如初始化和销毁。二是这些操作是容器到组件的单向通知。第三点,容器直接调用组件的相关方法,两者的耦合程度太高

Spring 提供了事件机制,解决了上述的三个痛点。首先,执行时机不受限制,允许在任意时间点发送事件。其次,事件既可以是容器到组件的通信,也可以是组件之间的通信。三是将容器与组件进行解耦,容器只管发送事件即可,监听器组件会侦测到这些事件,并进行相应的处理。

2. 观察者模式

2.1 概述

观察者模式定义了一种一对多的依赖关系,让多个观察者对象监听同一个主题对象(被观察者),当主题对象的状态发生变化时,会通知所有的观察者对象做出反应。观察者模式实现了主题对象与观察者对象之间的解耦,主题对象只需要发送事件,而观察者只关注如何处理事件,双方并不知道对方的存在。观察者模式由以下四个角色构成,简单介绍如下:

  • Subject 接口定义了管理观察者的方法 addObserverremoveObserver,以及通知所有观察者的方法 notifyObservers

  • ConcreteSubject 持有一个观察者的集合 observers,并实现了 Subject 接口的相关方法。

  • Observer 接口代表一个观察者对象,update 方法定义了观察者应该如何处理事件

  • ConcreteObserver 代表一个具体的观察者对象,需要实现 update 方法的具体逻辑

【重写SpringFramework】Spring事件机制上(chapter 3-11)观察者模式是常用的设计模式之一,

2.2 订阅发布模式

Java 提供了观察者模式的简单实现,主要涉及 java.util 包中的 Observer 接口和 Observable 类。其中 Observer 接口表示一个观察者对象,当被观察者对象的状态发生变化时,会调用观察者的 update 方法进行更新。

public interface Observer {
    void update(Observable o, Object arg);
}

Observable 类代表一个被观察对象,有时也称为主题对象。一个被观察对象可以有数个观察者对象,每个观察者对象都是 Observer 接口的实现类。当被观察对象发生变化时,会调用 notifyObservers 方法,通知所有观察者调用 update 方法进行更新。此外,addObserverremoveObserver 等方法是用来管理观察者的。

public class Observable {
    private Vector<Observer> obs;        //观察者列表

    //通知观察者
    public void notifyObservers(Object arg) {
        Object[] arrLocal = obs.toArray();

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    //添加一个观察者
    public synchronized void addObserver(Observer o) {
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
}

所谓的简单实现包括两方面,一是结构简化,将抽象主题角色与具体主题角色合并为一个 Observable 类,二是使用场景比较简单,所有的观察者都订阅同一个主题,主题对象适时地发布事件,并通知所有已订阅的观察者。需要注意的是,「通知所有观察者」这一行为说明,一个主题只发布一种事件,否则就要对所有的观察者进行筛选。

举个例子,张三订了一份报纸,每天早上都会收到一份报纸。如果李四也订报纸,同样会收到一份报纸。我们把报纸看成订阅关系中传递的数据,就会发现张三和李四其实并不关心数据的类型,因为他们肯定会收到一份报纸,而不是一瓶牛奶。除非他们又订了牛奶,但这是新的订阅关系了。由于一个订阅关系只处理一种事情,不会出现张三订报纸却收到牛奶,或者李四订牛奶却收到报纸的乌龙。因此这种单一事件的观察者模式也称为订阅发布模式

2.3 监听器模式

此外,Java 还提供了基于多事件的观察者模式,我们称之为监听器模式。其中 EventListener 是一个标记接口,所有的监听器类都必须实现该接口。EventObject 类表示一个事件对象,source 字段表示事件源,也就是产生事件的载体。具体的事件类应继承 EventObject,可以携带不同类型的参数。

public class EventObject implements java.io.Serializable {
    private static final long serialVersionUID = 5516075349620653480L;
    protected transient Object  source;

    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    public Object getSource() {
        return source;
    }
}

监听器模式有两个特点,一是对事件的类型进行区分,主题对象可以发送多种类型的事件,监听器只处理感兴趣的事件即可。二是没有定义主题(被观察者)相关的接口,需要用户自行实现主题对象。将两种模式进行对比,订阅发布模式是所有人一起吃大锅饭,而监听器模式则是每个人都可以点小炒。在实际应用中,我们要根据具体的情况来选择合适的模式。

3. Spring 事件机制

3.1 概述

Spring 的事件机制是基于监听器模式的,从而拥有了处理多事件的能力。事件相关的类图可以分为三部分,黄色区域是监听器模式的核心实现,红色区域是监听器的具体构成,紫色区域是ApplicationContext 与事件的整合。我们将分别对这三部分进行介绍,一步一步构建 Spring 事件机制的轮廓。

注:紫色区域 Spring 的整合放在下一节,本节的目标是实现基本的事件流程。

【重写SpringFramework】Spring事件机制上(chapter 3-11)观察者模式是常用的设计模式之一,

3.2 核心部分

ApplicationEvent 表示事件对象,所有的 Spring 事件类都要继承该类。Spring 内置了很多事件类,比如 ApplicationContextEvent 是用于处理 Spring 上下文相关的事件,还可以细分为 ContextStartedEventContextRefreshedEventContextClosedEventContextStoppedEvent等,每个事件类都代表着不同的运行时刻。

public class ApplicationEvent extends EventObject {
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
}

ApplicationListener 接口表示监听器对象,也就是观察者。onApplicationEvent 方法定义了处理事件的逻辑,参数 event 是一个泛型类型,且必须是 ApplicationEvent 的子类。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    //处理事件
    void onApplicationEvent(E event);
}

在监听器模式中,Java 没有提供主题角色的接口,因而 Spring 使用 ApplicationEventMulticaster 接口作为主题角色。接口名可以直译为「应用事件多播器」,所谓多播是指发布一个事件可能被多个监听器捕获。ApplicationEventMulticaster 接口定义了两种方法,其中 multicastEvent 方法用于通知监听器处理事件,addApplicationListener方法用于添加监听器(其余管理监听器的方法略)。

public interface ApplicationEventMulticaster {
    //添加监听器
    void addApplicationListener(ApplicationListener<?> listener);
    //发布多播事件
    void multicastEvent(ApplicationEvent event);
}

综上所述,观察者模式的三要素已齐备。ApplicationEventMulticaster 是事件源(被观察者),ApplicationListener 是观察者,ApplicationEvent 是事件对象,由事件源和数据组成。

3.3 监听器

对于用户(开发者)来说,直接继承 ApplicationListener 接口即可,GenericApplicationListener 接口及其子类实际上是供 Spring 内部使用的。GenericApplicationListener 接口的作用是判断监听器是否可以处理指定的事件,也就是说当前监听器对哪类事件感兴趣。

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent> {
    //是否支持指定的事件类型
    boolean supportsEventType(ResolvableType eventType);
}

Spring 对于同一个功能通常会提供两种方式,即编程式和声明式,比较典型的是通过 FactoryBean 接口和 @Bean 注解实现工厂方法。事件监听器也是一样,既可以实现 ApplicationListener 接口,也可以使用声明了 @EventListener 注解的方法。对于这两种不同的实现方式,Spring 使用两个适配器子类来分别处理。

  • ApplicationListenerMethodAdapter:对事件方法进行适配,即处理声明 @EventListener 注解的方法
  • GenericApplicationListenerAdapter:对监听器类进行适配,即处理 ApplicationListener 接口的实现类

4. 事件多播器

4.1 主要属性

SimpleApplicationEventMulticasterApplicationEventMulticaster 接口的简单实现,allListeners 字段存储了所有的监听器,添加监听器的工作是由 Spring 容器完成的,我们将在下节 Spring 容器整合事件功能中讲解。retrieverCache 字段是一个按事件类型进行分类的缓存,由于同一个事件可能存在多个感兴趣的监听器,缓存起来以便重复使用。

public class SimpleApplicationEventMulticaster implements ApplicationEventMulticaster {
    //所有的监听器集合
    private final Set<ApplicationListener<?>> allListeners = new LinkedHashSet<>();
    //按事件类型分类的监听器列表缓存
    private final Map<ListenerCacheKey, Set<ApplicationListener<?>>> retrieverCache = new ConcurrentHashMap<>(64);
}

4.2 发布事件

SimpleApplicationEventMulticaster 实现了 multicastEvent 方法。首先获取可以处理指定事件类型的监听器列表,然后遍历整个列表,依次执行每个监听器的回调。监听器的回调是由具体的监听器类来实现的,我们关心的是如何获取与当前事件的类型相匹配的监听器列表,也就是说关键在于 getApplicationListeners 方法的实现。

@Override
public void multicastEvent(final ApplicationEvent event) {
    ResolvableType type = ResolvableType.forInstance(event);
    for (final ApplicationListener listener : getApplicationListeners(event, type)) {
        listener.onApplicationEvent(event);
    }
}

第一步,尝试从缓存中查找。发布事件是一个频繁执行的操作,而每种事件类型对应的监听器列表在应用初始化之后是固定的,因此 getApplicationListeners 方法的主要工作是与缓存打交道。先尝试从缓存中查找对应的监听器列表,如果没有找到,则需要进一步调用 retrieveApplicationListeners 方法。该方法只执行一次,以后再调用 getApplicationListeners 方法直接从缓存中获取,以此提高效率。

protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
    Object source = event.getSource();
    Class<?> sourceType = (source != null ? source.getClass() : null);
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

    //尝试从缓存中获取指定事件的监听器列表
    Set<ApplicationListener<?>> listeners = this.retrieverCache.get(cacheKey);
    if (listeners != null) {
        return listeners;
    }

    synchronized (this.retrievalMutex) {
        listeners = retrieveApplicationListeners(eventType, sourceType);
        this.retrieverCache.put(cacheKey, listeners);
        return listeners;
    }
}

第二步,遍历所有的监听器。allListeners 字段存储了所有已注册的监听器,依次检查每个监听器,将符合条件的项加入集合中。supportsEvent 方法封装了判断逻辑的细节,分别对两种监听器的实现方式进行处理。

  • 监听器方法:实际类型为 ApplicationListenerMethodAdapter 对象,直接强转即可
  • 监听器类:实现了 ApplicationListener 接口,需要包装成 GenericApplicationListenerAdapter 对象
private Set<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, Class<?> sourceType) {
    Set<ApplicationListener<?>> listeners = new LinkedHashSet<>();
    for (ApplicationListener<?> listener : allListeners) {
        if(supportsEvent(listener, eventType, sourceType)){
            listeners.add(listener);
        }
    }
    return listeners;
}


protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
    //1) ApplicationListenerMethodAdapter 是 GenericApplicationListener 接口的直接子类,表示监听方法
    //2) 否则 listener 是一个监听器类,需要使用 GenericApplicationListenerAdapter 来包装
    GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
                                                (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
    return smartListener.supportsEventType(eventType);
}

第三步,对两种类型的监听器对象进行检查。先来看监听器类,在 GenericApplicationListenerAdapter 类中,declaredEventType 字段表示监听器类的泛型类型,方法入参 eventType 表示事件对象的类型。如果事件对象的类型是监听器类的泛型的子类型,则表示该监听器可以处理该事件。比如 FooListener<FooEvent> 监听器类,可以处理 FooEvent 类型的事件。

public class GenericApplicationListenerAdapter implements GenericApplicationListener {
    private final ResolvableType declaredEventType;

    @Override
    public boolean supportsEventType(ResolvableType eventType) {
        return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
    }
}

对于监听器方法来说,declaredEventTypes 字段存储了支持的事件类型列表。该字段是在 ApplicationListenerMethodAdapter 的构造方法中初始化的,实际上解析了 @EventListener 注解的 value 属性。比如 @EventListener(AEvent.class, BEvent.class) 声明在某个方法上,那么 declaredEventTypes 字段包括 AEventBEvent。可以看到,监听器类只支持单一的事件类型,而监听器方法可以同时支持多种事件

注:本节我们不涉及事件方法的具体实现,仅介绍与发布事件有关的逻辑。

//本节不实现,仅介绍判断逻辑
public class ApplicationListenerMethodAdapter implements GenericApplicationListener {
    private final List<ResolvableType> declaredEventTypes;

    @Override
    public boolean supportsEventType(ResolvableType eventType) {
        for (ResolvableType declaredEventType : this.declaredEventTypes) {
            if (declaredEventType.isAssignableFrom(eventType)) {
                return true;
            }
        }
        return eventType.hasUnresolvableGenerics();
    }
}

5. 测试

5.1 订阅发布模式

本测试的目的是验证 Java 的订阅发布模式,虽然不是我们关心的重点,但有必要对其有所了解。首先定义两组测试类,NewsObservable 是被观察者对象,负责向订阅报纸的用户派发报纸。NewsObserver 是观察者对象,当他收到报纸时,就开始读报。此外,MilkObservableMilkObserver 的相关代码略。

//测试类
public class NewsObservable extends Observable {

    public void delivery(){
        setChanged();
        notifyObservers("报纸");
    }
}

public class NewsObserver implements Observer {
    private String name;

    public NewsObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + " 读 " + arg);
    }
}

在测试方法中,张三和李四都订了一份报纸,当执行 NewsObservabledelivery 方法时,张三和李四会收到报纸,然后开始读报。仔细观察NewsObserver 类的 update 方法的实现,并没有对参数 arg 进行额外的处理,因为所有的订阅者都知道,他们会收到一份报纸,而不是一瓶牛奶。同样地,在第二组订阅关系中,张三和王五会收到一瓶牛奶。

//测试方法
@Test
public void testObserver() {
    //订报纸
    NewsObservable no = new NewsObservable();
    no.addObserver(new NewsObserver("张三"));
    no.addObserver(new NewsObserver("李四"));
    no.delivery();

    //订牛奶
    MilkObservable mo = new MilkObservable();
    mo.addObserver(new MilkObserver("张三"));
    mo.addObserver(new MilkObserver("王五"));
    mo.delivery();
}

从测试结果可以看到,第一组订阅的用户收到了报纸,第二组订阅的用户收到了牛奶。

李四 读 报纸
张三 读 报纸
王五 喝 牛奶
张三 喝 牛奶

5.2 监听器模式

首先定义两组事件和监听器,分别表示订阅报纸和牛奶。NewsListener 感兴趣的事件类型是 NewsEventMilkListener 感兴趣的事件类型是 MilkEvent(代码略)。

//测试类
public class NewsListener implements ApplicationListener<NewsEvent> {
    private String name;

    public NewsListener(String name) {
        this.name = name;
    }

    @Override
    public void onApplicationEvent(NewsEvent event) {
        System.out.println(name + "读 " + event.getSource());
    }
}

public class NewsEvent extends ApplicationEvent {
    public NewsEvent(Object source) {
        super(source);
    }
}

在测试方法中,我们给张三订阅了报纸,给李四订阅了报纸和牛奶。当发送 NewsEventMilkEvent 事件时,张三只能读报纸,而李四可以一边读报纸,一边喝牛奶。

//测试方法
@Test
public void testEventMulticaster(){
    ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
    multicaster.addApplicationListener(new NewsListener("张三"));
    multicaster.addApplicationListener(new NewsListener("李四"));
    multicaster.addApplicationListener(new MilkListener("李四"));

    multicaster.multicastEvent(new NewsEvent("《人民日报》"));
    multicaster.multicastEvent(new MilkEvent("光明牛奶"));
}

从测试结果可以看到,监听器模式支持发布多种事件,每个观察者都可以监听感兴趣的事件,拥有极大的灵活性。

张三读 《人民日报》
李四读 《人民日报》
李四喝 光明牛奶

6. 总结

本节初步讨论了 Spring 框架的事件机制,而事件机制是基于观察者模式实现的。观察者模式可以分为两类,一是订阅发布模式,二是监听器模式。两者的区别在于,订阅发布模式只能处理一种事件,监听器模式则可以同时处理多种事件。Spring 的事件机制是基于监听器模式实现的,使用的范围更广泛。监听器模式需要实现三个基本组件:

  • 事件对象:ApplicationEvent 扩展自 EventObject,代表事件在传播过程中携带的数据。
  • 监听器:ApplicationListener 接口代表观察者角色,作用是对发生的事件进行响应。
  • 事件多播器:ApplicationEventMulticaster 接口代表主题角色,持有一组监听器,负责发布事件。

【重写SpringFramework】Spring事件机制上(chapter 3-11)观察者模式是常用的设计模式之一,

Spring 提供了两种监听器实现,一种是编程式,通过实现 ApplicationListener 接口,称之为监听器类。一种是声明式,将 @EventListener 注解声明在方法上,称为监听器方法。事件多播器对这两种类型的监听器进行了适配,其中监听器类被包装成 GenericApplicationListenerAdapter 对象,监听器方法被包装成 ApplicationListenerMethodAdapter 对象。本节我们仅实现了监听器类的处理,有关监听器方法的实现将在下节介绍。

7. 项目信息

新增修改一览,新增(15),修改(0)。

context
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.spring.context
   │        └─ event
   │           ├─ ApplicationEvent.java (+)
   │           ├─ ApplicationEventMulticaster.java (+)
   │           ├─ ApplicationListener.java (+)
   │           ├─ GenericApplicationListener.java (+)
   │           ├─ GenericApplicationListenerAdapter.java (+)
   │           └─ SimpleApplicationEventMulticaster.java (+)
   └─ test
      └─ java
         └─ context
            └─ event
               ├─ EventTest.java (+)
               ├─ MilkEvent.java (+)
               ├─ MilkListener.java (+)
               ├─ MilkObservable.java (+)
               ├─ MilkObserver.java (+)
               ├─ NewsEvent.java (+)
               ├─ NewsListener.java (+)
               ├─ NewsObservable.java (+)
               └─ NewsObserver.java (+)

注:+号表示新增、*号表示修改

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。

转载自:https://juejin.cn/post/7418462670418559027
评论
请登录