設計模式之觀察者模式,事件機制的底層原理

前言

       原本打算這篇繼續和你們一塊兒討論springboot啓動源碼的,可以爲再講源碼估計你們都沒有看下去的勇氣了,那麼今天,咱們不講springboot的啓動源碼,咱們先來看看一個有趣的內容,具體是什麼,你們應該已經知道了,沒錯就是標題中的 – spring-boot事件。html

       可能有小夥伴以爲奇怪了,好好的源碼系列不講了,怎麼忽然講一個可有可無的內容呢?那麼真的是可有可無的內容嗎?關於這個疑問後面會有解答。目前你們就權當放鬆了,以一種輕鬆的心態和我一塊兒往下看。java

觀察者模式

  說好的不是講springboot的事件機制嗎,怎麼又講什麼觀察者模式呢?內心會說:「樓主,你還好嗎,你今天是否是被門夾了?」。樓主:「被門夾?不存在的,通常只有我夾門......」git

  仍是那就話,你們放鬆心態慢慢看,心中的疑問先放在內心或者用筆記錄下來,後面會慢慢解開的。web

  概念其實很簡單,兩個主體,一個觀察者,一個被觀察者,當被觀察者發生變化時,觀察者會有相應的動做。舉幾個例子,和咱們平常生活息息相關的紅綠燈,燈就至關於被觀察者,行人就至關於觀察者,當燈發生變化時,行人會有相應的動做:紅燈停,綠燈行,黃燈亮了等一等。再好比咱們如今玩的公衆號,當咱們訂閱了某個公衆號以後,公衆號每發表一篇文章,就會向訂閱了它的用戶發送這篇文章,咱們就能夠瀏覽這篇文章了;當咱們取消訂閱了,它就不會再向咱們推送這篇文章了;只要這個公衆號一直在運行,就會一直有人訂閱它或者取消訂閱。這兩個主體有個統一的稱呼:被觀察者成爲主題(Subject),觀察者還是稱爲觀察者(Observer)。spring

  觀察者模式還有不少其餘的稱謂,如發佈-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知全部觀察者對象,使它們可以自動更新本身。express

  理論上的東西講的再多也只是停留在理論,下面咱們來實現下,到底觀察者模式是個什麼神奇的東西。apache

  類圖

    所涉及到的角色以下:設計模式

      抽象主題(Subject):提供接口,能夠增長和剔除觀察者對象。通常用抽象類或者接口實現。
      抽象觀察者(Observer):提供接口,在獲得主題的通知時更新本身。通常用抽象類或者接口實現。
      具體主題(ConcreteSubject):將有關狀態存入具體觀察者,在具體主題的內部狀態發生變化時,給全部註冊過的觀察者發出通知。通常是具體子類實現。
      具體觀察者(ConcreteObserver):存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使自己的狀態與主題的狀態 像協調。若是須要,具體觀察者角色能夠保持一個指向具體主題對象的引用tomcat

    在上述類圖中,ConcreteSubject中有一個存儲Observer的列表,這意味着ConcreteSubject並不須要知道引用了哪些ConcreteObserver,只要實現(繼承)了Observer的對象均可以存到該列表中。在須要的時候調用Observer的update方法。springboot

  通常實現

    Subject:

package com.lee.myobserver;


/**
 * 抽象主題
 * 提供具體主題須要實現的接口
 */
public interface Subject {

    /**
     * 註冊觀察者
     * @param observer
     */
    void attach(Observer observer);

    /**
     *  移除觀察者
     *  @param observer
     */
    void detach(Observer observer);

    /**
     * 通知全部註冊的觀察者
     */
    void notifyObservers();
}
View Code

    Observer:

package com.lee.myobserver;

/**
 * 抽象觀察者
 * 提供具體觀察者須要實現的接口
 */
public interface Observer {

    /**
     *
     */
    void update(String state);
}
View Code

    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();
    }
}
View Code

    ConcreteObserver:

package com.lee.myobserver.impl;

import com.lee.myobserver.Observer;

/**
 * 具體觀察者
 */
public class ConcreteObserver implements Observer {

    @Override
    public void update(String state) {
        System.out.println("我被通知了,我要改變狀態爲:" + state);
    }
}
View Code

    MyObserverTest:

package com.lee.test;

import com.lee.myobserver.Observer;
import com.lee.myobserver.impl.ConcreteObserver;
import com.lee.myobserver.impl.ConcreteSubject;

