服務(wù)降級(jí)熔斷 - 觀(guān)察者模式

觀(guān)察者模式

場(chǎng)景描述

先看一個(gè)關(guān)注微信公眾號(hào)的業(yè)務(wù),關(guān)注完微信公眾號(hào)之后要進(jìn)行如下操作:

1、記錄文本日志

2、記錄數(shù)據(jù)庫(kù)日志

3、發(fā)送短信

4、送優(yōu)惠券,積分等

5、其他各類(lèi)活動(dòng)等

傳統(tǒng)解決方案:

在關(guān)注公眾號(hào)邏輯等類(lèi)內(nèi)部增加相關(guān)代碼,完成各種邏輯。

存在問(wèn)題:

1、一旦某個(gè)業(yè)務(wù)邏輯發(fā)生改變,如關(guān)注公眾號(hào)業(yè)務(wù)中增加其他業(yè)務(wù)邏輯,需要修改關(guān)注公眾號(hào)核心文件、甚至關(guān)注流程。

2、日積月累后,文件冗長(zhǎng),導(dǎo)致后續(xù)維護(hù)困難。

存在問(wèn)題原因主要是程序的"緊密耦合",使用觀(guān)察模式可以將目前的業(yè)務(wù)邏輯優(yōu)化成"松耦合",達(dá)到易維護(hù)、易修改的目的,同時(shí)也符合面向接口編程的思想。

什么是觀(guān)察者模式

觀(guān)察者模式(Observer Pattern):定義對(duì)象間的一種一對(duì)多依賴(lài)關(guān)系,使得每當(dāng)一個(gè)對(duì)象狀態(tài)發(fā)生改變時(shí),其相關(guān)依賴(lài)對(duì)象皆得到通知并被自動(dòng)更新。觀(guān)察者模式又叫做發(fā)布-訂閱(Publish/Subscribe)模式。觀(guān)察者模式是一種對(duì)象行為型模式

UML圖

各個(gè)字段含義如下:

  • Subject: 目標(biāo),抽象被觀(guān)察者。 抽象主題角色把所有觀(guān)察者對(duì)象保存在一個(gè)集合里,每個(gè)主題都可以有任意數(shù)量的觀(guān)察者,抽象主題提供一個(gè)接口,可以增加和刪除觀(guān)察者對(duì)象。
  • ConcreteSubject: 具體目標(biāo),具體的被觀(guān)察者。 該角色將有關(guān)狀態(tài)存入具體觀(guān)察者對(duì)象,在具體主題的內(nèi)部狀態(tài)發(fā)生改變時(shí),給所有注冊(cè)過(guò)的觀(guān)察者發(fā)送通知。
  • Observer: 觀(guān)察者。 是觀(guān)察者者的抽象類(lèi),它定義了一個(gè)更新接口,使得在得到主題更改通知時(shí)更新自己。
  • ConcreteObserver: 具體觀(guān)察者。實(shí)現(xiàn)抽象觀(guān)察者定義的更新接口,以便在得到主題更改通知時(shí)更新自身的狀態(tài)。

觀(guān)察者模式的簡(jiǎn)單實(shí)現(xiàn)

觀(guān)察者模式這種發(fā)布-訂閱的形式我們可以拿微信公眾號(hào)來(lái)舉例,假設(shè)微信用戶(hù)就是觀(guān)察者,微信公眾號(hào)是被觀(guān)察者,有多個(gè)的微信用戶(hù)關(guān)注了程序猿這個(gè)公眾號(hào),當(dāng)這個(gè)公眾號(hào)更新時(shí)就會(huì)通知這些訂閱的微信用戶(hù)。看看用代碼如何實(shí)現(xiàn):

抽象觀(guān)察者(Observer)

//抽象觀(guān)察者

public interface Observer {

//收到訂閱消息執(zhí)行的操作

void update(String message);

}

具體觀(guān)察者(ConcrereObserver)

//記錄日志

public class LogObserver implements Observer{

@Override

public void update(String message) {

System.out.println("日志訂閱---"+message+"---開(kāi)始記錄日志");

}

}

//優(yōu)惠券訂閱

