天天看点

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

观察者模式

概念其实很简单,两个主体,一个观察者,一个被观察者,当被观察者发生变化时,观察者会有相应的动作。举几个例子,和我们日常生活息息相关的红绿灯,灯就相当于被观察者,行人就相当于观察者,当灯发生变化时,行人会有相应的动作:红灯停,绿灯行,黄灯亮了等一等。再比如我们现在玩的公众号,当我们订阅了某个公众号之后,公众号每发表一篇文章,就会向订阅了它的用户发送这篇文章,我们就可以浏览这篇文章了;当我们取消订阅了,它就不会再向我们推送这篇文章了;只要这个公众号一直在运行,就会一直有人订阅它或者取消订阅。这两个主体有个统一的称呼:被观察者成为主题(Subject),观察者仍是称为观察者(Observer)。

观察者模式还有很多其他的称谓,如发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

理论上的东西讲的再多也只是停留在理论,下面我们来实现下,到底观察者模式是个什么神奇的东西。

类图

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

所涉及到的角色如下:

抽象主题(Subject):提供接口,可以增加和剔除观察者对象。一般用抽象类或者接口实现。      

抽象观察者(Observer):提供接口,在得到主题的通知时更新自己。一般用抽象类或者接口实现。      

具体主题(ConcreteSubject):将有关状态存入具体观察者,在具体主题的内部状态发生变化时,给所有注册过的观察者发出通知。一般是具体子类实现。      

具体观察者(ConcreteObserver):存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用

在上述类图中,ConcreteSubject中有一个存储Observer的列表,这意味着ConcreteSubject并不需要知道引用了哪些ConcreteObserver,只要实现(继承)了Observer的对象都可以存到该列表中。在需要的时候调用Observer的update方法。

设计模式面试真题解析地址:设计模式面试题 14道

一般实现

Subject:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

Observer:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

ConcreteSubject:

package com.lee.myobserver.impl;

import com.lee.myobserver.Observer;
import com.lee.myobserver.Subject;

import java.util.ArrayList;
import java.util.List;

/**
 * 具体主题
 */
public class ConcreteSubject implements Subject {

    private List<Observer> observerList = new ArrayList<>();
    private String state;

    @Override
    public void attach(Observer observer) {
        this.observerList.add(observer);
        System.out.println("向ConcreteSubject注册了一个观察者");
    }

    @Override
    public void detach(Observer observer) {
        this.observerList.remove(observer);
        System.out.println("从ConcreteSubject移除了一个观察者");
    }

    @Override
    public void notifyObservers() {
        this.observerList.forEach(observer -> observer.update(this.state));
    }

    public void changeState(String state) {
        this.state = state;
        this.notifyObservers();
    }
}           

复制

ConcreteObserver:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

MyObserverTest:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

完整代码可以从spring-boot-test获取,在com.lee.myobserver下

运行结果如下:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

以上实现中,我们发现ConcreteSubject必须维护一个Observer列表,这会让人产生疑问:难道每个ConcreteSubject中的List<Observer>会有不同吗?很明显,不会,因为List保存的类型是接口类型,那么我们是不是可以把这个维护列表放到Subject中去了?还有我们发现attach、detach、notifyObservers在各个ConcreteSubject的实现都是一样的,那么我们是不是可以共用起来呢?答案是肯定的!那么我们怎么处理了,只需要将Subject改成抽象类即可,类图如下,具体实现就交给大家自己了,有了这个类图,相信大家都可以轻松的完成代码的实现。

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

jdk实现

在Java语言的java.util包下,提供了一个Observable类以及一个Observer接口,构成Java语言对观察者模式的支持。

Observable:

package java.util;

