Reactor 是Reactive Programming规范的一个具体实现(rxjava也是规范的一个实现),可以概括为:响应式编程是一种涉及数据流和变化传播的异步编程范例。这意味着可以通过所采用的编程语言轻松地表达静态(例如阵列)或动态(例如事件发射器)数据流。
jdk stream总纲:
Java 8 Stream 旨在有效地处理数据流(包括原始类型),这些数据流可以在没有延迟或很少延迟的情况下访问。它是基于拉的,只能使用一次,缺少与时间相关的操作,并且可以执行并行计算,但无法指定要使用的线程池。但是它还没有设计用于处理延迟操作,例如I / O操作。其所不支持的特性就是Reactor或RxJava等Reactive API的用武之地。
Reactor 或 Rxjava等反应性API也提供Java 8 Stream等运算符,但它们更适用于任何流序列(不仅仅是集合),并允许定义一个转换操作的管道,该管道将应用于通过它的数据,这要归功于方便的流畅API和使用lambdas。它们旨在处理同步或异步操作,并允许您缓冲,合并,连接或对数据应用各种转换。
Java提供了两种异步编程模型:
但是上面两种方法都有局限性。首先多个callback难以组合在一起,很快导致代码难以阅读以及难以维护。
我们可以看下一个例子:在用户的UI上展示用户喜欢的top 5个商品的详细信息,如果不存在的话则调用推荐服务获取5个;这个功能的实现需要三个服务支持:一个是获取用户喜欢的商品的ID的接口(userService.getFavorites),第二个是获取商品详情信息接口(favoriteService.getDetails),第三个是推荐商品与商品详情的服务(suggestionService.getSuggestions),基于callback模式实现上面功能代码如下:
userService.getFavorites(userId, new Callback<List<String>>() { // 1
public void onSuccess(List<String> list) { // 2
if (list.isEmpty()) { // 3
suggestionService.getSuggestions(new Callback<List<Favorite>>() {
public void onSuccess(List<Favorite> list) { // 4
UiUtils.submitOnUiThread(() -> { // 5
list.stream()
.limit(5)
.forEach(uiList::show); // 6
});
}
public void onError(Throwable error) { // 7
UiUtils.errorPopup(error);
}
});
} else {
list.stream() // 8
.limit(5)
.forEach(favId -> favoriteService.getDetails(favId, // 9
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);
}
});
如上为了实现该功能,我们写了很多代码,使用了大量callback,这些代码比较晦涩难懂,并且存在代码重复,下面我们使用Reactor来实现等价的功能:
userService.getFavorites(userId) // 1
.flatMap(favoriteService::getDetails) // 2
.switchIfEmpty(suggestionService.getSuggestions()) // 3
.take(5) // 4
.publishOn(UiUtils.uiThreadScheduler()) // 5
.subscribe(uiList::show, UiUtils::errorPopup); // 6
另外如果你想在少于800毫秒的时间内检索到喜欢的ID,或者如果花费更长的时间从缓存中获取它们,在Reactor中,可以添加具有超时和回退的Reactor代码例子:
userService.getFavorites(userId)
.timeout(Duration.ofMillis(800)) //如果没有在800ms内响应,就抛出错误。
.onErrorResume(cacheService.cachedFavoritesFor(userId)) //如果发生错误,退回到cacheService
.flatMap(favoriteService::getDetails)
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.publishOn(UiUtils.uiThreadScheduler())
.subscribe(uiList::show, UiUtils::errorPopup);
如上代码可知基于reactor编写的代码逻辑属于声明式编程,比较通俗易懂,代码量也比较少,不含有重复的代码。
future模型相比callback模型要好一些,但尽管CompletableFuture在Java 8上进行了改进,但它们仍然表现不佳。Future通过调用get()方法很容易导致对象的另一种阻塞情况,另外Future不支持惰性计算,并且缺乏对多个值和高级错误处理的支持。
例如下面例子:首先我们获取一个id列表,然后根据id分别获取对应的name和统计数据,然后组合每个id对应的name和统计数据为一个新的数据,最后输出所有组合对的值,下面我们使用CompletableFuture来实现这个功能,以便保证整个过程是异步的,并且每个id对应的处理是并发的:
CompletableFuture<List<String>> ids = ifhIds(); // 1
CompletableFuture<List<String>> result = ids.thenComposeAsync(l -> { // 2
Stream<CompletableFuture<String>> zip =
l.stream().map(i -> { // 3
CompletableFuture<String> nameTask = ifhName(i); // 3.1
CompletableFuture<Integer> statTask = ifhStat(i); // 3.2
return nameTask.
thenCombineAsync(statTask,
(name, stat) -> "Name " + name + " has stats " + stat); // 3.3
});
List<CompletableFuture<String>> combinationList = zip.collect(Collectors.toList()); // 4
CompletableFuture<String>[] combinationArray = combinationList.toArray(new CompletableFuture[combinationList.size()]); //5
CompletableFuture<Void> allDone = CompletableFuture.allOf(combinationArray); // 6
return allDone.thenApply(v -> combinationList.stream() // 7
.map(CompletableFuture::join)
.collect(Collectors.toList()));
});
List<String> results = result.join(); // 8
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提供了更多的组合运算符,因此可以简化此过程,如下所示:
Flux<String> ids = ifhrIds(); // 1
Flux<String> combinations =
ids.flatMap(id -> { // 2
Mono<String> nameTask = ifhrName(id); // 2.1
Mono<Integer> statTask = ifhrStat(id); // 2.2
return nameTask.zipWith(statTask,
(name, stat) -> "Name " + name + " has stats " + stat); // 3
});
Mono<List<String>> result = combinations.collectList(); // 4
List<String> results = result.block(); // 5
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方式编写的代码相比使用CompletableFuture实现相同功能来说,更简洁,更通俗易懂。
诸如Reactor之类的stream库旨在解决JVM上“经典”异步方法的这些缺点,同时还关注一些其他方面:
可组合性,指的是编排多个异步任务的能力,使用先前任务的结果作为后续任务的输入或以fork-join方式执行多个任务。
编排任务的能力与代码的可读性和可维护性紧密相关。随着异步过程层数量和复杂性的增加,能够编写和读取代码变得越来越困难。正如我们所看到的,callback模型很简单,但其主要缺点之一是,对于复杂的处理,您需要从回调执行回调,本身嵌套在另一个回调中,依此类推。那个混乱被称为Callback Hell,正如你可以猜到的(或者从经验中得知),这样的代码很难维护;
引用: https://projectreactor.io/docs/core/release/reference/index.html#intro-reactive