public class TicketObserver implements Observer{

@Override

public void update(String message) {

System.out.println("優(yōu)惠券訂閱---"+message+"---開(kāi)始發(fā)送優(yōu)惠券");

}

}

抽象被觀(guān)察者(Subject)

//抽象被觀(guān)察者

public interface Subject {

/**

  • 增加訂閱者
  • @param observer

*/

void attach(Observer observer);

/**

  • 刪除訂閱者
  • @param observer

*/

void detach(Observer observer);

/**

  • 通知訂閱者

*/

void notify(String message);

}

具體被觀(guān)察者(ConcreteSubject)

//訂閱公眾號(hào)

public class SubscriptionSubject implements Subject {

//儲(chǔ)存訂閱公眾號(hào)的業(yè)務(wù)

private List<Observer> list= new ArrayList<Observer>();

@Override

public void attach(Observer observer) {

list.add(observer);

}

@Override

public void detach(Observer observer) {

list.remove(observer);

}

@Override

public void notify(String message) {

for (Observer observer : list) {

observer.update(message);

}

}

public void subscribe(){

System.out.println("執(zhí)行訂閱的具體操作");

notify("訂閱成功了");

}

}

客戶(hù)端調(diào)用

public class Client {

public static void main(String[] args) {

SubscriptionSubject subscriptionSubject = new SubscriptionSubject();

//添加日志訂閱

Observer logObserver = new LogObserver();

subscriptionSubject.attach(logObserver);

//添加優(yōu)惠券訂閱

TicketObserver ticketObserver = new TicketObserver();

subscriptionSubject.attach(ticketObserver);

//執(zhí)行訂閱的操作

subscriptionSubject.subscribe();

}

}

執(zhí)行結(jié)果

執(zhí)行訂閱的具體操作

日志訂閱---訂閱成功了---開(kāi)始記錄日志

優(yōu)惠券訂閱---訂閱成功了---開(kāi)始發(fā)送優(yōu)惠券

使用觀(guān)察者模式的場(chǎng)景和優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn)

解除耦合,讓耦合的雙方都依賴(lài)于抽象,從而使得各自的變換都不會(huì)影響另一邊的變換。

  • 缺點(diǎn)

在應(yīng)用觀(guān)察者模式時(shí)需要考慮一下開(kāi)發(fā)效率和運(yùn)行效率的問(wèn)題,程序中包括一個(gè)被觀(guān)察者、多個(gè)觀(guān)察者,開(kāi)發(fā)、調(diào)試等內(nèi)容會(huì)比較復(fù)雜,而且在Java中消息的通知一般是順序執(zhí)行,那么一個(gè)觀(guān)察者卡頓,會(huì)影響整體的執(zhí)行效率,在這種情況下,一般會(huì)采用異步實(shí)現(xiàn)。

java中的實(shí)現(xiàn)

JDK 提供了 一套 觀(guān)察者模式的實(shí)現(xiàn),在java.util包中, java.util.Observable類(lèi)和java.util.Observer接口。Observable是被觀(guān)察者,Observer是觀(guān)察者。

推模型和拉模型

在觀(guān)察者模式中,又分為推模型和拉模型兩種方式。

  • 推模型

主題對(duì)象向觀(guān)察者推送主題的詳細(xì)信息,不管觀(guān)察者是否需要,推送的信息通常是主題對(duì)象的全部或部分?jǐn)?shù)據(jù)。

  • 拉模型

主題對(duì)象在通知觀(guān)察者的時(shí)候,只傳遞少量信息。如果觀(guān)察者需要更具體的信息,由觀(guān)察者主動(dòng)到主題對(duì)象中獲取,相當(dāng)于是觀(guān)察者從主題對(duì)象中拉數(shù)據(jù)。一般這種模型的實(shí)現(xiàn)中,會(huì)把主題對(duì)象自身通過(guò)update()方法傳遞給觀(guān)察者,這樣在觀(guān)察者需要獲取數(shù)據(jù)的時(shí)候,就可以通過(guò)這個(gè)引用來(lái)獲取了。

根據(jù)上面的描述,發(fā)現(xiàn)上面的例子就是典型的推模型,下面給出一個(gè)拉模型的實(shí)例。