/**
 * 抽象主题,用普通类实现
 */
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    
    /**
     * 构建一个含有0个观察者的主题
     */
    public Observable() {
        obs = new Vector<>();
    }

    /**
     * 注册某个观察者到obs中
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * 从obs中移除某个观察者
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * 相当于notifyObservers(null),具体看下面那个
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * 如果本对象有变化,则通知所有注册了的观察者,调用他们的update方法
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

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

    /**
     * 清空obs
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * 将changed设置成true,标明本对象发生了变化
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 将changed重置成false
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 检测本对象是否发生了变化
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * 返回注册的观察者数量
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}           

复制

Observer:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

Watched:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

Watcher:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

JdkObserverTest:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

完整代码可以从spring-boot-test获取,在com.lee.jdkobserver下

运行结果如下

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

类图如下

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

jdk事件

JDK 1.0及更早版本的事件模型基于职责链模式,但是这种模型不适用于复杂的系统,因此在JDK 1.1及以后的各个版本中,事件处理模型采用基于观察者模式的委派事件模型(DelegationEvent Model, DEM),即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理,而是委派给独立的事件处理对象负责。这并不是说事件模型是基于Observer和Observable的,事件模型与Observer和Observable没有任何关系,Observer和Observable只是观察者模式的一种实现而已。

java中的事件机制的参与者有3种角色

Event Eource:事件源,发起事件的主体。

Event Object:事件状态对象,传递的信息载体,就好比Watcher的update方法的参数,可以是事件源本身,一般作为参数存在于listerner 的方法之中。

Event Listener:事件监听器,当它监听到event object产生的时候,它就调用相应的方法,进行处理。

其实还有个东西比较重要:事件环境,在这个环境中,可以添加事件监听器,可以产生事件,可以触发事件监听器。

限于篇幅,具体案例实现就不讲了,大家可以去spring-boot-test获取,在com.lee.jdkevent下,里面注释写的很详细了,大家可以好好看看。

spring事件机制

springboot没有自己的事件机制,用的就是spring的事件机制,这里希望大家别以为和标题不一致。springboot和spring的关系大家可以去捋一捋,这里可以明确的告诉大家,不是对立关系!

spring的事件机制也是从java的事件机制拓展而来,具体往下看

ApplicationEvent:Spring中所有的事件父接口,继承自java的EventObject

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {

    /** use serialVersionUID from Spring 1.2 for interoperability */
    private static final long serialVersionUID = 7099057708183571937L;

    /** System time when the event happened */
    private final long timestamp;


    /**
     * Create a new ApplicationEvent.
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
     * Return the system time in milliseconds when the event happened.
     */
    public final long getTimestamp() {
        return this.timestamp;
    }

}           

复制

ApplicationListener:spring中所有的事件监听器父接口,继承自java的EventListener

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventListener;

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type
 * that it is interested in. When registered with a Spring ApplicationContext, events
 * will be filtered accordingly, with the listener getting invoked for matching event
 * objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific ApplicationEvent subclass to listen to
 * @see org.springframework.context.event.ApplicationEventMulticaster
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}           

复制

具体案例

MessageEvent:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

MessageListener:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

MessageService:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

spring-event.xml:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

SpringEventTest:

同城旅行面试官:说说设计模式中事件底层原理!观察者模式类图一般实现jdk实现jdk事件spring事件机制具体案例总结观察者模式优点与缺点观察者模式应用场景事件机制

执行SpringEventTest中的main方法就可以看到结果了。

更多详情请从spring-boot-test获取,在com.lee.springevent下,大家可以根据注释好好消化下。实在是不懂得话,可以放一下,下篇博文我会和大家一起着重研究下spring的事件源码,后续的博文中也都会有提到。

总结

为什么讲与springboot启动源码无关的内容

关于前言中的疑问:为什么讲一篇与spring启动源码无关的内容,有两个考虑,第一,确实是让大家放松下心态,读源码确实挺累的;第二,主要目的,就是为spring-boot-2.0.3启动源码篇二 - SpringApplication的run方法(一)之

SpringApplicationRunListener做准备,里面会涉及到spring的事件机制

观察者模式优点与缺点

上面长篇大论,似乎也没讨论其优点与缺点;其实通过实现大家应该才能体会到其优点与缺点,我总结下,有什么不对的大家可以在评论区补充

优点:

(1) 主题与观察者建立一个抽象的耦合而不是紧密的耦合,降低了耦合度;主题只需要维护一个抽象观察者的集合,无需了解具体观察者,使得可以有各种各样不同的观察者实现。

(2) 支持广播通信,主题会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。

(3) 符合“开闭原则”,增加新的具体观察者无须修改原有代码,可拓展性高。

缺点:

(1) 如果主题有很多直接或者间接观察者,那么全部通知到会很耗时。

(2) 主题与观察者之间如果存在循环依赖,可能导致系统崩溃。

观察者模式应用场景

抽象的来讲:对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变;对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。大家可以根据以上两点作为基本准则,某个场景是否满足观察者模式。

更多Java资料共享可以关注公众号:麒麟改bug获取!

具体应用场景就有很多了,比如文中的事件机制、公众号订阅,tomcat源码中也有很多地方用到了(有兴趣的兄弟可以去找找)。

事件机制

jdk事件实现是基于观察者模式,而spring事件又是在jdk事件的基础上进行了拓展。

主要有四个角色

事件源:触发事件的主体,比如jdk事件案例中的UserService、spring事件案例中的MessageService、SpringBoot中的SpringApplication。

事件:事件本身,指的是EventObject中的source,具体可以是任何数据(包括事件源),用来传递数据,比如jdk事件案例中MessageEvent、spring事件案例中的MessageEvent。

事件监听器:当事件发生时,负责对事件的处理,比如jdk事件案例中MessageListener、spring事件案例中的MessageListener。

事件环境:整个事件所处的上下文,对整个事件提供支持,类似web上下文;比如jdk事件案例中的UserService、spring事件案例中的ApplicationContext