《Kotlin 反應式編程》使用 RxKotlin 實現一個極簡的 http DSL

《Kotlin 反應式編程》使用 RxKotlin 實現一個極簡的 http DSL Reactive Programming Using Rx Kotlin

https://github.com/ReactiveX/...html

RxKotlin: RxJava bindings for Kotlinreact

使用 RxKotlin 實現一個極簡的 http DSL ( Reactive Programming Using Rx Kotlin )

咱們如今已經基本知道 Kotlin 中 DSL 的樣子了。可是這些 DSL 都是怎樣實現的呢?本節咱們就經過實現一個極簡的http DSL來學習建立 DSL 背後的基本原理。jquery

在這裏咱們對 OkHttp 作一下簡單的封裝,實現一個相似 jquery 中的 Ajax 的 http 請求的DSL。git

OkHttp 是一個成熟且強大的網絡庫,在Android源碼中已經使用OkHttp替代原先的HttpURLConnection。不少著名的框架例如Picasso、Retrofit也使用OkHttp做爲底層框架。github

提示: 更多關於OkHttp 的使用可參考: http://square.github.io/okhttp/ajax

建立 Kotlin Gradle 項目

咱們首先使用 IDEA 建立 Kotlin Gradle 項目編程

螢幕快照 2017-07-23 18.43.04.png

而後,在 build.gradle 裏面配置依賴json

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 RxJavahttps://github.com/ReactiveX/...
JavaScript  RxJShttps://github.com/ReactiveX/...
C#  Rx.NEThttps://github.com/Reactive-E...
C#(Unity)  UniRxhttps://github.com/neuecc/UniRx
Scala  RxScalahttps://github.com/ReactiveX/...
Clojure  RxClojurehttps://github.com/ReactiveX/...
C++  RxCpphttps://github.com/Reactive-E...
Lua  RxLuahttps://github.com/bjornbytes...
Ruby  Rx.rbhttps://github.com/Reactive-E...
Python: RxPYhttps://github.com/ReactiveX/...
Go  RxGohttps://github.com/ReactiveX/...
Groovy  RxGroovyhttps://github.com/ReactiveX/...
JRuby  RxJRubyhttps://github.com/ReactiveX/...
Kotlin  RxKotlinhttps://github.com/ReactiveX/...
Swift  RxSwifthttps://github.com/kzaher/RxS...
PHP  RxPHPhttps://github.com/ReactiveX/...
Elixir  reaxivehttps://github.com/alfert/rea...
Dart  RxDarthttps://github.com/ReactiveX/...

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...

本章小結

相比於Java,Kotlin對函數式編程的支持更加友好。Kotlin 的擴展函數和高階函數(Lambda 表達式),爲定義Kotlin DSL提供了核心的特性支持。

使用DSL的代碼風格,可讓咱們的程序更加直觀易懂、簡潔優雅。若是使用Kotlin來開發項目的話,咱們徹底能夠去嘗試一下。


《Kotlin極簡教程》:

點擊這裏 > 去京東商城購買閱讀

點擊這裏 > 去天貓商城購買閱讀

很是感謝您親愛的讀者,你們請多支持!!!有任何問題,歡迎隨時與我交流~

相關文章
相關標籤/搜索