NetDiscovery (https://github.com/fengzhizi715/NetDiscovery) 是一款基于 Vert.x、RxJava 2 等框架实现的爬虫框架。
一. 如何创建 DSL
领域特定语言(英语:domain-specific language、DSL)指的是专注于某个应用程序领域的计算机语言。又译作领域专用语言。DSL 能够简化程序设计过程,提高生产效率的技术,同时也让非编程领域专家的人直接描述逻辑成为可能。
NetDiscovery 本身提供了很多功能的 API,然而它的 DSL 模块是为了让使用者拥有更多的选择。
本文讨论的 DSL 是内部 DSL。
内部 DSL:通用语言的特定语法,用内部DSL写成的脚本是一段合法的程序,但是它具有特定的风格,而且仅仅用到了语言的一部分特性,用于处理整个系统一个小方面的问题。
NetDiscovery 的 DSL 主要是结合 Kotlin 带接收者的 Lambda、运算符重载、中缀表达式等 Kotlin 语法特性来编写。
运算符重载、中缀表达式其实很多语言都有,那么我们着重介绍一下带接收者的 Lambda。
在介绍 Kotlin 带接收者的 Lambda 之前,先介绍一下带接收者的函数类型。
带接收者的函数类型,例如 A.(B) -> C,其中 A 是接收者类型,B是参数类型,C是返回类型。
例如:
val sum: Int.(Int) -> Int = {
this + it
}
sum 是带接收者的函数类型,它在使用上类似于扩展函数。在函数内部,可以使用this指代传给调用的接收者对象。
而带接收者的 Lambda 典型代表是 Kotlin 标准库的扩展函数:with 和 apply。
看一下 apply 的源码:
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
在 apply 函数中,参数 block 是一个带有接收者的函数类型的参数。
对于 apply 函数的使用,先定义一个 User 对象:
class User{
var name:String?=null
var password: String?=null
override fun toString(): String {
return "name:$name,password=$password"
}
}
然后,使用 apply 函数对 User 的属性进行赋值:
fun main(args: Array<String>) {
val user = User().apply {
name = "Tony"
password = "123456"
}
println(user)
}
二. Request 的 DSL 封装
Request 请求包含了爬虫网络请求 Request 的封装,例如:url、userAgent、httpMethod、header、proxy 等等。当然,还包含了请求发生之前、之后做的一些事情,类似于AOP。
那么,我们来看一下使用 DSL 来编写Request:
val request = request {
url = "https://www.baidu.com/"
httpMethod = HttpMethod.GET
spiderName = "tony"
header {
"111" to "2222"
"333" to "44444"
}
extras {
"tt" to "qqq"
}
}
Spider.create().name("tony").request(request).pipeline(DebugPipeline()).run()
可以看到,Request 使用 DSL 封装之后,非常简单明了。
下面的代码是具体的实现,主要是使用带接收者的 Lambda、中缀表达式。
package com.cv4j.netdiscovery.dsl
import com.cv4j.netdiscovery.core.domain.Request
import io