Vert.x Java开发指南——第九章 利用RxJava进行响应式编程

第九章 利用RxJava进行响应式编程

版权声明:本文为博主自主翻译,转载请标明出处。 https://blog.csdn.net/elinespace/article/details/80675371
相应代码位于本指南仓库的step-8目录下

截止目前,我们已经探索了Vert.x技术栈的多个部分,使用基于回调的API。它仅仅可以正常工作,而且这个编程模型对于开发者在许多语言中是非常熟悉的。尽管如此,它可能有点繁琐,尤其当你组合几个事件源或者处理复杂的数据流时。

而这正是RxJava闪耀的地方,Vert.x无缝的集成了它。

9.1 启用RxJava API

除了基于回调的API,Vert.x模块提供了一套“Rxified”API。为了启用它,首先需要添加vertx-rx-java模块到Maven POM文件:

<dependency> 
    <groupId>io.vertx</groupId>
    <artifactId>vertx-rx-java</artifactId>
</dependency>

Verticle现在需要修改为继承自io.vertx.rxjava.core.AbstractVerticle而不是io.vertx.core.AbstractVerticle。这有什么不同?前一个类扩展了后者,并暴露了一个io.vertx.rxjava.core.Vertx类型的属性。

io.vertx.rxjava.core.Vertx定义了额外的rxSomething(…)方法,这相当于基于回调的对等体。

让我们看一下MainVerticle,以便在实践中更好地了解它是如何工作的:

Single<String> dbVerticleDeployment = vertx.rxDeployVerticle("io.vertx.guides.wiki.database.WikiDatabaseVerticle");

rxDeploy方法没有使用一个Handler

9.2 按顺序部署Verticle

为了完成MainVerticle重构,我们必须确保部署操作被触发并按顺序发生:

dbVerticleDeployment.flatMap(id -> { ①
    Single<String> httpVerticleDeployment = vertx.rxDeployVerticle( "io.vertx.guides.wiki.http.HttpServerVerticle",new DeploymentOptions().setInstances(2));
    return httpVerticleDeployment; 
}).subscribe(id->startFuture.complete(),startFuture::fail); ②

① flatMap方法应用该函数到dbVerticleDeployment的结果。此处它调度HttpServerVerticle的部署。

② 操作是在订阅时启动。根据结果成功还是失败,MainVerticle调用startFuture的complete或者fail方法。

9.3 部分“Rxifying”的HttpServerVerticle

如果你按顺序阅读指南,并按照讲解编辑你的代码,那么你的HttpServerVerticle类依旧使用基于回调的API。在你顺理成章的使用RxJava API执行异步操作之前,如并发,你需要重构HttpServerVerticle。

9.3.1 导入Vert.x类的RxJava版本

import io.vertx.rxjava.core.AbstractVerticle; 
import io.vertx.rxjava.core.http.HttpServer; 
import io.vertx.rxjava.ext.auth.AuthProvider; 
import io.vertx.rxjava.ext.auth.User;
import io.vertx.rxjava.ext.auth.jwt.JWTAuth;
import io.vertx.rxjava.ext.auth.shiro.ShiroAuth;
import io.vertx.rxjava.ext.web.Router;
import io.vertx.rxjava.ext.web.RoutingContext;
import io.vertx.rxjava.ext.web.client.WebClient;
import io.vertx.rxjava.ext.web.client.HttpResponse; ①
import io.vertx.rxjava.ext.web.codec.BodyCodec;
import io.vertx.rxjava.ext.web.handler.*;
import io.vertx.rxjava.ext.web.sstore.LocalSessionStore; 
import io.vertx.rxjava.ext.web.templ.FreeMarkerTemplateEngine; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Single;

① 我们的backupHandler()方法依旧使用HttpResponse类,因此它必须被导入。事实证明,Vert.x提供的RxJava版本的HttpResponse可以作为这种情况下的替代。在本指南仓库step-8目录中的“Rxified”代码没有导入这个类,因为响应类型是由lambda表达式推断的。

9.3.2 在一个“Rxified” vertx实例上使用委派

当你有一个io.vertx.rxjava.core.Vertx对象,并希望对io.vertx.core.Vertx实例进行方法调用时,可以调用getDelegate()方法。Verticle的start()方法需要调整,当创建一个WikiDatabaseService时:

@Override
public void start(Future<Void> startFuture) throws Exception {
    String wikiDbQueue = config().getString(CONFIG_WIKIDB_QUEUE, "wikidb.queue");
    dbService = io.vertx.guides.wiki.database.WikiDatabaseService.createProxy(vertx.getDelegate(), wikiDbQueue);

9.4 并发执行授权查询

在前面的例子中,我们看到了如何使用RxJava和Rxified Vert.x API按顺序执行异步操作。但是有时候这种保证(译者注:指按顺序执行异步操作)是不需要的,或许你只是出于性能原因需要它们简单的并发运行。

HttpServerVerticle的JWT令牌生成过程是这种情况的一个好例子。为了创建一个令牌,我们需要所有的授权查询结果完成,但是查询是相互独立的:

auth.rxAuthenticate(creds).flatMap(user -> { 
    Single<Boolean> create = user.rxIsAuthorised("create"); ① 
    Single<Boolean> delete = user.rxIsAuthorised("delete"); 
    Single<Boolean> update = user.rxIsAuthorised("update");
    return Single.zip(create, delete, update, (canCreate, canDelete, canUpdate) -> { ②
        return jwtAuth.generateToken(
            new JsonObject()
                .put("username", context.request().getHeader("login")) .put("canCreate", canCreate)
                .put("canDelete", canDelete)
                .put("canUpdate", canUpdate),
            new JWTOptions() .setSubject("Wiki API") .setIssuer("Vert.x"));
    });
}).subscribe(token -> {
    context.response().putHeader("Content-Type", "text/plain").end(token); 
}, t -> context.fail(401));

① 创建了三个Single对象,表示不同的授权查询。

② 当三个操作成功完成时,执行zip操作的回调方法,使用前面三个操作的结果。

9.5 使用数据库链接

为了从池中获取一个数据库链接,所有你需要做的就是调用JDBCClient上的rxGetConnection方法:

Single<SQLConnection> connection = dbClient.rxGetConnection();

这个方法返回了一个Single,你可以轻易使用flatMap变换来执行SQL查询:

Single<ResultSet> resultSet = dbClient.rxQueryWithParams( sqlQueries.get(SqlQuery.GET_PAGE_BY_ID), new JsonArray().add(id));

但是,如果SQLConnection引用不再可达,我们怎么释放该链接?一个简单而且方便的方法是当Single取消订阅时执行close:

private Single<SQLConnection> getConnection() { 
    return dbClient.rxGetConnection().flatMap(conn -> {
        Single<SQLConnection> connectionSingle = Single.just(conn); ①
        return connectionSingle.doOnUnsubscribe(conn::close); ②
    });
}

① 在获取链接之后,我们将其封装为一个Single。

② Single修改为当取消订阅时,调用close。

现在我们可以在数据库Verticle中任何需要执行SQL查询的时候使用getConnection。

9.6 消除回调和RxJava之间的差距

有时,你可能必须混合RxJava代码和基于回调的API。例如,服务代理接口只能定义为回调的方式,但是它的实现使用了Vert.x Rxified API。

这种情况下,io.vertx.rx.java.RxHelper类可以适配Handler

@Override
public WikiDatabaseService fetchAllPagesData(Handler<AsyncResult<List<JsonObject>>> resultHandler) { ① 
    dbClient.rxQuery(sqlQueries.get(SqlQuery.ALL_PAGES_DATA))
      .map(ResultSet::getRows)
      .subscribe(RxHelper.toSubscriber(resultHandler)); ②
    return this;
}

① fetchAllPagesData是一个异步服务代理操作,其定义使用了Handler

9.7 数据流

RxJava不仅是合并不同事件来源的伟大工具,它对于数据流也非常有帮助。不像Vert.x future或者JDK future,Observable发出一个事件流,而不仅是一个单独的事件,并且它拥有一个广泛的数据操作运算集。

我们可以使用这些操作中一些来重构数据库Verticle中的fetchAllPages方法:

public WikiDatabaseService fetchAllPages(Handler<AsyncResult<JsonArray>> resultHandler) { 
    dbClient.rxQuery(sqlQueries.get(SqlQuery.ALL_PAGES))
        .flatMapObservable(res -> { ① 
            List<JsonArray> results = res.getResults();
            return Observable.from(results); ② 
        })
        .map(json->json.getString(0)) ③
        .sorted() ④
        .collect(JsonArray::new, JsonArray::add) ⑤
        .subscribe(RxHelper.toSubscriber(resultHandler));
    return this; 
}

① 通过flatMapObservable,我们可以使用Single发出的条目创建一个Observable。

② from将数据库results迭代转换成一个Observable,该Observable发出数据库行条目。

③ 由于我们只需要页面名称,我们可以map每个JsonObject行到首列。

④ 客户端希望数据按照字母表顺序sorted。

⑤ 事件总线服务应答包含在一个单独的JsonArray中。collect方法通过JsonArray::new创建一个新的对象,然后当条目发出时通过JsonArray::add方法添加它们。

猜你喜欢

转载自blog.csdn.net/elinespace/article/details/80675371