拉模型的抽象主題類(lèi)

拉模型的抽象主題類(lèi)主要的改變是nodifyObservers()方法。在循環(huán)通知觀(guān)察者的時(shí)候,也就是循環(huán)調(diào)用觀(guān)察者的update()方法的時(shí)候,傳入的參數(shù)不同了。

public abstract class Subject {

/**

  • 用來(lái)保存注冊(cè)的觀(guān)察者對(duì)象

*/

private List<Observer> list = new ArrayList<Observer>();

/**

  • 注冊(cè)觀(guān)察者對(duì)象
  • @param observer 觀(guān)察者對(duì)象

*/

public void attach(Observer observer) {

list.add(observer);

System.out.println("Attached an observer");

}

/**

  • 刪除觀(guān)察者對(duì)象
  • @param observer 觀(guān)察者對(duì)象

*/

public void detach(Observer observer) {

list.remove(observer);

}

/**

  • 通知所有注冊(cè)的觀(guān)察者對(duì)象

*/

public void nodify() {

for (Observer observer : list) {

observer.update(this);

}

}

}

拉模型的具體主題類(lèi)

跟推模型相比,有一點(diǎn)變化,就是調(diào)用通知觀(guān)察者的方法的時(shí)候,不需要傳入?yún)?shù)了。

public class ConcreteSubject extends Subject {

private String state;

public String getState() {

return state;

}

public void change(String newState) {

state = newState;

System.out.println("主題狀態(tài)為:" + state);

//狀態(tài)發(fā)生改變,通知各個(gè)觀(guān)察者

this.nodify();

}

}

拉模型的抽象觀(guān)察者類(lèi)

拉模型通常都是把主題對(duì)象當(dāng)做參數(shù)傳遞。

public interface Observer {

/**

  • 更新接口
  • @param subject 傳入主題對(duì)象,方面獲取相應(yīng)的主題對(duì)象的狀態(tài)

*/

void update(Subject subject);

}

拉模型的具體觀(guān)察者類(lèi)

public class ConcreteObserver implements Observer {

//觀(guān)察者的狀態(tài)

private String observerState;

@Override

public void update(Subject subject) {

/**

  • 更新觀(guān)察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致

*/

observerState = ((ConcreteSubject)subject).getState();

System.out.println("觀(guān)察者狀態(tài)為:"+observerState);

}

}

兩種模式的比較

  • 推模型是假定主題對(duì)象知道觀(guān)察者需要的數(shù)據(jù);而拉模型是主題對(duì)象不知道觀(guān)察者具體需要什么數(shù)據(jù),沒(méi)有辦法的情況下,干脆把自身傳遞給觀(guān)察者,讓觀(guān)察者自己去按需要取值。

  • 推模型可能會(huì)使得觀(guān)察者對(duì)象難以復(fù)用,因?yàn)橛^(guān)察者的update()方法是按需要定義的參數(shù),可能無(wú)法兼顧沒(méi)有考慮到的使用情況。這就意味著出現(xiàn)新情況的時(shí)候,就可能提供新的 update()方法,或者是干脆重新實(shí)現(xiàn)觀(guān)察者;而拉模型就不會(huì)造成這樣的情況,因?yàn)槔P拖拢瑄pdate()方法的參數(shù)是主題對(duì)象本身,這基本上是主題對(duì)象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。

RxJava

場(chǎng)景描述

假設(shè)有這樣一個(gè)需求:界面上有一個(gè)自定義的視圖 imageCollectorView (圖片展示),它的作用是顯示多張圖片,并能使用 addImage(Bitmap) 方法來(lái)任意增加顯示的圖片。現(xiàn)在需要程序?qū)⒁粋€(gè)給出的目錄數(shù)組 File[] folders 中每個(gè)目錄下的 png 圖片都加載出來(lái)并顯示在 imageCollectorView 中。由于讀取圖片的這一過(guò)程較為耗時(shí),需要放在后臺(tái)執(zhí)行,而圖片的顯示則必須在 UI 線(xiàn)程執(zhí)行。常見(jiàn)的實(shí)現(xiàn)方式,在后臺(tái)開(kāi)啟一個(gè)線(xiàn)程,執(zhí)行加載圖片的操作。代碼如下:

