正如Groovy對Java很多特性進行的包裝優化一樣,基於Groovy的HttpBuilder也包裹了HttpClient,使網路編程變得更加的方便易用,下面稍微來用一個例子看一下。
尋找各種依賴的jar包有時候會讓我們失去耐心,不過值得慶幸的是我們有Maven和Gradle這樣的工具,可以根據配置輕松的幫我們配置好我們需要的數據。下面我們來敘述一下整個過程。
1. 創建文件夾httpbuildertest
2. 創建gradle配置文件,build.gradle,內容如下:
apply plugin: "groovy"
apply plugin: "eclipse"
repositories {
mavenCentral()
}
dependencies {
compile "org.codehaus.groovy:http-builder:0.4.0"
compile "org.codehaus.groovy:groovy-all:2.3.3"
testCompile "org.spockframework:spock-core:0.7-groovy-2.0"
}
gradle我們將做另文介紹
3. 執行gralde eclipse(當然必須先安裝gradle),就可以生成eclipse所需要的.classpath和.project文件,這時候就可以使用eclipse導入功能來import->Existing Projects Into WorkSpace。
4. 創建我們的一個測試,試試看是不是可以使用httpbuilder了
import groovyx.net.http.HTTPBuilder
import spock.lang.Specification;
import static groovyx.net.http.Method.*
import static groovyx.net.http.ContentType.*
class HttpbuildLabSpec extends Specification{
HTTPBuilder http = new HTTPBuilder( 'http://m.weather.com.cn' )
public void testRequestWeather(){
when:
def info =""
http.request( GET, jsON ) {
url.path = '/data/101290401.html'
headers.'User-Agent' = 'Mozilla/5.0 Ubuntu/8.10 Firefox/3.0.4'
response.success = { resp, json ->
info = json.weatherinfo.city
}
response.failure = { resp -> println "Unexpected error: ${resp.statusLine.statusCode} : ${resp.statusLine.reasonPhrase}" }
}
then: "曲靖"==info
}
}
打完收工,通過這個小例子我們可以了解到這么一些內容:
(1)HTTPBuilder 是這個框架的核心類(要不然怎麼和框架一個名字),構建這個類的對象的時候,指定要請求的baseUrl。
(2)request方法可以指定請求的method和返回的數據格式,method對應的是GET/PUT/POST/DELETE/HEAD幾個常量,而數據格式目前有JSON/XML/HTML/BINARY/URLENC/ANY幾種。
(3)一個比較有意思的地方是,在http的request方法裡面,彷彿是無根之水一樣突然冒出來了幾個沒有聲明過的變數,看起來連編譯也不會通過的方法,那麼是如何能正常運作的呢?這個我們就要研究到Groovy的Closure(閉包)。Groovy的閉包里包含有一個delegate屬性,一般來說,這個delegate里保存的是閉包使用上下文的對象引用,比如a調用了一個閉包b,則b的delegate就是a的this對象。而在HTTPBuilder對象調用request方法的時候,它把傳入閉包的delegate改成了一個叫做SendDelegate的類對象(這是HTTPBuilder的內部類,他們都是用Java寫的,在新版的HttpBuilder里,已經改名為RequestConfigDelegate),這個類裡面,分別包含了一個叫做getHeaders()的方法,一個叫做getUrL()的方法,一個叫做getResponse()的方法。稍微思索一下我們就可以想到,Groovy里有這樣的特性,如果直接使用一個識別不出來的變數,Groovy會假設它是getter的一種簡寫形式,自動進行補全(當然這也是DSL的常用伎倆,把方法偽裝成短語),而getter並沒有參數,所以其括弧是可以簡寫的,實際上上面的代碼可以寫作getUrl().path = '/data/101290401.html',這樣就非常符合程序員的視覺體驗了。
(4)主要是為了喜歡追根問題的同學釋疑,實際上整個調用還是非常的簡單明快的,在request閉包里,我們通過response(記得嗎,實際上就是GetResponse()),獲得了一個Map結構,這個Map的內部結構實際上是Map<String,Closure>,對「success」和「failure」這兩個key我們分別設置了對應的閉包,這樣就完成了回調的設置,一旦方法成功或者失敗,就可以調用到對應的閉包。
(5)使用了JSON作為返回格式,閉包的第二個參數就是解析好的返回body,就是一個Json對象,是可以直接用點號來訪問的。當然最好不要在失敗的閉包里放這個參數,一般失敗的情況比較多,也許就是一個html返回,格式錯誤那麼測試也就無法按照預期進行了。
⑵ 閉包的Lua中
當一個函數內部嵌套另一個函數定義時,內部的函數體可以訪問外部的函數的局部變數,這種特徵在lua中我們稱作詞法定界。雖然這看起來很清楚,事實並非如此,詞法定界加上第一類函數在編程語言里是一個功能強大的概念,很少語言提供這種支持。
下面看一個簡單的例子,假定有一個學生姓名的列表和一個學生名和成績對應的表;想根據學生的成績從高到低對學生進行排序, names = {Peter,Paul,Mary}
grades = {Mary = 10,Paul = 7,Peter = 8}
table.sort(names,function (n1,n2)
return grades[n1] > grades[n2] -- compare the grades
end) 假定創建一個函數實現此功能:
function sortbygrade (names,grades)
table.sort(names,function (n1,n2)
return grades[n1] > grades[n2] --compare the grades
end) 例子中包含在sortbygrade函數內部的sort中的匿名函數可以訪問sortbygrade的參數grades,在匿名函數內部grades不是全局變數也不是局部變數,我們稱作外部的局部變數(external local variable)或者upvalue。(upvalue意思有些誤導,然而在Lua中他的存在有歷史的根源,還有他比起external local variable簡短)。
看下面的代碼:
function newCounter()
local i = 0
return function() -- anonymous function
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
匿名函數使用upvalue i保存他的計數,當我們調用匿名函數的時候i已經超出了作用范圍,因為創建i的函數newCounter已經返回了。然而Lua用閉包的思想正確處理了這種情況。簡單的說,閉包是一個函數以及它的upvalues。如果我們再次調用newCounter,將創建一個新的局部變數i,因此我們得到了一個作用在新的變數i上的新閉包。
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
c1、c2是建立在同一個函數上,但作用在同一個局部變數的不同實例上的兩個不同的閉包。
技術上來講,閉包指值而不是指函數,函數僅僅是閉包的一個原型聲明;盡管如此,在不會導致混淆的情況下我們繼續使用術語函數代指閉包。
閉包在上下文環境中提供很有用的功能,如前面我們見到的可以作為高級函數(sort)的參數;作為函數嵌套的函數(newCounter)。這一機制使得我們可以在Lua的函數世界裡組合出奇幻的編程技術。閉包也可用在回調函數中,比如在GUI環境中你需要創建一系列button,但用戶按下button時回調函數被調用,可能不同的按鈕被按下時需要處理的任務有點區別。具體來講,一個十進制計算器需要10個相似的按鈕,每個按鈕對應一個數字,可以使用下面的函數創建他們:
function digitButton (digit)
return Button{ label = digit,
action = function ()
add_to_display(digit)
end
}
end
這個例子中我們假定Button是一個用來創建新按鈕的工具,label是按鈕的標簽,action是按鈕被按下時調用的回調函數。(實際上是一個閉包,因為他訪問upvalue digit)。digitButton完成任務返回後,局部變數digit超出范圍,回調函數仍然可以被調用並且可以訪問局部變數digit。
閉包在完全不同的上下文中也是很有用途的。因為函數被存儲在普通的變數內我們可以很方便的重定義或者預定義函數。通常當你需要原始函數有一個新的實現時可以重定義函數。例如你可以重定義sin使其接受一個度數而不是弧度作為參數:
oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end
更清楚的方式:
do
local oldSin = math.sin
local k = math.pi/180
math.sin = function (x)
return oldSin(x*k)
end
end
這樣我們把原始版本放在一個局部變數內,訪問sin的唯一方式是通過新版本的函數。
利用同樣的特徵我們可以創建一個安全的環境(也稱作沙箱,和java里的沙箱一樣),當我們運行一段不信任的代碼(比如我們運行網路伺服器上獲取的代碼)時安全的環境是需要的,比如我們可以使用閉包重定義io庫的open函數來限製程序打開的文件。
do
local oldOpen = io.open
io.open = function (filename,mode)
if access_OK(filename,mode) then
return oldOpen(filename,mode)
else
return nil,access denied
end
end Scheme中的閉包
其他編程的語言主要採用的是閉包的第二種意義(一個與閉包毫不相乾的概念):閉包也算一種為表示帶有自由變數的過程而用的實現技術。但Scheme的術語「閉包」來自抽象代數。在抽象代數里,一集元素稱為在某個運算(操作)之下封閉,如果將該運算應用於這一集合中的元素,產生出的仍然是該集合里的元素。
用Scheme的序對舉例,為了實現數據抽象,Scheme提供了一種稱為序對的復合結構。這種結構可以通過基本過程cons構造出來。過程cons取兩個參數,返回一個包含這兩個參數作為其成分的復合數據對象。請注意,一個序對也算一個數據對象。進一步說,還可以用cons去構造那種其元素本身就是序對的序對,並繼續這樣做下去。
(define x (cons 1 2)) //構造一個x序對,有1,2組成
(define y (cons 3 4))
(define z (cons x y))
Scheme可以建立元素本身也算序對的序對,這就是表結構得以作為一種表示工具的根本基礎。我們將這種能力稱為cons的閉包性質。一般說,某種組合數據對象的操作滿足閉包性質,那就是說,通過它組合起數據對象得到的結果本身還可以通過同樣的操作再進行組合。閉包性質是任何一種組合功能的威力的關鍵要素,因為它使我們能夠建立起層次性結構,這種結構由一些部分構成,而其中的各個部分又是由它們的部分構成,並且可以如此繼續下去。