# 實戰 Groovy: SwingBuilder 和 Twitter API,第 2 部分
_使用 HTTP 基本身份驗證和 ConfigSlurper_
在本期 [_實戰 Groovy_](http://www.ibm.com/developerworks/cn/java/j-pg/) 文章中,Scott Davis 將繼續構建 [第 1 部分](http://www.ibm.com/developerworks/cn/java/j-groovy09299/) 中的 Groovy Twitter 客戶機:Gwitter。這次,他將解決 HTTP Basic 身份驗證問題,并使用 Groovy 的 `ConfigSlurper` 讀入配置設置。
在 “[SwingBuilder 和 Twitter API,第 1 部分](http://www.ibm.com/developerworks/cn/java/j-groovy09299/)” 中,我使用 Groovy 的 `SwingBuilder` 建立了一個簡單 Swing GUI ,從而創建了一個 Twitter 客戶機(名稱為 Gwitter)。我展示了通用的 Twitter Search API,它可以方便地繞過對身份驗證的需求。但是 Twitter 的典型使用模式是獲取所關注的人(在 Twitter 中為_朋友_)的 tweet,并發表自己的 tweet。要能夠讀取朋友的 tweet,Twitter 必須首先知道您是誰。因此,您必須能夠使用 Twitter API 以編程的方式_登錄_。
## 關于本系列
Groovy 是一種新興的運行于 Java? 平臺之上的編程語言。它提供與已有 Java 代碼的無縫集成,并引入了一些強大的新特性,比如閉包和元編程。簡單來講,Groovy 就是在 21 世紀 Java 語言的的效果。
將任何新工具整合進開發工具包中的關鍵是知道何時使用它以及何時保留它。Groovy 有時非常強大,但僅僅適用于一些適當的場景。因此,_實戰 Groovy_ 系列將探索 Groovy 的實際使用,從而幫助您了解如何成功地應用它們。
在本文中,您將向 Gwitter 添加身份驗證功能,并使 Gwitter 能夠請求和解析您的朋友時間軸。與上次一樣,您首先將了解基本的命令行概念,在確認它能獨立工作之后,將它整合到 Gwitter 中 — 并對 Gwitter UI 進行一些增強。要獲取本文的完整源代碼,請參見 [下載](#download) 小節。
## Twitter 中的身份驗證
Twitter 支持兩種身份驗證方法:OAuth 和 HTTP Basic 身份驗證。Twitter 開發團隊在文檔中明確表明建議您首選 OAuth 機制(參見 [參考資料](#resources))。但他們也承認 “針對桌面客戶機的傳統 OAuth 流程有時非常麻煩”。因此,我將使用 Groovy 中最為簡單的 HTTP Basic 身份驗證方式,相信大家也不會感到奇怪。
## 您自己的 Twitter 帳戶
要讓本文中的示例正常運行,您需要提供自己的 Twitter 用戶名和密碼。如果還沒有帳戶,請訪問 https://twitter.com/signup。
HTTP Basic 身份驗證的流行歸因于它的的實例非常簡單(參見 [參考資料](#resources))。您只需要使用冒號來連接用戶名和密碼,然后對結果執行 Base64 編碼。可以使用最樸實的語言來描述它的安全性:它可以防止純文本憑證通過線路傳遞出去。但是,與 Web 開發相關的任何編程語言都可以使用 Base64 編碼和解碼。
在 Groovy中,Base64 編碼的 Twitter 用戶名和密碼如下所示:
```
def authString = "username:password".getBytes().encodeBase64().toString()
```
創建身份驗證字符串僅僅是整個問題中的一小部分。為了能夠讀取朋友的 tweet,您需要向 http://twitter.com/statuses/friends_timeline.atom 發出一個 REST 式請求,并在 HTTP GET 請求的 `Authorization` 頭部中傳入 Base6 編碼的字符串(參見 [參考資料](#resources))。
創建一個名稱為 friends.groovy 文件。添加如清單 1 所示的代碼:
1. 以 Atom 文檔的形式請求朋友時間軸。
2. 使用 `XmlSlurper` 解析它。
3. 向控制臺打印輸出結果。
(要回顧如何使用 Groovy `XmlSlurper` 來解析 XML,請閱讀 “[實戰 Groovy:構建和解析 XML](http://www.ibm.com/developerworks/cn/java/j-pg05199/)”。)
##### 清單 1\. 在 Twitter 中請求朋友時間軸
```
def addr = "http://twitter.com/statuses/friends_timeline.atom"
def authString = "username:password".getBytes().encodeBase64().toString()
def conn = addr.toURL().openConnection()
conn.setRequestProperty("Authorization", "Basic ${authString}")
if(conn.responseCode == 200){
def feed = new XmlSlurper().parseText(conn.content.text)
feed.entry.each{entry->
println entry.author.name
println entry.title
println "-"*20
}
}else{
println "Something bad happened."
println "${conn.responseCode}: ${conn.responseMessage}"
}
```
在命令行中輸入 `groovy friends`。輸出應該類似于清單 2。當然,您的輸出將根據傳遞給 Twitter 的憑證以及所關注的朋友而有所不同。
##### 清單 2\. friends.groovy 的 Twitter 輸出
```
--------------------
Scott Davis
scottdavis99: @neal4d Is the Bishop's Arms *diagonally* adjacent
to your hotel? Or is it two doors over and one door up?
--------------------
Neal Ford
neal4d: At the Bishop's Arms, the pub adjacent our hotel, with
a shocking number of single-malt scotches. I need to spend some
quality time here.
--------------------
```
## `cURL`
`cURL` 是一個用于發起 HTTP GET、POST、PUT 和 DELETE 請求的命令行實用工具。它是 UNIX?、Linux? 和 Mac OS X 系統上的標準工具,并且可以下載到 Windows? 中(參見 [參考資料](#resources))。(有關使用 `cURL` 與 REST 式 Web 服務交互的更多信息,請閱讀 “[精通 Grails:RESTful Grails](http://www.ibm.com/developerworks/cn/java/j-grails09168/)”。)
要掌握原始 Atom 輸出,可以在命令行中發起一個 [cURL](#curl) 請求。清單 3 給出了一個示例:
##### 清單 3\. 使用 `cURL` 從 Twitter 獲取原始 Atom
```
$ curl -u scottdavis99:password
http://twitter.com/statuses/friends_timeline.atom
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<title>Twitter / scottdavis99 with friends</title>
<updated>2009-11-03T22:15:45+00:00</updated>
<entry>
<title>scottdavis99: @neal4d Is the Bishop's Arms *diagonally*
adjacent to your hotel? Or is it two doors over and one door up?
</title>
<id>tag:twitter.com,2007:
http://twitter.com/scottdavis99/statuses/5402041984</id>
<published>2009-11-03T21:23:43+00:00</published>
<updated>2009-11-03T21:23:43+00:00</updated>
<author>
<name>Scott Davis</name>
<uri>http://thirstyhead.com</uri>
</author>
</entry>
<entry />
<entry />
<entry />
</feed>
```
這樣,現在您已經確定可以使用 Basic 身份驗證來發起 HTTP GET 請求并解析結果了。但是,源代碼中硬編碼的用戶名和密碼會生成一個紅色標記。較好的方法是將身份驗證信息重構到外部屬性文件中。
* * *
## 使用 Groovy 的 `ConfigSlurper` 讀取 Java 屬性
在 Java 應用程序中存儲外部信息的一種最常用的方法是使用屬性文件。屬性文件是存儲在純文本中的簡單鍵值對。屬性文件是平面文件 — 它們并未提供任何形式的鍵嵌套和分組 — 但是您可以通過創建具有相同前綴的帶點名稱來對您的鍵實現虛假嵌套。
創建一個名稱為 config.properties 的文件,并添加如清單 4 所示的內容:
##### 清單 4\. 一個簡單的 Java 屬性文件
```
login.username=fred
login.password=wordpass
```
讀取屬性文件的一種方法是使用 `java.util.Properties` 類。創建一個名稱為 PropertiesTest.groovy 的文件并編寫如清單 5 所示的單元測試:
##### 清單 5\. 讀取 Java 屬性文件的簡單單元測試
```
class PropertiesTest extends GroovyTestCase{
void testReadingProperties(){
Properties properties = new Properties();
try{
properties.load(new FileInputStream("config.properties"));
assertEquals("fred", properties.getProperty("login.username"));
assertEquals("wordpass", properties.getProperty("login.password"));
}catch (IOException e) {
fail(e.getMessage());
}
}
}
```
要運行單元測試,在命令行中鍵入 `groovy PropertiesTest`。每個測試方法應該都有一個點,并且在運行完成后會顯示 `OK (1 test)`。
您已經了解了使用 `XmlSlurper` 來剖析 XML 是多么地簡單。Groovy 提供了一個類似的類 — `groovy.util.ConfigSlurper` — 用于讀入屬性文件。 `ConfigSlurper` 使讀取 Java 屬性文件變得非常簡單。
向 PropertiesTest.groovy 添加一個新測試,如清單 6 所示:
##### 清單 6\. 通過 `ConfigSlurper` 讀取 Java 屬性文件
```
void testReadingPropertiesWithConfigSlurper(){
Properties properties = new Properties();
properties.load(new FileInputStream("config.properties"));
def config = new ConfigSlurper().parse(properties);
assertEquals "fred", config.login.username
assertEquals "wordpass", config.login.password
}
```
如您所見,`ConfigSlurper` 允許您使用相同的點表示來遍歷 Java 屬性(`XmlSlurper` 支持的 XML 方式)。`ConfigSlurper GPath` 語法要比 [清單 5](#listing5) 的測試中所使用的 `properties.getProperty("login.username")` 簡單很多。
但是,`ConfigSlurper` 的益處已經超越了語法的可讀性。您可以使用一組非常類似于 Groovy 的嵌套配置塊在配置文件中存儲您的值。
* * *
## 使用 `ConfigSlurper` 讀取 Groovy 配置
幾乎可以說 Groovy 配置文件是用于存儲配置設置的域相關語言 (DSL)。您可以將相似的設置分組到一個塊中,并且可以將一些塊嵌入到任意深度。這種嵌套方式既可以減少重復鍵入,又可以確保相關設計的清晰性。
創建一個名稱為 config.groovy 的文件并添加如清單 7 所示的代碼 :
##### 清單 7\. Groovy 配置文件
```
login{
username = "fred"
password = "wordpass"
}
```
向 PropertiesTest.groovy 再添加一個測試,用于讀入 Groovy 配置文件,如清單 8 所示:
##### 清單 8\. 使用 `ConfigSlurper` 讀取 Groovy 配置文件
```
void testConfigSlurper(){
def config = new ConfigSlurper().parse(new File("config.groovy").text)
assertEquals "fred", config.login.username
assertEquals "wordpass", config.login.password
}
```
由于過載的解析方法可以接受一個簡單的 `String`,因此在一個單元測試中模擬配置設置很繁瑣。Groovy 的三重引用可以很好地解決此問題,如清單 9 所示:
##### 清單 9\. 創建模擬設置
```
void testMockConfig(){
def mockConfig = """
smtp{
server = "localhost"
port = 25
auth{
user = "testuser"
password = "testpass"
}
}
"""
def config = new ConfigSlurper().parse(mockConfig)
assertEquals "testuser", config.smtp.auth.user
}
```
結合使用 Groovy 配置文件(而不是 Java 屬性文件)與 `ConfigSlurper` 將提供更加具體的收益。您可以根據預定義的環境,比如 `development`、`production` 和 `testing`,來切換各值。這可以極大的提高可測試性。
* * *
## 通過 `ConfigSlurper` 來使用環境
如果在 Groovy 配置文件中創建了一個特別命名的塊 —`environments`,那么可以選擇性地覆蓋配置設置。如果您是一名 Grails 用戶,則應該清楚它在 grails-app/conf/DataSource.groovy 中的工作原理,如清單 10 所示:
##### 清單 10\. 使用 `ConfigSlurper` 管理數據庫連接的 Grails
```
dataSource {
pooled = true
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
}
// environment specific settings
environments {
development {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:mem:devDB"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:file:prodDb;shutdown=true"
}
}
}
```
在清單 10 中,`ConfigSlurper` 傳回的最后一個 `dataSource` 配置對象是頂部 `dataSource` 塊中的全局值與 `development`、`test` 或 `production` 塊中的環境相關設置的組合。您在 `environments` 塊中設置的值將覆蓋全局值。
最外層的塊必須被命名為 `environments`,但您可以將內層塊命名為任何名稱。在 config.groovy 中添加如清單 11 所示的代碼,然后在 PropertiesTest.groovy 中測試它:
##### 清單 11\. 在 Groovy 配置文件中使用環境
```
//config.groovy
login{
username = "fred"
password = "wordpass"
}
environments{
qa{
login{
username = "testuser"
password = "testpass"
}
}
}
//PropertiesTest.groovy
void testWithEnvironment(){
def config = new ConfigSlurper("qa").parse(new File("config.groovy").text)
assertEquals "testuser", config.login.username
assertEquals "testpass", config.login.password
}
```
現在,您已經具備了一定的 `ConfigSlurper` 經驗。接下來,結合它與之前運行的朋友時間軸示例,將它們添加到 Gwitter Swing 應用程序中。首先,您需要切換為分頁界面,以便于顯示新的信息。
* * *
## 向 Gwitter 添加一個 `TabbedPane`
在 [第 1 部分](http://www.ibm.com/developerworks/cn/java/j-groovy09299/) 結束時,您創建了一個單窗格的應用程序,如圖 1 所示:
##### 圖 1\. 單窗格 Gwitter 應用程序

要顯示朋友時間軸,您需要切換為分頁視圖。應該還記得 Groovy 的 `SwingBuilder` 可以脫離典型的 Java 語法 `JTabbedPane tabpane = new JTabbedPane();`,而只需向 `SwingBuilder.frame` 添加一個嵌套的 `tabbedPane` 閉包。(類似于 Groovy 配置文件和 `ConfigBuilder`,不是嗎?)向 Gwitter 添加一個新的 `tabbedPane`,如清單 12 所示:
##### 清單 12\. 向 Gwitter 添加一個 `tabbedPane`
```
swingBuilder.frame(title:"Gwitter",
defaultCloseOperation:JFrame.EXIT_ON_CLOSE,
size:[400,500],
show:true) {
customMenuBar()
tabbedPane{
panel(title:"Friends")
panel(title:"New Tweet")
panel(title:"Search"){
searchPanel()
resultsPanel()
}
}
}
```
可以預見,`title` 屬性將顯示在選項卡中。在命令行中鍵入 `groovy Gwitter`,應該能夠看到如圖 2 所示的選項卡:
##### 圖 2\. 帶有新分頁界面的 Gwitter

再次強調,`SwingBuilder` 僅僅在傳統 Java Swing 上添加了一層語法。所有原 Java 技巧仍然適用于 Groovy。舉例來說,無需使用在一行中實例化 `JTabbedPane` 并在第二行中調用 `setTabPlacement()` 這樣的 setter 方法,您可以在一行中完成這兩個任務,如清單 13 所示:
##### 清單 13\. 在分頁面板的其余部分放置些選項卡
```
swingBuilder.frame(title:"Gwitter",
defaultCloseOperation:JFrame.EXIT_ON_CLOSE,
size:[400,500],
show:true) {
customMenuBar()
tabbedPane(tabPlacement:JTabbedPane.LEFT){
panel(title:"Friends")
panel(title:"New Tweet")
panel(title:"Search"){
searchPanel()
resultsPanel()
}
}
}
```
如您所料,這將在面板左側對齊選項卡,如圖 3 所示:
##### 圖 3\. 在分頁窗格左側對齊選項卡

運行應用程序并確認其他 `tabPlacement` 可正常工作之后,刪除將選項卡返回到面板頂部的默認位置的設置。
* * *
## 填充 Friends 選項卡
現在,您將使用 Twitter API 中的實時數據來填充空的 Friends 選項卡
首先,在與 Gwitter.groovy 相同的目錄中創建一個 gwitterConfig.groovy 文件。添加如清單 14 所示的代碼(記住要將憑證更改為您的 Twitter 用戶名和密碼):
##### 清單 14\. gwitterConfig.groovy 文件
```
login{
username = "username"
password = "password"
}
```
接下來,在相同目錄中創建一個 FriendsTimeline.groovy 文件。添加如清單 15 所示的代碼:
##### 清單 15\. FriendsTimeline.groovy 文件
```
class FriendsTimeline{
static final String addr = "http://twitter.com/statuses/friends_timeline.atom"
static Object[] refresh(){
def results = []
def configFile = new File("gwitterConfig.groovy")
if(configFile.exists()){
def config = new ConfigSlurper().parse(configFile.text)
//NOTE: this should be a single line in your code
def authString = "${config.login.username}:${config.login.password}"
.getBytes().encodeBase64().toString()
def conn = addr.toURL().openConnection()
conn.setRequestProperty("Authorization", "Basic ${authString}")
if(conn.responseCode == 200){
def feed = new XmlSlurper().parseText(conn.content.text)
feed.entry.each{entry->
def tweet = new Tweet()
tweet.author = entry.author.name
tweet.published = entry.published
tweet.content = entry.title
results << tweet
}
}else{
println "Something bad happened."
println "${conn.responseCode}: ${conn.responseMessage}"
}
}else{
println "Cannot find ${configFile.name}."
}
return results as Object[]
}
}
```
清單 15 幾乎與 Search.groovy 中的代碼相同(參見 [第 1 部分](http://www.ibm.com/developerworks/cn/java/j-groovy09299/))。區別在于,FriendsTimeline.groovy 中使用 `ConfigSlurper` 并使用 HTTP Basic 身份驗證來讀取 gwitterConfig.groovy 中的值。在 Search.groovy 中,您發起了一個簡單的無需身份驗證的 HTTP GET 請求。
您將 `FriendsTimeline` 類整合到了主 `Gwitter` 類中,這與 [第 1 部分中](http://www.ibm.com/developerworks/cn/java/j-groovy09299/) 整合 `Search` 類的方法如出一轍。首先,為 `friendsList` 添加一個字段,如清單 16 所示:
##### 清單 16\. 向 Gwitter 添加一個 `friendsList` 字段
```
class Gwitter{
def searchField
def resultsList
def friendsList
// snip...
}
```
接下來,為 Refresh 按鈕以及 `FriendsTimeline.refresh()` 方法調用的結果添加一個面板,如清單 17 所示:
##### 清單 17\. 向 Gwitter 添加兩個新面板
```
class Gwitter{
def searchField
def resultsList
def friendsList
void show(){
// snip...
def friendsRefreshPanel = {
swingBuilder.panel(constraints: BorderLayout.NORTH){
button(text:"Refresh", actionPerformed:{
doOutside{
friendsList.listData = FriendsTimeline.refresh()
}
} )
}
}
def friendsPanel = {
swingBuilder.scrollPane(constraints: BorderLayout.CENTER){
friendsList = list(fixedCellWidth: 380,
fixedCellHeight: 75,
cellRenderer:new StripeRenderer())
}
}
}
}
```
最后,在 Friends 選項卡中呈現 `friendsRefreshPanel` 和 `friendsPanel`,如清單 18 所示:
##### 清單 18\. 在 Friends 選項卡中呈現兩個面板
```
swingBuilder.frame(title:"Gwitter",
defaultCloseOperation:JFrame.EXIT_ON_CLOSE,
size:[400,500],
show:true) {
customMenuBar()
tabbedPane{
panel(title:"Friends"){
friendsRefreshPanel()
friendsPanel()
}
panel(title:"New Tweet")
panel(title:"Search"){
searchPanel()
resultsPanel()
}
}
}
```
在命令行中鍵入 `groovy Gwitter`,并單擊 Refresh 按鈕。結果應該如圖 4 所示:
##### 圖 4\. 顯示朋友時間軸的 Gwitter

* * *
## 結束語
在本文中,您學習如何使用 Basic 身份驗證發起 HTTP GET 請求。您還了解了如何創建大多數 Groovy 配置文件和 `groovy.util.ConfigSlurper`。最后,您向 Gwitter 添加了一個分頁界面,用于顯示 REST 式 Twitter API 調用的結果, 以便于返回朋友時間軸。
下一期,您將發起 HTTP POST 請求來添加自己的 tweet。在此過程中,您還將了解 `JTextArea` 字段以及如何使用 `DocumentSizeFilter` 來限制輸入的具體字符數量(比如說 140)。最后,我希望您可以掌握 Groovy 的大量實際應用。
* * *
## 下載
| 描述 | 名字 | 大小 |
| --- | --- | --- |
| 源代碼 | [j-pg11179.zip](http://www.ibm.com/developerworks/apps/download/index.jsp?contentid=461633&filename=j-pg11179.zip&method=http&locale=zh_CN) | 21KB |
- 實戰 Groovy
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 2 部分
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 1 部分
- 實戰 Groovy: @Delegate 注釋
- 實戰 Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程
- 實戰 Groovy: 構建和解析 XML
- 實戰 Groovy: for each 剖析
- 實戰 Groovy: Groovy:Java 程序員的 DSL
- 實戰 Groovy: 關于 MOP 和迷你語言
- 實戰 Groovy: 用 curry 過的閉包進行函數式編程
- 實戰 Groovy: Groovy 的騰飛
- 實戰 Groovy: 在 Java 應用程序中加一些 Groovy 進來
- 實戰 Groovy: 用 Groovy 生成器作標記
- 實戰 Groovy: 用 Groovy 打造服務器端
- 實戰 Groovy: 使用 Groovy 模板進行 MVC 編程
- 實戰 Groovy: 用 Groovy 進行 JDBC 編程
- 實戰 Groovy: 用 Groovy 進行 Ant 腳本編程
- 實戰 Groovy: 用 Groovy 更迅速地對 Java 代碼進行單元測試
- alt.lang.jre: 感受 Groovy