new Thread() {

@Override

public void run() {

super.run();

for (File folder : folders) {

File[] files = folder.listFiles();

for (File file : files) {

if (file.getName().endsWith(".png")) {

final Bitmap bitmap = getBitmapFromFile(file);

getActivity().runOnUiThread(new Runnable() {

@Override

public void run() {

imageCollectorView.addImage(bitmap);

}

});

}

}

}

}

}.start();

而如果使用 RxJava ,實(shí)現(xiàn)方式是這樣的:

Observable.from(folders)

.flatMap(new Func1<File, Observable<File>>() {

@Override

public Observable<File> call(File file) {

return Observable.from(file.listFiles());

}

})

.filter(new Func1<File, Boolean>() {

@Override

public Boolean call(File file) {

return file.getName().endsWith(".png");

}

})

.map(new Func1<File, Bitmap>() {

@Override

public Bitmap call(File file) {

return getBitmapFromFile(file);

}

})

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new Action1<Bitmap>() {

@Override

public void call(Bitmap bitmap) {

imageCollectorView.addImage(bitmap);

}

});

從中可以看出rxjava代碼邏輯的簡(jiǎn)潔。rxjava代碼簡(jiǎn)潔不是指代碼量少,而是指代碼邏輯的簡(jiǎn)潔。

什么是RxJava

原文 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一個(gè)在 Java VM 上使用可觀(guān)測(cè)的序列來(lái)組成異步的、基于事件的程序的庫(kù))。這就是 RxJava ,概括得非常精準(zhǔn)。

其實(shí), RxJava 的本質(zhì)可以壓縮為異步這一個(gè)詞。說(shuō)到根上,它就是一個(gè)實(shí)現(xiàn)異步操作的庫(kù)。

RxJava的特點(diǎn)

異步操作很關(guān)鍵的一點(diǎn)是程序的簡(jiǎn)潔性,因?yàn)樵谡{(diào)度過(guò)程比較復(fù)雜的情況下,異步代碼經(jīng)常會(huì)既難寫(xiě)也難被讀懂。 Android 創(chuàng)造的 AsyncTask 和Handler ,其實(shí)都是為了讓異步代碼更加簡(jiǎn)潔。RxJava 的優(yōu)勢(shì)也是簡(jiǎn)潔,但它的簡(jiǎn)潔的與眾不同之處在于,隨著程序邏輯變得越來(lái)越復(fù)雜,它依然能夠保持簡(jiǎn)潔。

RxJava的 觀(guān)察者模式

RxJava 的異步實(shí)現(xiàn),是通過(guò)一種擴(kuò)展的觀(guān)察者模式來(lái)實(shí)現(xiàn)的。

RxJava 的觀(guān)察者模式大致如下圖:

RxJava 有四個(gè)基本概念:

  1. Observable (被觀(guān)察者) 產(chǎn)生事件
  2. Observer (觀(guān)察者) 接收事件,并給出響應(yīng)動(dòng)作
  3. subscribe (訂閱) 連接 被觀(guān)察者 & 觀(guān)察者
  4. 事件 被觀(guān)察者 & 觀(guān)察者 溝通的載體

Observable 和 Observer 通過(guò) subscribe() 方法實(shí)現(xiàn)訂閱關(guān)系,從而 Observable 可以在需要的時(shí)候發(fā)出事件來(lái)通知Observer。

與傳統(tǒng)觀(guān)察者模式不同, RxJava 的事件回調(diào)方法除了普通事件 onNext() (相當(dāng)于 onClick() / onEvent())之外,還定義了兩個(gè)特殊的事件:onCompleted() 和 onError()。

  • onCompleted(): 事件隊(duì)列完結(jié)。RxJava 不僅把每個(gè)事件單獨(dú)處理,還會(huì)把它們看做一個(gè)隊(duì)列。RxJava 規(guī)定,當(dāng)不會(huì)再有新的 onNext() 發(fā)出時(shí),需要觸發(fā) onCompleted() 方法作為標(biāo)志。
  • onError(): 事件隊(duì)列異常。在事件處理過(guò)程中出異常時(shí),onError() 會(huì)被觸發(fā),同時(shí)隊(duì)列自動(dòng)終止,不允許再有事件發(fā)出。

在一個(gè)正確運(yùn)行的事件序列中, onCompleted() 和 onError() 有且只有一個(gè),并且是事件序列中的最后一個(gè)。需要注意的是,onCompleted() 和 onError() 二者也是互斥的,即在隊(duì)列中調(diào)用了其中一個(gè),就不應(yīng)該再調(diào)用另一個(gè)。

RxJava的實(shí)現(xiàn)

基于以上的概念, RxJava 的基本實(shí)現(xiàn)主要有三點(diǎn):

  1. 創(chuàng)建 觀(guān)察者Observer

Observer 即觀(guān)察者,它決定事件觸發(fā)的時(shí)候?qū)⒂性鯓拥男袨椤?RxJava 中的 Observer 接口的實(shí)現(xiàn)方式:

