java rx3x_2020年应该抛弃RxJava,使用kotlin coroutine

使用了多年go之后,再转到Java的世界,我一直对Reactive Extensions - Rx的存在感到十分困惑。

虽然不像原生js那样的callback hell,但这样的代码,难道就很好读咩?

private fun getCityDetail(cityId: Long?): Mono {

return webClient.get()

.uri("/cities/{id}", cityId!!)

.exchange()

.flatMap { response ->

val city: Mono = response.bodyToMono()

LOGGER.info("Received city..")

city

}

}

能不能解释一下flatMap跟map的不同?错误处理又怎么办?为什么似乎很多人在说这样的代码风格很好?很适合什么数据流式处理?

(flatMap跟map的区别当然很容易搞懂,上面只是举例说Rx有额外的“心智负担”。)

我就一直将信将疑,猜想可能是Java系在数据流式处理领域证明的Rx的威力,然后再将其应用到网络异步编程的领域来;总之就是,不明觉厉。

直到我深入使用Rx再认识了kotlin coroutine,才确信Rx就是Java在缺乏coroutine协程支持,搞不了async / await等之后的妥协方式,它比在js中使用promise去处理异步高明不了多少,Rx必然是等着被历史淘汰的!类似的淘汰,已经在c#、python甚至js等语言中出现过多次了。

以下的样例均取自kotlin团队的开发Team Lead Roman Elizarov在KotlinConf 2017中的演讲:

假设有这样的同步代码:

fun postItem(item: Item) {

val token = requestToken()

val post = createPost(token, item)

processPost(post)

}

这样的同步代码是存在堵塞的,并且只能通过增加线程来提高并发,而线程若是过千,程序则需要耗费大量的CPU去处理线程切换,进而造成性能低下。

解决的办法是采用异步来避免线程堵塞,然后通过异步调用callback来触发处理,但这会导致所谓的callback hell:

fun postItem(item: Item) {

requestTokenAsync { token ->

createPostAsync(token, item) { post ->

processPost(post)

}

}

}

我们可以看到每一层异步调用都会产生多一层的函数嵌套,缩进会非常难看;上面的代码实际上还是简化过的,如果加上错误处理,其可读性会更加糟糕。

而如果采用future / promise / Rx等风格的话,实际上也没有把情况改善多少:

fun postItem(item: Item) {

requestTokenAsync()

.thenCompose { token -> createPostAsync(token, item) }

.thenAccept { post -> processPost(post) }

}

从代码风格上看,它仅是讲callback hell中的函数嵌套、缩进给“拍平”;但取而代之的是各种链式调用,仿佛整个函数从此都可以“一行搞定”。

但我们在“一行代码”中堆积了过多逻辑,其维护性必然是迅速下降的,debug更会是个麻烦。每次去看Rx代码抛出来的异常堆栈,我都觉得很头痛。

而kotlin引入coroutine之后,代码则可以变成这样:

suspend fun postItem(item: Item) {

val token = requestToken()

val post = createPost(token, item)

processPost(post)

}

coroutine的版本,几乎跟最初同步代码的版本一模一样,仅仅只是函数签名前面多了一个新的关键字suspend。

错误处理也非常简单,就跟普通的同步代码一样,使用try / catch即可;而且性能也会于使用callbacak / Rx的异步代码一致。

debug也获得极大的改善,所有suspend函数的嵌套,会产生所谓的coroutine boundary,即便是在最内层抛出异常,整个coroutine堆栈都会被打印出来,跟同步代码差不多容易看懂。

kotlin coroutine包对于Java目前流行的Rx类库(reactive / reactor / rx2 / rx3等)也提供了内置的支持,我们可以很方便的将现有的Rx代码封装成为coroutine。

比方说,现有一个httpclient使用rx2返回数据:

@Client("\\${blogapi.url}")

interface BlogApi {

@Get(value = "/blog?id={blogId}")

fun blog(blogId: Int): Single

}

可以写个Service把这么个异步client封装起来:

@Singleton

class BlogService (val blogApi: BlogApi) {

suspend fun blog(blogId: Int): Blog {

return blogApi.blog(blogId).await()

}

}

其中blogApi.blog(blogId).await()是中的await 是kotlin coroutine库内置提供的rx2扩展方法。

那么,这样一来,相应的上层代码也可以做修改成为同步的风格。

从实现的角度讲,kotlin coroutine并没有在jvm层面做任何修改,简单的说,编译器只是识别suspend关键字的函数,然后进行CPS - Continuation-Passing Style转换,即把代码内部转换成为类似:

fun postItem(item: Item, cont: Continuation) {

val sm = object: CoroutineImpl{ ...}

switch(sm.label) {

case0:

sm.item= item

sm.label = 1

requestToken(sm)

case1:

createPost(token, item, sm)

case2:

processPost(post)

}

}

即自动构造了函数相应的sm - State Machine状态机,然后在发生callback的时候,重新调用函数,函数的“状态”会被保持在状态机中,而不同的状态也会使其执行不同的步骤。

状态机也是其它很多语言实现协程所选择的常见方式,而并不是kotlin独创,而kotlin coroutine在2017年在kotlinConf上被介绍到现在也过了3年,可以相信其成熟度。

Project Loom暂时依旧遥遥无期的2020年,Java程序员不应该再使用Rx,而应该拥抱kotlin,拥抱coroutine。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值