最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java和Spring中觀察者模式的應(yīng)用詳解

 更新時(shí)間:2021年10月14日 11:08:56   作者:商俊帥  
這篇文章主要介紹了java和Spring中觀察者模式的應(yīng)用,,具有一定的參考價(jià)值,感興趣的可以了解一下,希望能夠給你帶來幫助

一、觀察者模式基本概況

1.概念

觀察者模式(Observer Design Pattern)也被稱為發(fā)布訂閱模式(Publish-Subcribe Design Pattern)。定義如下

Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and update automatically。

觀察者模式定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在狀態(tài)變化時(shí),會(huì)通知所有觀察者對(duì)象,使它們能夠自動(dòng)更新自己。

2.作用

參考設(shè)計(jì)模式之美的一段總結(jié)

回到本質(zhì),設(shè)計(jì)模式要干的事情就是解耦。創(chuàng)建型模式是將創(chuàng)建對(duì)象和使用對(duì)象解耦,結(jié)構(gòu)型模式是將不同功能代碼解耦,行為型模式是將不同的行為代碼解耦,具體到觀察者模式,是將觀察者和被觀察者代碼解耦。

3.實(shí)現(xiàn)方式

觀察者模式在不同的場(chǎng)景有不同的實(shí)現(xiàn)方式,自然也有不同的名字。如Publisher-Subscriber、Producer-Consumer、Dispatcher-Listener,等等。

有哪些不同實(shí)現(xiàn)方式呢?同步阻塞方式異步非阻塞方式;進(jìn)程內(nèi)實(shí)現(xiàn)方式,跨進(jìn)程實(shí)現(xiàn)方式。

同步阻塞方式是經(jīng)典的實(shí)現(xiàn)方式,是說在主題通知觀察者和觀察者執(zhí)行自己的邏輯是由同一個(gè)線程完成的,比如下面要說的java中Observer接口和Observable類,后有UML圖

public void notifyObservers(Object arg) {
    for (int i = arrLocal.length-1; i>=0; i--){
    	//使用了for循環(huán)同步通知觀察者
    	((Observer)arrLocal[i]).update(this, arg);
    } 
}

異步非阻塞方式是在通知觀察者和觀察者執(zhí)行自己邏輯不是同一個(gè)線程,如下代碼

public void notifyObservers(Object arg) {
    for (int i = arrLocal.length-1; i>=0; i--){
    	//使用了開啟新線程方式異步通知觀察者
    	new Thread(()->{
    		((Observer)arrLocal[i]).update(this, arg);
		}).start();
    } 
}

上面都是在同一個(gè)進(jìn)程中,還有跨進(jìn)程的實(shí)現(xiàn)方式就是常用的MQ,消息隊(duì)列

二、java實(shí)現(xiàn)兩種觀察者模式

1.Observer接口和Observable類

下面我們使用JDK提供的接口和類實(shí)現(xiàn)項(xiàng)目中使用觀察者模式

在這里插入圖片描述

先說下Observer接口和Observable類

Observable類,被觀察者。有三個(gè)組成部分,用Vector管理注冊(cè)的觀察者(Observer),并提供了增刪、統(tǒng)計(jì)方法。一旦狀態(tài)(changed)改變就通知(notifyObservers)觀察者。notifyObservers方法會(huì)調(diào)用觀察者(Observer)的update方法。

Observer接口就是一個(gè)觀察者的標(biāo)準(zhǔn),實(shí)現(xiàn)該接口并重寫update方法就可以實(shí)現(xiàn)一個(gè)觀察者。

具體的源碼大家可以自己看下,比較簡(jiǎn)單,在java.util包下。

實(shí)現(xiàn)一個(gè)需求

當(dāng)自定義被觀察者data值=1時(shí)通知各個(gè)觀察者執(zhí)行update方法,應(yīng)該如何做?

UML圖已經(jīng)畫出來了,剩下就是寫代碼了。

代碼如下

