书接上回,我们认识了Quarkus
的依赖注入的特性。这一回,我们了解下订阅的概念。
Subscribe
订阅,在
Reactive
编程中经常遇见这个概念。
我们先来看一段代码。如下,是一个查询接口,检索所有的品牌信息,并返回。
@Inject
BrandInfoRepository brandInfoRepository;
@Route(path = "/brandInfos", methods = HttpMethod.GET)
void brandInofs2(RoutingContext rc) {
brandInfoRepository.findAll()
.list()
.subscribe()
.with(brandInfos -> {
LOGGER.info("size:{}", brandInfos.size());
handleJson(rc, brandInfos);
},
t -> handleFail(rc, t));
}
而其中的subscribe()
方法就是订阅。我们将上面的链式写法的代码铺开。
@Inject
BrandInfoRepository brandInfoRepository;
@Route(path = "/brandInfos", methods = HttpMethod.GET)
void brandInfos(RoutingContext rc) {
PanacheQuery<BrandInfo> query = brandInfoRepository.findAll();
Uni<List<BrandInfo>> listUni = query.list();
UniSubscribe<List<BrandInfo>> uniSubscribe = listUni.subscribe();
Consumer<List<BrandInfo>> listConsumer = brandInfos -> {
LOGGER.info("btandInfos size: {}", brandInfos.size());
handleJson(rc, brandInfos);
};
Consumer<Throwable> throwableConsumer = t -> handleFail(rc, t);
uniSubscribe.with(listConsumer, throwableConsumer);
}
- 第1行代码返回PanacheQuery,这只是在组建有效的查询条件,并没有发起查询。
- 第2行代码返回Uni,用于接受0或者1个结果的异步操作。
- 第3行代码返回UniSubscribe,这是一个订阅。(我们订阅了Uni,触发计算)
- 第4-7行代码返回Consumer,成功时的处理方法。
- 第8行代码返回Consumer,失败时的处理方法。
- 第9行代码调用
.with
方法,告知成功时和失败时的处理方法。
Uni
Uni允许接受0或者1个异步结果。
/**
* A {@link Uni} represents a lazy asynchronous action. It follows the subscription pattern, meaning that the action
* is only triggered once a {@link UniSubscriber} subscribes to the {@link Uni}.
* <p>
* A {@link Uni} can have two outcomes:
* <ol>
* <li>An {@code item} event, forwarding the completion of the action (potentially {@code null} if the item
* does not represent a value, but the action was completed successfully)</li>
* <li>A {@code failure} event, forwarding an exception</li>
* </ol>
* <p>
* To trigger the computation, a {@link UniSubscriber} must subscribe to the Uni. It will be notified of the outcome
* once there is an {@code item} or {@code failure} event fired by the observed Uni. A subscriber receives
* (asynchronously) a {@link UniSubscription} and can cancel the demand at any time. Note that cancelling after
* having received the outcome is a no-op.
* <p>
*
* @param <T> the type of item produced by the {@link Uni}
*/
public interface Uni<T> {}
我们并不知道Uni到底返回的结果是0个还是1个。而Uni也是Quarkus最常用的异步返回结果。
- Uni是一个惰性的异步操作,遵循订阅模式。
- 只能触发一次订阅。
- 只有两种结果,一是返回了item代表操作完成;另外是返回了一个异常。
- 要触发计算,那必须订阅Uni。
返回一个元素
Uni<BrandInfo> findOne(String id);
返回一个集合
Uni<List<BrandInfo>> findAll();
后续操作
Quarkus
对Uni
提供了丰富的方法来进行后续的处理,包含的API大致分为创建数据
、事件方法
、转换方法
。
常用的后续操作一般有两种:item 和 subscribe
Item
@Route(path = "/brandInfos3", methods = HttpMethod.GET)
Uni<List<BrandInfo>> brandInfos3(RoutingContext rc) {
return brandInfoRepository.findAll().list()
// 成功拿到非空的Item
.onItem().ifNotNull().transformToUni(entity -> {
return Uni.createFrom().item(entity);
})
// 如果Item为空,那么fail
.onItem().ifNull().fail();
}
如果能拿到Item,那么直接进行后续的操作。
subscribe
@Route(path = "/brandInfos2", methods = HttpMethod.GET)
void brandInofs2(RoutingContext rc) {
brandInfoRepository.findAll()
.list()
.subscribe()
.with(brandInfos -> {
LOGGER.info("size:{}", brandInfos.size());
handleJson(rc, brandInfos);
},
t -> handleFail(rc, t));
}
转换为UniSubscribe
,采用订阅的模式去处理。
UniSubscribe
UniSubscribe的目的是以订阅的方式去处理异步返回的结果。通常可以将Uni转换为订阅的方式来处理我们的业务代码。
/**
* Allow subscribing to a {@link Uni} to be notified of the different events coming from {@code upstream}.
* Two kind of events can be received:
* <ul>
* <li>{@code item} - the item of the {@link Uni}, can be {@code null}</li>
* <li>{@code failure} - the failure propagated by the {@link Uni}</li>
* </ul>
*
* @param <T> the type of item
*/
public class UniSubscribe<T> {
}
允许订阅Uni
,针对upstream
,我们可以采取不同的事件进行处理。
当异步结果返回后,UniSubscribe
接受两种事件(也可以说是消费者):
- 返回的结果,也就是
Uni
中的Item
,这个值可以为null
。 - 返回的异常,失败了,不多说。
闲话
从编码体验上来说,还是推荐使用订阅的模式去进行数据操作,毕竟订阅、消费者的概念都比较熟悉。我们口述下Quarkus
异步操作的过程:得到了一个Uni后,我们订阅了Uni,这时候触发了Uni中获取item的计算,在with中声明了成功或者失败的处理方法。
主要业务代码体现在消费者
的处理过程中,整体设计还是比较优雅的。