一、什麼是響應式編程(Reactive Progarmming)
響應式編程(Reactive Progarmming)是一種面向數據流和變化傳播的編程範式。
響應式編程主要處理二個問題:
1、異步非阻塞
2、流速控制
Reactive Progarmming模型:
二、響應式編程的優勢
Java 提供了兩種異步編程方式:
1、回調(Callbacks) :異步方法沒有返回值,而是採用一個 callback 作爲參數(lambda 或匿名類),當結果出來後回調這個 callback。常見的例子比如 Swings 的 EventListener。
2、Futures(這個方法是阻塞的) :異步方法 立即 返回一個 Future<T>,該異步方法要返回結果的是 T 類型,通過Future`封裝。這個結果並不是 *立刻* 可以拿到,而是等實際處理結束纔可用。比如, `ExecutorService 執行 Callable<T> 任務時會返回 Future 對象。
官方示例:
回調地獄 Callback Hell:獲取前5的建議,如果沒有建議,則採用默認的建議,傳統的回調寫法
userService.getFavorites(userId, new Callback<List<String>>() {
public void onSuccess(List<String> list) {
if (list.isEmpty()) {
suggestionService.getSuggestions(new Callback<List<Favorite>>() {
public void onSuccess(List<Favorite> list) {
UiUtils.submitOnUiThread(() -> {
list.stream()
.limit(5)
.forEach(uiList::show);
});
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
} else {
list.stream()
.limit(5)
.forEach(favId -> favoriteService.getDetails(favId,
new Callback<Favorite>() {
public void onSuccess(Favorite details) {
UiUtils.submitOnUiThread(() -> uiList.show(details));
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
}
));
}
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
Reactor的寫法
userService.getFavorites(userId)
.flatMap(favoriteService::getDetails)
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.publishOn(UiUtils.uiThreadScheduler())
.subscribe(uiList::show, UiUtils::errorPopup);
Reactor 中增加超時控制的例子
userService.getFavorites(userId)
// 數據在800ms內獲得
.timeout(Duration.ofMillis(800))
// 如果超時/異常,從緩存中獲取
.onErrorResume(cacheService.cachedFavoritesFor(userId))
.flatMap(favoriteService::getDetails)
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.publishOn(UiUtils.uiThreadScheduler())
.subscribe(uiList::show, UiUtils::errorPopup);
CompletableFuture的寫法(異步阻塞)
CompletableFuture<List<String>> ids = ifhIds();
CompletableFuture<List<String>> result = ids.thenComposeAsync(l -> {
Stream<CompletableFuture<String>> zip =
l.stream().map(i -> {
CompletableFuture<String> nameTask = ifhName(i);
CompletableFuture<Integer> statTask = ifhStat(i);
return nameTask.thenCombineAsync(statTask, (name, stat) -> "Name " + name + " has stats " + stat);
});
List<CompletableFuture<String>> combinationList = zip.collect(Collectors.toList());
CompletableFuture<String>[] combinationArray = combinationList.toArray( new CompletableFuture[combinationList.size()]);
CompletableFuture<Void> allDone = CompletableFuture.allOf(combinationArray);
return allDone.thenApply(v -> combinationList.stream() .map(CompletableFuture::join)
.collect(Collectors.toList()));
});
List<String> results = result.join();
assertThat(results).contains(
"Name NameJoe has stats 103",
"Name NameBart has stats 104",
"Name NameHenry has stats 105",
"Name NameNicole has stats 106",
"Name NameABSLAJNFOAJNFOANFANSF has stats 121");
Reactor 實現與 Future 同樣功能的代碼
Flux<String> ids = ifhrIds();
Flux<String> combinations =
ids.flatMap(id -> {
Mono<String> nameTask = ifhrName(id);
Mono<Integer> statTask = ifhrStat(id);
return nameTask.zipWith(statTask,
(name, stat) -> "Name " + name + " has stats " + stat);
});
Mono<List<String>> result = combinations.collectList();
List<String> results = result.block();
assertThat(results).containsExactly(
"Name NameJoe has stats 103",
"Name NameBart has stats 104",
"Name NameHenry has stats 105",
"Name NameNicole has stats 106",
"Name NameABSLAJNFOAJNFOANFANSF has stats 121"
);
你可以通過代碼對比,發現Reactor的代碼可讀性、可編排性更強、並且提供了背壓的支持。單次請求的處理耗時並不能得到有效提升,但是你可以用固定數量的線程和較少的內存實現擴展
三、Reactor基礎特性
Reactor 引入了實現 Publisher 的響應式類 Flux 和 Mono,以及豐富的操作方式。一個 Flux 對象代表一個包含 0..N 個元素的響應式序列,而一個 Mono 對象代表一個包含 零/一個(0..1)元素的結果。
Flux 對象代表一個包含 0..N 個元素的響應式序列
Mono 對象代表一個包含 零/一個(0..1)元素的結果
看完這篇文章,你應該要對Reactor有個基礎認知,爲後期的Spring WebFlux的實戰打下基礎,talk is cheap, show me the code,like
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpiderConfigApplication.class)
@Slf4j
public class ProductInfoControllerTest {
/**
* webflux test demo
*/
@Test
public void testDemo() {
log.info("getProductInfoByProductCode result is {}", WebClient
.create()
.get()
.uri("http://localhost:20501/product/getProductInfoByProductCode?productCode={productCode}", "PC000014")
.retrieve()
.bodyToMono(DataResult.class)
.block()
.feignData());
}
/**
* 測試字段冗餘問題
*/
@Test
public void testEnv() {
log.info("getChannelVerifyInfo result is {}", WebClient
.create()
.get()
.uri("http://localhost:20501/product/getChannelVerifyInfo/{channelId}", 1)
.retrieve()
.bodyToMono(DataResult.class)
.block()
.feignData());
log.info("getChannelInfoByCodeAndPlateform result is {}", WebClient
.create()
.get()
.uri(uriBuilder ->
uriBuilder
.scheme("http")
.host("localhost")
.port(20501)
.path("/product/getChannelInfoByCodeAndPlateform")
.queryParam("productCode", "3")
.queryParam("plateform", 2)
.build())
.retrieve()
.bodyToMono(DataResult.class)
.block()
.feignData());
}
/**
* 需要模擬文件上傳
*/
@Test
public void testUploadLimit200() throws MalformedURLException {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("file", new UrlResource(" file:///D:\\參數文件導入模板.xlsx"));
log.info("importCustomParameters result is {}", WebClient
.create()
.post()
.uri("http://localhost:20501/product/importCustomParameters")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(parts))
.retrieve()
.bodyToMono(DataResult.class)
.block()
.feignData());
}
參考:
https://projectreactor.io/
https://blog.csdn.net/get_set/article/details/79480233