//自定義被觀察者
public class MyObservable extends Observable {
    private int data;
    public void setData(int data) {
        this.data = data;
    }
    public void notifyObserver(){
    	//當(dāng)data等于1時(shí)通知觀察者
        if (data==1){
            this.setChanged();
            super.notifyObservers();
        }
    }
}
//自定義觀察者
public class MyObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("自定義觀察者執(zhí)行了.....");
    }
}
//測(cè)試類
public class PubSubTest {
    public static void main(String[] args) {
        MyObservable observable = new MyObservable();
        observable.addObserver(new MyObserver());
        observable.setData(1);
        observable.notifyObserver();
    }
}

2.EventObject和EventListener

JDK提供了EventObject類和EventListener接口定義了實(shí)現(xiàn)觀察者模式的第二個(gè)標(biāo)準(zhǔn),只是定義了標(biāo)準(zhǔn)沒有提供默認(rèn)實(shí)現(xiàn),但是其他框架如Spring實(shí)現(xiàn)該方式,這個(gè)下面再說。

先看EventObject類

該類實(shí)現(xiàn)的功能就是定義一個(gè)事件,其中有一個(gè)最重要的屬性source,叫事件源。含義是哪個(gè)類發(fā)出的事件。自定義事件時(shí)需要繼承該類

public class EventObject implements java.io.Serializable {
    protected transient Object  source;
    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");
        this.source = source;
    }
    public Object getSource() {
        return source;
    }
}

EventListener接口是一個(gè)空接口,自定義監(jiān)聽器需要繼承該接口。

注意此時(shí)事件就是一個(gè)被觀察者,監(jiān)視器就是觀察者。

三、Spring事件監(jiān)聽實(shí)戰(zhàn)及原理

1.Spring如何使用EventObject和EventListener實(shí)現(xiàn)觀察者?

在Spring中為自定義事件和自定義監(jiān)聽者,分別提供一個(gè)類和一個(gè)接口。

ApplicationEvent類繼承了EventObject,用于在Spring環(huán)境下自定義事件

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

ApplicationListener接口繼承JDK的EventListener,用于在Spring環(huán)境下自定義監(jiān)聽者

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);
}

onApplicationEvent方法就是根據(jù)傳過來的事件參數(shù),監(jiān)聽是否是自己感興趣的事件,然后執(zhí)行方法體。

介紹完類和接口剩下的就是如何在Spring中如何自定義事件、如何發(fā)布事件、如何使用自定義監(jiān)聽器監(jiān)聽事件,下面舉個(gè)例子

2.先實(shí)戰(zhàn)—要先會(huì)用

實(shí)現(xiàn)一個(gè)需求:當(dāng)調(diào)用一個(gè)類的方法完成時(shí),該類發(fā)布事件,事件監(jiān)聽器監(jiān)聽該類的事件并執(zhí)行的自己的方法邏輯

假設(shè)這個(gè)類是Request、發(fā)布的事件是ReuqestEvent、事件監(jiān)聽者是ReuqestListener。當(dāng)調(diào)用Request的doRequest方法時(shí),發(fā)布事件。模擬的代碼如下

public class SpringEventTest {
    public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext("com.thinkcoder.parttern.behavioral.pubsub.spring");
        Request request = (Request) context.getBean("request");
        //調(diào)用方法,發(fā)布事件
        request.doRequest();
    }
}
//定義事件
class RequestEvent extends ApplicationEvent {
    public RequestEvent(Request source) {
        super(source);
    }
}
//發(fā)布事件
@Component
class Request{
    @Autowired
    private ApplicationContext applicationContext;
    public void doRequest(){
        System.out.println("調(diào)用Request類的doRequest方法發(fā)送一個(gè)請(qǐng)求");
        applicationContext.publishEvent(new RequestEvent(this));
    }
}
//監(jiān)聽事件
@Component
class RequestListener implements ApplicationListener<RequestEvent> {
    @Override
    public void onApplicationEvent(RequestEvent event) {
        System.out.println("監(jiān)聽到RequestEvent事件,執(zhí)行方法");
    }
}
//打印的日志
調(diào)用Request類的doRequest方法發(fā)送一個(gè)請(qǐng)求
監(jiān)聽到RequestEvent事件,執(zhí)行方法