Observer<String> observer = new Observer<String>() {

@Override

public void onNext(String s) {

Log.d(tag, "Item: " + s);

}

@Override

public void onCompleted() {

Log.d(tag, "Completed!");

}

@Override

public void onError(Throwable e) {

Log.d(tag, "Error!");

}

};

  1. 創(chuàng)建 被觀(guān)察者Observable

Observable 即被觀(guān)察者,它決定什么時(shí)候觸發(fā)事件以及觸發(fā)怎樣的事件。 RxJava 使用 create() 方法來(lái)創(chuàng)建一個(gè) Observable ,并為它定義事件觸發(fā)規(guī)則:

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {

@Override

public void call(Subscriber<? super String> subscriber) {

subscriber.onNext("Hello");

subscriber.onNext("Hi");

subscriber.onNext("Aloha");

subscriber.onCompleted();

}

});

可以看到,這里傳入了一個(gè) OnSubscribe 對(duì)象作為參數(shù)。OnSubscribe 會(huì)被存儲(chǔ)在返回的 被觀(guān)察者Observable 對(duì)象中,它的作用相當(dāng)于一個(gè)計(jì)劃表,當(dāng)被觀(guān)察者Observable 被訂閱的時(shí)候,OnSubscribe 的 call() 方法會(huì)自動(dòng)被調(diào)用,事件序列就會(huì)依照設(shè)定依次觸發(fā)(對(duì)于上面的代碼,就是觀(guān)察者Subscriber 將會(huì)被調(diào)用三次 onNext() 和一次 onCompleted())。這樣,由被觀(guān)察者調(diào)用了觀(guān)察者的回調(diào)方法,就實(shí)現(xiàn)了由被觀(guān)察者向觀(guān)察者的事件傳遞,即觀(guān)察者模式。

create() 方法是 RxJava 最基本的創(chuàng)造事件序列的方法。基于這個(gè)方法, RxJava 還提供了一些方法用來(lái)快捷創(chuàng)建事件隊(duì)列,例如:

  • just(T...): 將傳入的參數(shù)依次發(fā)送出來(lái)。

Observable observable = Observable.just("Hello", "Hi", "Aloha");

// 將會(huì)依次調(diào)用:

// onNext("Hello");

// onNext("Hi");

// onNext("Aloha");

// onCompleted();

  • from(T[]) / from(Iterable<? extends T>) : 將傳入的數(shù)組或 Iterable 拆分成具體對(duì)象后,依次發(fā)送出來(lái)。

String[] words = {"Hello", "Hi", "Aloha"};

Observable observable = Observable.from(words);

// 將會(huì)依次調(diào)用:

// onNext("Hello");

// onNext("Hi");

// onNext("Aloha");

// onCompleted();

  1. Subscribe (訂閱)

創(chuàng)建了被觀(guān)察者 Observable 和觀(guān)察者 Observer 之后,再用 subscribe() 方法將它們聯(lián)結(jié)起來(lái),整條鏈子就可以工作了。代碼形式很簡(jiǎn)單:

observable.subscribe(observer);

// 或者:

observable.subscribe(subscriber);

整個(gè)過(guò)程中對(duì)象間的關(guān)系如下圖:

基于事件流的鏈?zhǔn)秸{(diào)用

上述的實(shí)現(xiàn)方式是為了說(shuō)明Rxjava的原理 & 使用

在實(shí)際應(yīng)用中,會(huì)將上述步驟&代碼連在一起,從而更加簡(jiǎn)潔、更加優(yōu)雅,即所謂的 RxJava基于事件流的鏈?zhǔn)秸{(diào)用

// RxJava的鏈?zhǔn)讲僮?/p>

Observable.create(new ObservableOnSubscribe<Integer>() {

// 1. 創(chuàng)建被觀(guān)察者 & 生產(chǎn)事件

@Override

public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {

emitter.onNext(1);

emitter.onNext(2);

emitter.onNext(3);

emitter.onComplete();

}

}).subscribe(new Observer<Integer>() {

// 2. 通過(guò)通過(guò)訂閱(subscribe)連接觀(guān)察者和被觀(guān)察者

// 3. 創(chuàng)建觀(guān)察者 & 定義響應(yīng)事件的行為

@Override

public void onSubscribe(Disposable d) {

Log.d(TAG, "開(kāi)始采用subscribe連接");

}

// 默認(rèn)最先調(diào)用復(fù)寫(xiě)的 onSubscribe()

@Override

public void onNext(Integer value) {

Log.d(TAG, "對(duì)Next事件"+ value +"作出響應(yīng)" );

}

@Override

public void onError(Throwable e) {

Log.d(TAG, "對(duì)Error事件作出響應(yīng)");

}

@Override

public void onComplete() {

Log.d(TAG, "對(duì)Complete事件作出響應(yīng)");

}

});

這種 基于事件流的鏈?zhǔn)秸{(diào)用,使得RxJava:

  • 邏輯簡(jiǎn)潔
  • 實(shí)現(xiàn)優(yōu)雅
  • 使用簡(jiǎn)單

更重要的是,隨著程序邏輯的復(fù)雜性提高,它依然能夠保持簡(jiǎn)潔 & 優(yōu)雅。所以,一般建議使用這種基于事件流的鏈?zhǔn)秸{(diào)用方式實(shí)現(xiàn)RxJava。

線(xiàn)程控制

在 RxJava 的默認(rèn)規(guī)則中,事件的發(fā)出和消費(fèi)都是在同一個(gè)線(xiàn)程的。也就是說(shuō),如果只用上面的方法,實(shí)現(xiàn)出來(lái)的只是一個(gè)同步的觀(guān)察者模式。觀(guān)察者模式本身的目的就是『后臺(tái)處理,前臺(tái)回調(diào)』的異步機(jī)制,因此異步對(duì)于 RxJava 是至關(guān)重要的。而要實(shí)現(xiàn)異步,則需要用到 RxJava 的另一個(gè)概念: Scheduler 。

在不指定線(xiàn)程的情況下, RxJava 遵循的是線(xiàn)程不變的原則,即:在哪個(gè)線(xiàn)程調(diào)用 subscribe(),就在哪個(gè)線(xiàn)程生產(chǎn)事件;在哪個(gè)線(xiàn)程生產(chǎn)事件,就在哪個(gè)線(xiàn)程消費(fèi)事件。如果需要切換線(xiàn)程,就需要用到 Scheduler (調(diào)度器)。

在RxJava 中,Scheduler ——調(diào)度器,相當(dāng)于線(xiàn)程控制器,RxJava 通過(guò)它來(lái)指定每一段代碼應(yīng)該運(yùn)行在什么樣的線(xiàn)程。

略...

感興趣可參考 給 Android 開(kāi)發(fā)者的 RxJava 詳解

場(chǎng)景示例

  • 需求場(chǎng)景


  • 功能邏輯
  • 代碼實(shí)現(xiàn)

public class RxJavaRetry {

private static final String TAG = "RxJava";

// 可重試次數(shù)

private int maxConnectCount = 10;

// 當(dāng)前已重試次數(shù)

private int currentRetryCount = 0;

// 重試等待時(shí)間

private int waitRetryTime = 0;

@Override

protected void onCreate(Bundle savedInstanceState) {

//省略

// 步驟3:采用Observable<...>形式 對(duì) 網(wǎng)絡(luò)請(qǐng)求 進(jìn)行封裝

Observable<Translation> observable = request.getCall();

// 步驟4:發(fā)送網(wǎng)絡(luò)請(qǐng)求 & 通過(guò)retryWhen()進(jìn)行重試

// 注:主要異常才會(huì)回調(diào)retryWhen()進(jìn)行重試

observable.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

@Override

public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {

// 參數(shù)Observable<Throwable>中的泛型 = 上游操作符拋出的異常,可通過(guò)該條件來(lái)判斷異常的類(lèi)型

return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {

@Override

public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {

// 輸出異常信息

Log.d(TAG, "發(fā)生異常 = "+ throwable.toString());

/**

  • 需求1:根據(jù)異常類(lèi)型選擇是否重試
  • 即,當(dāng)發(fā)生的異常 = 網(wǎng)絡(luò)異常 = IO異常 才選擇重試

*/

if (throwable instanceof IOException){

Log.d(TAG, "屬于IO異常,需重試" );

/**

  • 需求2:限制重試次數(shù)
  • 即,當(dāng)已重試次數(shù) < 設(shè)置的重試次數(shù),才選擇重試

*/

if (currentRetryCount < maxConnectCount){

// 記錄重試次數(shù)

currentRetryCount++;

/**

  • 需求2:實(shí)現(xiàn)重試
  • 通過(guò)返回的Observable發(fā)送的事件 = Next事件,從而使得retryWhen()重訂閱,最終實(shí)現(xiàn)重試功能
  • 需求3:延遲1段時(shí)間再重試

  • 采用delay操作符 = 延遲一段時(shí)間發(fā)送,以實(shí)現(xiàn)重試間隔設(shè)置
  • 需求4:遇到的異常越多,時(shí)間越長(zhǎng)

  • 在delay操作符的等待時(shí)間內(nèi)設(shè)置 = 每重試1次,增多延遲重試時(shí)間1s

*/

// 設(shè)置等待時(shí)間

waitRetryTime = 1000 + currentRetryCount* 1000;

return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);

}else{

// 若重試次數(shù)已 > 設(shè)置重試次數(shù),則不重試

// 通過(guò)發(fā)送error來(lái)停止重試(可在觀(guān)察者的onError()中獲取信息)

return Observable.error(new Throwable("重試次數(shù)已超過(guò)設(shè)置次數(shù) = " +currentRetryCount + ",即 不再重試"));

}

}else{

// 若發(fā)生的異常不屬于I/O異常,則不重試

// 通過(guò)返回的Observable發(fā)送的事件 = Error事件 實(shí)現(xiàn)(可在觀(guān)察者的onError()中獲取信息)

return Observable.error(new Throwable("發(fā)生了非網(wǎng)絡(luò)異常(非I/O異常)"));

}

}

});

}

}).subscribeOn(Schedulers.io()) // 切換到IO線(xiàn)程進(jìn)行網(wǎng)絡(luò)請(qǐng)求