public class MyObserverTest {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer = new ConcreteObserver();
        subject.attach(observer);
        subject.changeState("new state");
    }
}
View Code

    完整代碼能夠從spring-boot-test獲取,在com.lee.myobserver下

    運行結果以下:

    以上實現中,咱們發現ConcreteSubject必須維護一個Observer列表,這會讓人產生疑問:難道每一個ConcreteSubject中的List<Observer>會有不一樣嗎?很明顯,不會,由於List保存的類型是接口類型,那麼咱們是否是能夠把這個維護列表放到Subject中去了?還有咱們發現attach、detach、notifyObservers在各個ConcreteSubject的實現都是同樣的,那麼咱們是否是能夠共用起來呢?答案是確定的!那麼咱們怎麼處理了,只須要將Subject改爲抽象類便可,類圖以下,具體實現就交給你們本身了,有了這個類圖,相信你們均可以輕鬆的完成代碼的實現。

  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();
    }
}
View Code

    Observer:

package java.util;

/**
 * 抽象觀察者,接口實現
 */
public interface Observer {
    /**
     * 當被觀察者對象發生改變時,此方法被調用
     */
    void update(Observable o, Object arg);
}
View Code

    Watched:

package com.lee.jdkobserver;

import java.util.Observable;

/**
 * 具體主題
 */
public class Watched extends Observable {
    private String data = "";

    public void changeData(String data) {
        if (this.data.equals(data)) {
            return;
        }
        this.data = data;
        setChanged();
        notifyObservers(this.data);
    }
}
View Code

    Watcher:

package com.lee.jdkobserver;

import java.util.Observable;
import java.util.Observer;

/**
 * 具體觀察者,實現jdk中的Observer
 */
public class Watcher implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("數據改變成了:" + arg);
    }
}
View Code

    JdkObserverTest:

package com.lee.test;

import com.lee.jdkobserver.Watched;
import com.lee.jdkobserver.Watcher;

import java.util.Observer;

public class JdkObserverTest {

    public static void main(String[] args) {
        Watched watched = new Watched();
        Observer observer = new Watcher();
        watched.addObserver(observer);
        watched.changeData("first");
        watched.changeData("second");
        watched.changeData("third");
        watched.changeData("fourth");
    }
}
View Code

    完整代碼能夠從spring-boot-test獲取,在com.lee.jdkobserver下

    運行結果以下

    類圖以下

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;
    }

}
View Code

  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);

}
View Code 

  具體案例

    MessageEvent:  

package com.lee.springevent.event;

import org.springframework.context.ApplicationEvent;

/**
 * 短信事件,事件信息的載體
 * 能夠從中獲取事件源信息,本例中source不表明事件源
 */
public class MessageEvent extends ApplicationEvent {

    public MessageEvent(Object source) {
        super(source);
    }
}
View Code

    MessageListener:

package com.lee.springevent.listener;

import com.lee.springevent.event.MessageEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class MessageListener implements ApplicationListener{

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof MessageEvent) {
            String phoneNumber = (String)event.getSource();
            System.out.println("我已收到通知:即將向"+phoneNumber+"發短信了...");
        }
    }
}
View Code

    MessageService:

package com.lee.springevent.service;

import com.lee.springevent.event.MessageEvent;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * ApplicationContextAware,可以獲取spring的上下文環境
 *
 * MessageService至關於以前說的事件環境
 */
public class MessageService implements ApplicationContextAware {

    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    public void sendMessage(String phoneNumber)
    {
        MessageEvent evt = new MessageEvent(phoneNumber);

        // 發佈事件
        ctx.publishEvent(evt);
    }
}
View Code

    spring-event.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="messageService" class="com.lee.springevent.service.MessageService" />
    <bean id="messageListener" class="com.lee.springevent.listener.MessageServiceListener" />

</beans>
View Code

    SpringEventTest:

package com.lee.test;

import com.lee.springevent.service.MessageService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringEventTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-event.xml");
        MessageService messageService = (MessageService) applicationContext.getBean("messageService");
        messageService.sendMessage("1574480311");
    }
}
View Code

    執行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)    主題與觀察者之間若是存在循環依賴,可能致使系統崩潰。

  觀察者模式應用場景

    抽象的來說:對一個對象狀態的更新,須要其餘對象同步更新,並且其餘對象的數量動態可變;對象僅須要將本身的更新通知給其餘對象而不須要知道其餘對象的細節。你們能夠根據以上兩點做爲基本準則,某個場景是否知足觀察者模式。

    具體應用場景就有不少了,好比文中的事件機制、公衆號訂閱,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

參考

       《Head First 設計模式》

       《java與模式》

相關文章
相關標籤/搜索