上面我們依靠spring實(shí)現(xiàn)了事件—監(jiān)聽機(jī)制,使用的步驟有如下幾步

  • 定義事件:繼承ApplicationEvent類,實(shí)現(xiàn)方法傳入事件源。由事件源產(chǎn)生事件
  • 發(fā)布事件:使用Spring的IOC容器ApplicationContext的publishEvent方法發(fā)布事件
  • 監(jiān)聽事件:實(shí)現(xiàn)ApplictionListener接口重寫方法即可實(shí)現(xiàn)自定義監(jiān)聽器

3.會(huì)原理—搞清楚為什么會(huì)這樣

先將上述過程畫成一幅圖,然后展開來解釋各個(gè)步驟,相信我你能看懂

在這里插入圖片描述

通過上面的流程圖,回答下面幾個(gè)問題

1.監(jiān)聽器什么時(shí)候注冊(cè)到IOC容器中?

注冊(cè)的開始邏輯是在AbstractApplicationContext類的refresh方法,該方法包含了整個(gè)IOC容器初始化所有方法。其中有一個(gè)registerListeners()方法就是注冊(cè)系統(tǒng)監(jiān)聽者(spring自帶的)和自定義監(jiān)聽器的。

看registerListeners的關(guān)鍵方法體,其中的兩個(gè)方法addApplicationListeneraddApplicationListenerBean,從方法可以看出是添加監(jiān)聽者。

for (ApplicationListener<?> listener : getApplicationListeners()) {
	getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
	getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}

那么最后將監(jiān)聽者放到哪里了呢?就是ApplicationEventMulticaster接口的子類

在這里插入圖片描述

該接口主要兩個(gè)職責(zé),維護(hù)ApplicationListener相關(guān)類和發(fā)布事件。

實(shí)現(xiàn)是在默認(rèn)實(shí)現(xiàn)類AbstractApplicationEventMulticaster,最后將Listener放到了內(nèi)部類ListenerRetriever兩個(gè)set集合中

private class ListenerRetriever {
	public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
	public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

ListenerRetriever又被稱為監(jiān)聽器注冊(cè)表。

2.Spring如何發(fā)布的事件并通知的監(jiān)聽者?

該問題開始是在ApplicationContext.publishEvent的方法,該方法調(diào)用路線

AbstractApplicationContext.publishEventSimpleApplicaitonEventMulticaster.multicastEventSimpleApplicaitonEventMulticaster.invokeListener→SimpleApplicaitonEventMulticaster.doInvokeListener→調(diào)用系統(tǒng)及自定義listener的onApplicationEvent方法,這個(gè)就是發(fā)布事件并通知的調(diào)用路線。

這個(gè)注意的有兩個(gè)方法

multicastEvent方法,該方法有兩種方式調(diào)用invokeListener,通過線程池和直接調(diào)用,進(jìn)一步說就是通過異步和同步兩種方式調(diào)用

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}

最后看doInvokeListener方法

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		//直接調(diào)用了listener接口的onApplicationEvent方法
		listener.onApplicationEvent(event);
	}
}

四、最后一張圖總結(jié)

在這里插入圖片描述

上圖包含了在Spring如何自定義事件、監(jiān)聽器以及發(fā)布事件和通知監(jiān)聽者的原理,大家可以自己梳理下。