.observeOn(AndroidSchedulers.mainThread()) // 切換回到主線(xiàn)程 處理請(qǐng)求結(jié)果

.subscribe(new Observer<Translation>() {

@Override

public void onSubscribe(Disposable d) {

}

@Override

public void onNext(Translation result) {

// 接收服務(wù)器返回的數(shù)據(jù)

Log.d(TAG, "發(fā)送成功");

result.show();

}

@Override

public void onError(Throwable e) {

// 獲取停止重試的信息

Log.d(TAG, e.toString());

}

@Override

public void onComplete() {

}

});

}

}

更多場(chǎng)景可以參考 使用RxJava的最佳開(kāi)發(fā)場(chǎng)景

HYSTRIX中的觀(guān)察者模式

Hystrix中的命令

Hystrix有兩個(gè)請(qǐng)求命令 HystrixCommand、HystrixObservableCommand。

HystrixCommand用在依賴(lài)服務(wù)返回單個(gè)操作結(jié)果的時(shí)候。又兩種執(zhí)行方式

  • execute():同步執(zhí)行。從依賴(lài)的服務(wù)返回一個(gè)單一的結(jié)果對(duì)象,或是在發(fā)生錯(cuò)誤的時(shí)候拋出異常。
  • queue();異步執(zhí)行。直接返回一個(gè)Future對(duì)象,其中包含了服務(wù)執(zhí)行結(jié)束時(shí)要返回的單一結(jié)果對(duì)象。

