《Kotlin 反应式编程》使用 RxKotlin 实现一个极简的 http DSL ( Reactive Programming Using Rx Kotlin )《Kotlin 反应式编程》使用
《Kotlin 反应式编程》使用 RxKotlin 实现一个极简的 http DSL Reactive Programming Using Rx Kotlin
https://github.com/ReactiveX/RxKotlin
RxKotlin: RxJava bindings for Kotlin
使用 RxKotlin 实现一个极简的 http DSL ( Reactive Programming Using Rx Kotlin )
我们现在已经基本知道 Kotlin 中 DSL 的样子了。但是这些 DSL 都是怎样实现的呢?本节我们就通过实现一个极简的http DSL来学习创建 DSL 背后的基本原理。
在这里我们对 OkHttp 做一下简单的封装,实现一个类似 jquery 中的 Ajax 的 http 请求的DSL。
OkHttp 是一个成熟且强大的网络库,在Android源码中已经使用OkHttp替代原先的HttpURLConnection。很多著名的框架例如Picasso、Retrofit也使用OkHttp作为底层框架。
提示: 更多关于OkHttp 的使用可参考: http://square.github.io/okhttp/
创建 Kotlin Gradle 项目
我们首先使用 IDEA 创建 Kotlin Gradle 项目
螢幕快照 2017-07-23 18.43.04.png
然后,在 build.gradle 里面配置依赖
compile 'com.github.ReactiveX:RxKotlin:2.1.0'
compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.8.1'
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.35'
其中,RxKotlin是ReactiveX 框架对 Kotlin 语言的支持库。我们这里主要用RxKotlin来进行请求回调的异步处理。
我们使用的是 'com.github.ReactiveX:RxKotlin:2.1.0' , 这个库是在 https://jitpack.io 上,所以我们在repositories配置里添加 jitpack 仓库
repositories {
maven { url 'https://jitpack.io' }
...
}
RxKotlin
ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展,由微软的架构师Erik Meijer领导的团队开发,在2012年11月开源。
Rx扩展了观察者模式用于支持数据和事件序列。Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步I/O(非阻塞)数据流。
Rx库支持.NET、JavaScript和C++ 。Rx近几年越来越流行,现在已经支持几乎全部的流行编程语言了。一个语言列表如下所示:
Rx 支持的编程语言 |
项目主页 |
---|---|
Java |
RxJava : https://github.com/ReactiveX/RxJava |
JavaScript |
RxJS:https://github.com/ReactiveX/rxjs |
C# |
Rx.NET:https://github.com/Reactive-Extensions/Rx.NET |
C#(Unity) |
UniRx:https://github.com/neuecc/UniRx |
Scala |
RxScala:https://github.com/ReactiveX/RxScala |
Clojure |
RxClojure:https://github.com/ReactiveX/RxClojure |
C++ |
RxCpp:https://github.com/Reactive-Extensions/RxCpp |
Lua |
RxLua:https://github.com/bjornbytes/RxLua |
Ruby |
Rx.rb:https://github.com/Reactive-Extensions/Rx.rb |
Python: |
RxPY:https://github.com/ReactiveX/RxPY |
Go |
RxGo:https://github.com/ReactiveX/RxGo |
Groovy |
RxGroovy:https://github.com/ReactiveX/RxGroovy |
JRuby |
RxJRuby:https://github.com/ReactiveX/RxJRuby |
Kotlin |
RxKotlin:https://github.com/ReactiveX/RxKotlin |
Swift |
RxSwift:https://github.com/kzaher/RxSwift |
PHP |
RxPHP:https://github.com/ReactiveX/RxPHP |
Elixir |
reaxive:https://github.com/alfert/reaxive |
Dart |
RxDart:https://github.com/ReactiveX/rxdart |
Rx的大部分语言库由ReactiveX这个组织负责维护。Rx 比较流行的库有RxJava/RxJS/Rx.NET等,当然未来RxKotlin也必将更加流行。
提示: Rx 的社区网站是: http://reactivex.io/ 。 Github 地址:https://github.com/ReactiveX/
Http请求对象封装类
首先我们设计Http请求对象封装类如下
class HttpRequestWrapper {
var url: String? = null
var method: String? = null
var body: RequestBody? = null
var timeout: Long = 10
internal var success: (String) -> Unit = {}
internal var fail: (Throwable) -> Unit = {}
fun success(onSuccess: (String) -> Unit) {
success = onSuccess
}
fun error(onError: (Throwable) -> Unit) {
fail = onError
}
}
HttpRequestWrapper的成员变量和函数说明如下表
成员 |
说明 |
---|---|
url |
请求 url |
method |
请求方法,例如 Get、Post 等,不区分大小写 |
body |
请求头,为了简单起见我们直接使用 OkHttp的RequestBody类型 |
timeout |
超时时间ms,我们设置了默认值是10s |
success |
请求成功的函数变量 |
fail |
请求失败的函数变量 |
fun success(onSuccess: (String) -> Unit) |
请求成功回调函数 |
fun error(onError: (Throwable) -> Unit) |
请求失败回调函数 |
http 执行引擎
我们直接调用 OkHttp 的 Http 请求 API
private fun call(wrap: HttpRequestWrapper): Response {
var req: Request? = null
when (wrap.method?.toLowerCase()) {
"get" -> req = Request.Builder().url(wrap.url).build()
"post" -> req = Request.Builder().url(wrap.url).post(wrap.body).build()
"put" -> req = Request.Builder().url(wrap.url).put(wrap.body).build()
"delete" -> req = Request.Builder().url(wrap.url).delete(wrap.body).build()
}
val http = OkHttpClient.Builder().connectTimeout(wrap.timeout, TimeUnit.MILLISECONDS).build()
val resp = http.newCall(req).execute()
return resp
}
它返回请求的响应对象Response。
我们在OkHttpClient.Builder().connectTimeout(wrap.timeout, TimeUnit.MILLISECONDS).build()
中设置超时时间的单位是 TimeUnit.MILLISECONDS
。
我们通过wrap.method?.toLowerCase()
处理请求方法的大小写的兼容。
使用 RxKotlin 完成请求响应的异步处理
我们首先新建一个数据发射源:一个可观察对象(Observable),作为发射数据用
val sender = Observable.create<Response>({
e ->
e.onNext(call(wrap))
})
其中,e 的类型是 io.reactivex.Emitter
(发射器),它的接口定义是
public interface Emitter<T> {
void onNext(@NonNull T value);
void onError(@NonNull Throwable error);
void onComplete();
}
其方法功能简单说明如下:
方法 |
功能 |
---|---|
onNext |
发射一个正常值数据(value) |
onError |
发射一个Throwable异常 |
onComplete |
发射一个完成的信号 |
这里,我们通过调用onNext方法,把 OkHttp 请求之后的响应对象Response 作为正常值发射出去。
然后我们再创建一个数据接收源:一个观察者(Observer)
val receiver: Observer<Response> = object : Observer<Response> {
override fun onNext(resp: Response) {
wrap.success(resp.body()!!.string())
}
override fun onError(e: Throwable) {
wrap.fail(e)
}
override fun onSubscribe(d: Disposable) {
}
override fun onComplete() {
}
}
receiver 的 onNext 函数接收 sender 发射过来的数据 Response, 然后我们在函数体内,调用这个响应对象,给 wrap.success 回调函数进行相关的赋值操作。同样的,onError 函数中也执行相应的赋值操作。
最后,通过 subscribe 订阅函数来绑定 sender 与 receiver 的关联:
sender.subscribe(receiver)
作为接收数据的 receiver (也就是 观察者 (Observer) ),对发送数据的 sender (也就是可被观察对象( Observable)) 所发射的数据或数据序列作出响应。
这种模式可以极大地简化并发操作,因为它创建了一个处于待命状态的观察者,在未来某个时刻响应 sender 的通知,而不需要阻塞等待 sender 发射数据。这个很像协程中的通道编程模型。
DSL主函数 ajax
我们的ajax DSL主函数设计如下:
fun ajax(init: HttpRequestWrapper.() -> Unit) {
val wrap = HttpRequestWrapper()
wrap.init()
doCall(wrap)
}
其中,参数init: HttpRequestWrapper.() -> Unit
是一个带接收者的函数字面量,它的类型是init = Function1<com.kotlin.easy.HttpRequestWrapper, kotlin.Unit>
。 HttpRequestWrapper是扩展函数init()
的接收者,点号 .
是扩展函数修饰符。
我们在函数体内直接调用了这个函数字面量 wrap.init()
。这样的写法可能比较难以理解,这个函数字面量 init 的调用实际上是 init.invoke(wrap)
,就是把传入 ajax 的函数参数直接传递给 wrap 。为了更简单的理解这个 init 函数的工作原理,我们通过把上面的 ajax 函数的代码反编译成对应的 Java 代码如下:
public static final void ajax(@NotNull Function1 init) {
Intrinsics.checkParameterIsNotNull(init, "init");
HttpRequestWrapper wrap = new HttpRequestWrapper();
init.invoke(wrap);
doCall(wrap);
}
也就是说,ajax 函数的一个更容易理解的写法是
fun ajax(init: HttpRequestWrapper.() -> Unit) {
val wrap = HttpRequestWrapper()
init.invoke(wrap)
doCall(wrap)
}
我们在实际应用的时候,可以直接把 init 写成Lambda 表达式的形式,因为接收者类型HttpRequestWrapper 可以从上下文推断出来。
我们这样调用 ajax 函数:
ajax {
url = testUrl
method = "get"
success {
string ->
println(string)
Assert.assertTrue(string.contains("百度一下"))
}
error {
e ->
println(e.message)
}
}
下面是几个测试代码示例:
package com.kotlin.easy
import com.alibaba.fastjson.JSONObject
import okhttp3.MediaType
import okhttp3.RequestBody
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/**
* Created by jack on 2017/7/23.
*/
@RunWith(JUnit4::class)
class KAjaxTest {
@Test fun testHttpOnSuccess() {
val testUrl = "https://www.baidu.com"
ajax {
url = testUrl
method = "get"
success {
string ->
println(string)
Assert.assertTrue(string.contains("百度一下"))
}
error {
e ->
println(e.message)
}
}
}
@Test fun testHttpOnError() {
val testUrl = "https://www2.baidu.com"
ajax {
url = testUrl
method = "get"
success {
string ->
println(string)
}
error {
e ->
println(e.message)
Assert.assertTrue("connect timed out" == e.message)
}
}
}
@Test fun testHttpPost() {
var json = JSONObject()
json.put("name", "Kotlin DSL Http")
json.put("owner", "Kotlin")
val postBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json.toString())
ajax {
url = "saveArticle"
method = "post"
body = postBody
success {
string ->
println(string)
}
error {
e ->
println(e.message)
}
}
}
@Test fun testLambda() {
val testUrl = "https://www.baidu.com"
val init: HttpRequestWrapper.() -> Unit = {
this.url = testUrl
this.method = "get"
this.success {
string ->
println(string)
Assert.assertTrue(string.contains("百度一下"))
}
this.error {
e ->
println(e.message)
}
}
ajax(init)
}
到这里,我们已经完成了一个极简的 Kotlin Ajax DSL。
本节工程源码: https://github.com/EasyKotlin/chatper14_kotlin_dsl_http
本章小结
相比于Java,Kotlin对函数式编程的支持更加友好。Kotlin 的扩展函数和高阶函数(Lambda 表达式),为定义Kotlin DSL提供了核心的特性支持。
使用DSL的代码风格,可以让我们的程序更加直观易懂、简洁优雅。如果使用Kotlin来开发项目的话,我们完全可以去尝试一下。
- Selenium2+python自动化56-unittest之断言(assert)
- 长文 | 手把手教你如何使用python进行数据分析(最好将文章代码自己码一遍)
- 回归与梯度下降法及实现原理
- 【宅男宅女们的福音】电影天堂最新电影爬取及搜索脚本
- 把模块有关联的放在一个文件夹中 在python2中调用文件夹名会直接失败 在python3中调用会成功,但是调用不能成功的解决方案
- numpy用法小结
- 凯撒密码加解密及破解实现原理
- linux bash Shell脚本经典 Fork炸弹演示及命令详解
- python易错盲点排查之+=与+的区别分析以及一些赋值运算踩过的坑
- Selenium2+python自动化57-捕获异常(NoSuchElementException)
- 你真的会用ABAP, Java和JavaScript里的constructor么?
- 【Python学习笔记之三】lambda表达式用法小结
- Selenium2+python自动化58-读取Excel数据(xlrd)
- 软件测试金字塔
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux 块设备驱动代码编写
- ubuntu中终端命令提示符太长的修改方法汇总
- CentOS 6.5 环境实现本地局域网搭建YUM的方法【基于FTP】
- iPhone手机越狱-逆向砸壳-代码注入
- Flutter基础widgets教程-SizedBox篇
- 详解linux 看门狗驱动编写
- CentOS 6.5平台本地YUM配置的方法
- Linux环境(CentOS6.7 64位)下安装subversion1.9.5的方法
- CentOS 6.5平台实现快速部署FTP的方法
- Linux系统中sudo命令的十个技巧总结
- 详解linux电源管理驱动编写
- CentOS6.5系统简单安装与配置Nginx服务器的方法
- 详解linux 摄像头驱动编写
- Ubuntu16.04搭建NFS 文件共享服务器的方法
- 详解linux pwm驱动编写