相關(guān)文章

  • 值得收藏的2017年Java開發(fā)崗位面試題

    值得收藏的2017年Java開發(fā)崗位面試題

    這篇文章主要為大家推薦一份值得收藏的2017年Java開發(fā)崗位面試題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • idea 自動(dòng)生成類注釋和方法注釋的實(shí)現(xiàn)步驟

    idea 自動(dòng)生成類注釋和方法注釋的實(shí)現(xiàn)步驟

    這篇文章主要介紹了idea 自動(dòng)生成類注釋和方法注釋的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • JDK輸入命令Javac報(bào)錯(cuò)的解決方法

    JDK輸入命令Javac報(bào)錯(cuò)的解決方法

    相信很多人都經(jīng)歷過配置環(huán)境變量失敗的經(jīng)歷,尤其是很多時(shí)候明明按照老師教的步驟或者教程上的方法循規(guī)守矩配置卻還是出錯(cuò),下面我們來解決一個(gè)非常蹊蹺的問題---輸入Java和Java -version都沒問題,但是輸入Javac報(bào)錯(cuò),感興趣的朋友一起看看吧
    2023-11-11
  • Java實(shí)現(xiàn)對(duì)象復(fù)制的方法實(shí)例

    Java實(shí)現(xiàn)對(duì)象復(fù)制的方法實(shí)例

    這篇文章主要介紹了Java實(shí)現(xiàn)對(duì)象復(fù)制的方法實(shí)例,深復(fù)制:復(fù)制出來的對(duì)象中的變量(包括基本類型和字符串)和原來的對(duì)象的值都相同,引用對(duì)象也會(huì)指向復(fù)制出來的對(duì)象,需要的朋友可以參考下
    2023-08-08
  • java生成圖片驗(yàn)證碼功能

    java生成圖片驗(yàn)證碼功能

    最近在用ssm框架做一個(gè)管理系統(tǒng),做到登錄驗(yàn)證時(shí),需要實(shí)現(xiàn)圖片驗(yàn)證碼功能,今天小編給大家分享基于java制作的圖片驗(yàn)證碼功能,感興趣的朋友一起看看吧
    2019-12-12
  • java jni調(diào)用c函數(shù)實(shí)例分享(java調(diào)用c函數(shù))

    java jni調(diào)用c函數(shù)實(shí)例分享(java調(diào)用c函數(shù))

    Java代碼中調(diào)用C/C++代碼,當(dāng)然是使用JNI,JNI是Java native interface的簡(jiǎn)寫,可以譯作Java原生接口,下面看實(shí)例吧
    2013-12-12
  • 解決SpringBoot整合RocketMQ遇到的坑

    解決SpringBoot整合RocketMQ遇到的坑

    這篇文章主要介紹了解決SpringBoot整合RocketMQ遇到的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • SpringBoot 定制化返回?cái)?shù)據(jù)的實(shí)現(xiàn)示例

    SpringBoot 定制化返回?cái)?shù)據(jù)的實(shí)現(xiàn)示例

    這篇文章主要介紹了SpringBoot 定制化返回?cái)?shù)據(jù)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java使用DFA算法實(shí)現(xiàn)過濾多家公司自定義敏感字功能詳解

    Java使用DFA算法實(shí)現(xiàn)過濾多家公司自定義敏感字功能詳解

    這篇文章主要介紹了Java使用DFA算法實(shí)現(xiàn)過濾多家公司自定義敏感字功能,結(jié)合實(shí)例形式分析了DFA算法的實(shí)現(xiàn)原理及過濾敏感字的相關(guān)操作技巧,需要的朋友可以參考下
    2017-08-08
  • Java?C++題解leetcode字符串輪轉(zhuǎn)KMP算法詳解

    Java?C++題解leetcode字符串輪轉(zhuǎn)KMP算法詳解

    這篇文章主要為大家介紹了Java?C++題解leetcode字符串輪轉(zhuǎn)KMP算法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09

最新評(píng)論

景泰县| 舞阳县| 德昌县| 商丘市| 资兴市| 陇西县| 济宁市| 颍上县| 勐海县| 亚东县| 高陵县| 太保市| 六枝特区| 泸州市| 寻甸| 辽中县| 丹东市| 岳普湖县| 庄河市| 广州市| 宜黄县| 根河市| 乐都县| 东明县| 昌吉市| 济南市| 应用必备| 新蔡县| 二手房| 云安县| 和政县| 大同县| 平罗县| 东方市| 仪征市| 绍兴县| 广昌县| 饶河县| 湟中县| 八宿县| 淮北市|