HystrixObservableCommand 用在依賴(lài)服務(wù)返回多個(gè)操作結(jié)果的時(shí)候。它也實(shí)現(xiàn)了兩種執(zhí)行方式

  • observe():返回Obervable對(duì)象,他代表了操作的多個(gè)結(jié)果,他是一個(gè)HotObservable
  • toObservable():同樣返回Observable對(duì)象,也代表了操作多個(gè)結(jié)果,但它返回的是一個(gè)Cold Observable。

在Hystrix的底層實(shí)現(xiàn)中大量使用了RxJava。上面提到的Observable對(duì)象就是RxJava的核心內(nèi)容之一,可以把Observable對(duì)象理解為事件源或是被觀(guān)察者,與其對(duì)應(yīng)的是Subscriber對(duì)象,可以理解為訂閱者或是觀(guān)察者。

  1. Observable用來(lái)向訂閱者Subscriber對(duì)象發(fā)布事件,Subscriber對(duì)象在接收到事件后對(duì)其進(jìn)行處理,這里所指的事件通常就是對(duì)依賴(lài)服務(wù)的調(diào)用。
  2. 一個(gè)Observable可以發(fā)出多個(gè)事件,直到結(jié)束或是發(fā)生異常。
  3. Observable對(duì)象每發(fā)出一個(gè)事件,就會(huì)調(diào)用對(duì)應(yīng)觀(guān)察者Subscriber對(duì)象的onNext()方法。
  4. 每一個(gè)Observable的執(zhí)行,最后一定會(huì)通過(guò)調(diào)用Subscriber.onCompleted()或是Subscriber.onError()來(lái)結(jié)束該事件的操作流。

調(diào)用實(shí)例

public class HelloWorldHystrixObservableCommand extends HystrixObservableCommand<String> {

private final String name;

protected HelloWorldHystrixObservableCommand(String name) {

super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));

this.name = name;

}

@Override

protected Observable<String> construct() {

System.out.println("in construct! thread:" + Thread.currentThread().getName());

return (Observable<String>) Observable.create(new Observable.OnSubscribe<String>() {

@Override

public void call(Subscriber<? super String> observer) {

try {

System.out.println("in call of construct! thread:" + Thread.currentThread().getName());

if (!observer.isUnsubscribed()) {

// 直接拋異常退出,不會(huì)往下執(zhí)行

// observer.onError(getExecutionException());

observer.onNext("Hello1" + " thread:" + Thread.currentThread().getName());

observer.onNext("Hello2" + " thread:" + Thread.currentThread().getName());

observer.onNext(name + " thread:" + Thread.currentThread().getName());

System.out.println("complete before------" + " thread:" + Thread.currentThread().getName());

// 不會(huì)往下執(zhí)行observer的任何方法

observer.onCompleted();

System.out.println("complete after------" + " thread:" + Thread.currentThread().getName());

// 不會(huì)執(zhí)行到

observer.onCompleted();

// 不會(huì)執(zhí)行到

observer.onNext("abc");

}

} catch (Exception e) {

observer.onError(e);

}

}

});

}

public static void main(String[] args) {

Observable<String> observable = new HelloWorldHystrixObservableCommand("test").observe();

observable.subscribe(new Subscriber<String>() {

public void onCompleted() {

System.out.println("completed");

}

public void onError(Throwable throwable) {

System.out.println("error-----------" + throwable);

}

public void onNext(String v) {

System.out.println("next------------" + v);

}

});

}

}

執(zhí)行結(jié)果

in construct! thread:main

in call of construct! thread:main

complete before------ thread:main

complete after------ thread:main

next------------Hello1 thread:main

next------------Hello2 thread:main

next------------test thread:main

completed

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,951評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,796評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評(píng)論 6 407
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,055評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,303評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,520評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,757評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容