webx小結

分類  >  Web前端 >
tags:    時間:2013-12-11 18:29:04
webx小結簡介
webx總結 Webx框架指南Michael ZhouWebx框架指南Michael Zhou出版日期 2010-11-13iii引言 ...........................……
webx小結正文
    webx總結
    Webx框架指南
    Michael Zhou
    Webx框架指南
    Michael Zhou
    出版日期 2010-11-13
    iii
    引言 ............................................................................................................................... ix
    1. 閱讀嚮導 ............................................................................................................. ix
    2. Webx是什麼? .................................................................................................... ix
    3. Webx的歷史 ....................................................................................................... ix
    4. 為什麼要用Webx而不是其它的開源框架? ............................................................. x
    5. Webx的優勢 ........................................................................................................ x
    5.1. 成熟可靠性 ................................................................................................ x
    5.2. 開放和擴展性 ............................................................................................. x
    6. Webx還缺少什麼? .............................................................................................. x
    部分 I. Webx框架概覽 ..................................................................................................... 1
    第 1 章 Webx總體介紹 ............................................................................................. 4
    1.1. 設計理念 ................................................................................................... 4
    1.1.1. 框架的本質 ..................................................................................... 4
    1.1.2. 基礎框架 ........................................................................................ 4
    1.1.3. 層次化 ........................................................................................... 5
    1.2. Webx的層次 ............................................................................................. 6
    1.2.1. 三個大層次 ..................................................................................... 6
    1.2.2. 剪裁和定製Webx ............................................................................ 7
    1.3. 本章總結 ................................................................................................... 9
    第 2 章 SpringExt ................................................................................................... 10
    2.1. 用SpringExt裝配服務 ............................................................................... 10
    2.1.1. Spring Beans ............................................................................... 11
    2.1.2. Spring Schema ........................................................................... 12
    2.1.3. SpringExt Schema ....................................................................... 15
    2.2. SpringExt原理 ......................................................................................... 16
    2.2.1. XML Schema中的秘密 ................................................................. 16
    2.2.2. 擴展點,Configuration Point ....................................................... 17
    2.2.3. 捐獻,Contribution ...................................................................... 17
    2.2.4. 組件和包 ...................................................................................... 18
    2.2.5. 取得Schemas .............................................................................. 19
    2.3. SpringExt其它特性 ................................................................................... 22
    2.4. 本章總結 ................................................................................................. 23
    第 3 章 Webx Framework ...................................................................................... 24
    3.1. Webx的初始化 ........................................................................................ 24
    3.1.1. 初始化級聯的Spring容器 ............................................................... 24
    3.1.2. 初始化日誌系統 ............................................................................ 26
    3.2. Webx響應請求 ........................................................................................ 27
    3.2.1. 增強request、response、session的功能 ....................................... 27
    3.2.2. Pipeline流程機制 .......................................................................... 29
    3.2.3. 異常處理機制 ................................................................................ 30
    3.2.4. 開發模式工具 ................................................................................ 30
    3.2.5. 響應和處理請求的更多細節 ............................................................ 33
    3.3. 定製Webx Framework ............................................................................ 36
    3.3.1. 定製WebxRootController ............................................................ 36
    Webx框架指南
    iv
    3.3.2. 定製WebxController .................................................................... 36
    3.4. 本章總結 ................................................................................................. 36
    第 4 章 Webx Turbine ............................................................................................ 37
    4.1. 設計理念 ................................................................................................. 37
    4.1.1. 頁面驅動 ...................................................................................... 37
    4.1.2. 約定勝於配置 ................................................................................ 38
    4.2. 頁面布局 ................................................................................................. 39
    4.3. 處理頁面的基本流程 ................................................................................. 40
    4.4. 依賴注入 ................................................................................................. 42
    4.4.1. Spring原生注入手段 ...................................................................... 42
    4.4.2. 注入request、response和session對象 ........................................... 42
    4.4.3. 參數注入 ...................................................................................... 43
    4.5. 定製Webx Turbine .................................................................................. 43
    4.6. 本章總結 ................................................................................................. 44
    部分 II. Webx基礎設施服務 ............................................................................................ 45
    第 5 章 Resource Loading服務指南 ....................................................................... 49
    5.1. 資源概述 ................................................................................................. 49
    5.1.1. 什麼是資源? ................................................................................ 49
    5.1.2. 如何表示資源? ............................................................................ 50
    5.1.3. 如何訪問資源? ............................................................................ 50
    5.1.4. 如何遍歷資源? ............................................................................ 51
    5.1.5. 有什麼問題? ................................................................................ 52
    5.2. Spring的ResourceLoader機制 ................................................................. 53
    5.2.1. Resource介面 .............................................................................. 53
    5.2.2. ResourceLoader和ResourcePatternResolver介面 ...................... 53
    5.2.3. 在代碼中取得資源 ......................................................................... 54
    5.2.4. Spring如何裝載資源? ................................................................... 55
    5.2.5. Spring ResourceLoader的缺點 ..................................................... 57
    5.3. Resource Loading服務 ........................................................................... 58
    5.3.1. 替換Spring ResourceLoader ......................................................... 58
    5.3.2. 定義新資源 ................................................................................... 59
    5.3.3. 重命名資源 ................................................................................... 60
    5.3.4. 重定向資源 ................................................................................... 61
    5.3.5. 匹配資源 ...................................................................................... 62
    5.3.6. 在多個ResourceLoader中查找 ...................................................... 63
    5.3.7. 裝載parent容器中的資源 .............................................................. 64
    5.3.8. 修改資源文件的內容 ...................................................................... 64
    5.3.9. 直接使用ResourceLoadingService .............................................. 65
    5.3.10. 在非Web環境中使用Resource Loading服務 ............................... 67
    5.4. ResourceLoader參考 ............................................................................. 68
    5.4.1. FileResourceLoader ................................................................... 68
    5.4.2. WebappResourceLoader ............................................................... 69
    5.4.3. ClasspathResourceLoader .......................................................... 69
    5.4.4. SuperResourceLoader ................................................................. 69
    Webx框架指南
    v
    5.4.5. 關於ResourceLoader的其它考慮 ................................................... 70
    5.5. 本章總結 ................................................................................................. 70
    第 6 章 Filter、Request Contexts和Pipeline ........................................................... 71
    6.1. Filter ....................................................................................................... 71
    6.1.1. Filter的用途 .................................................................................. 71
    6.1.2. Filter工作原理 ............................................................................... 72
    6.1.3. Filter的限制 .................................................................................. 73
    6.1.4. Webx對filter功能的補充 ................................................................ 73
    6.2. Request Contexts服務 ........................................................................... 74
    6.2.1. Request Contexts工作原理 .......................................................... 74
    6.2.2. Request Contexts的用途 ............................................................. 75
    6.2.3. Request Contexts的使用 ............................................................. 76
    6.3. Pipeline服務 ........................................................................................... 79
    6.3.1. Pipeline工作原理 .......................................................................... 79
    6.3.2. Pipeline的用途 ............................................................................. 80
    6.3.3. Pipeline的使用 ............................................................................. 82
    6.4. 本章總結 ................................................................................................. 91
    第 7 章 Request Contexts功能指南 ........................................................................ 93
    7.1. <basic> - 提供基礎特性 .......................................................................... 94
    7.1.1. 攔截器介面 ................................................................................... 94
    7.1.2. 默認攔截器 ................................................................................... 95
    7.2. <set-locale> -設置locale區域和charset字符集編碼 ............................... 95
    7.2.1. Locale基礎 .................................................................................. 95
    7.2.2. Charset編碼基礎 .......................................................................... 96
    7.2.3. Locale和charset的關係 ............................................................... 97
    7.2.4. 設置locale和charset .................................................................... 97
    7.2.5. 使用方法 ...................................................................................... 98
    7.3. <parser> - 解析參數 ............................................................................. 101
    7.3.1. 基本使用方法 .............................................................................. 101
    7.3.2. 上傳文件 .................................................................................... 102
    7.3.3. 高級選項 .................................................................................... 104
    7.4. <buffered> - 緩存response中的內容 ..................................................... 107
    7.4.1. 實現原理 .................................................................................... 107
    7.4.2. 使用方法 .................................................................................... 109
    7.5. <lazy-commit> - 延遲提交response ...................................................... 111
    7.5.1. 什麼是提交 ................................................................................. 111
    7.5.2. 實現原理 .................................................................................... 111
    7.5.3. 使用方法 .................................................................................... 112
    7.6. <rewrite> -重寫請求的URL和參數 .......................................................... 112
    7.6.1. 概述 ........................................................................................... 112
    7.6.2. 取得路徑 .................................................................................... 114
    7.6.3. 匹配rules .................................................................................... 114
    7.6.4. 匹配conditions ........................................................................... 115
    7.6.5. 替換路徑 .................................................................................... 117
    7.6.6. 替換參數 .................................................................................... 117
    Webx框架指南
    vi
    7.6.7. 後續操作 .................................................................................... 118
    7.6.8. 重定向 ........................................................................................ 119
    7.6.9. 自定義處理器 .............................................................................. 120
    7.7. 本章總結 ............................................................................................... 120
    第 8 章 Request Context之Session指南 ................................................................ 121
    8.1. Session概述 ........................................................................................... 121
    8.1.1. 什麼是Session ............................................................................. 121
    8.1.2. Session數據存在哪? ................................................................... 121
    8.1.3. 創建通用的session框架 ................................................................ 123
    8.2. Session框架 ........................................................................................... 124
    8.2.1. 最簡配置 .................................................................................... 124
    8.2.2. Session ID .................................................................................. 124
    8.2.3. Session的生命期 ......................................................................... 125
    8.2.4. Session Store ............................................................................. 127
    8.2.5. Session Model ........................................................................... 129
    8.2.6. Session Interceptor .................................................................... 129
    8.3. Cookie Store ........................................................................................ 130
    8.3.1. 多值Cookie Store ...................................................................... 131
    8.3.2. 單值Cookie Store ...................................................................... 134
    8.4. 其它Session Store ................................................................................. 137
    8.4.1. Simple Memory Store ................................................................ 137
    8.5. 本章總結 ............................................................................................... 138
    部分 III. Webx應用支持服務 .......................................................................................... 139
    第 9 章 表單驗證服務指南 ..................................................................................... 141
    9.1. 表單概述 ............................................................................................... 141
    9.1.1. 什麼是表單驗證 ........................................................................... 141
    9.1.2. 表單驗證的形式 ........................................................................... 142
    9.2. 設計 ...................................................................................................... 144
    9.2.1. 驗證邏輯與表現邏輯分離 ............................................................. 144
    9.2.2. 驗證邏輯和應用代碼分離 ............................................................. 145
    9.2.3. 表單驗證的流程 ........................................................................... 145
    9.3. 使用表單驗證服務 .................................................................................. 146
    9.3.1. 創建新數據 ................................................................................. 146
    9.3.2. 修改老數據 ................................................................................. 153
    9.3.3. 批量創建或修改數據 .................................................................... 155
    9.4. 表單驗證服務詳解 .................................................................................. 159
    9.4.1. 配置詳解 .................................................................................... 159
    9.4.2. Validators .................................................................................. 167
    9.4.3. Form Tool .................................................................................. 177
    9.4.4. 外部驗證 .................................................................................... 179
    9.5. 本章總結 ............................................................................................... 180
    部分 IV. Webx應用實作 ............................................................................................... 181
    第 10 章 創建第一個Webx應用 .............................................................................. 183
    10.1. 準備工作 ............................................................................................. 183
    Webx框架指南
    vii
    10.1.1. 安裝JDK ................................................................................... 183
    10.1.2. 安裝和配置maven .................................................................... 183
    10.1.3. 安裝集成開發環境 ..................................................................... 183
    10.2. 創建應用 ............................................................................................. 183
    10.3. 運行應用 ............................................................................................. 184
    10.4. 提問和解答 .......................................................................................... 186
    10.4.1. 在生產環境的應用上,也會出現前述的「開發者首頁」嗎? .......... 186
    10.4.2. 「開發模式」是什麼意思? ........................................................ 187
    10.4.3. 所生成的應用中包含了什麼? ..................................................... 187
    第 11 章 Webx日誌系統的配置 .............................................................................. 189
    11.1. 名詞解釋 ............................................................................................. 189
    11.1.1. 日誌系統(Logging System) ................................................... 189
    11.1.2. 日誌框架(Logging Framework) ............................................ 190
    11.2. 在Maven中組裝日誌系統 ..................................................................... 190
    11.2.1. 在Maven中配置logback作為日誌系統 ...................................... 192
    11.2.2. 在Maven中配置log4j作為日誌系統 ............................................ 195
    11.3. 在WEB應用中配置日誌系統 ................................................................... 198
    11.3.1. 設置WEB應用 ............................................................................ 198
    11.3.2. 定製/WEB-INF/logback.xml(或/WEB-INF/log4j.xml) .......... 200
    11.3.3. 同時初始化多個日誌系統 ............................................................ 203
    11.4. 常見錯誤及解決 ................................................................................... 205
    11.4.1. 查錯技巧 ................................................................................... 205
    11.4.2. 異常信息:No log system exists ................................................ 205
    11.4.3. 異常信息:NoSuchMethodError:
    org.slf4j.MDC.getCopyOfContextMap() ........................................... 206
    11.4.4. STDERR輸出:Class path contains multiple SLF4J bindings ....... 206
    11.4.5. 看不到日誌輸出 ......................................................................... 206
    11.5. 本章總結 ............................................................................................. 208
    部分 V. 輔助工具 ......................................................................................................... 209
    第 12 章 AutoConfig工具使用指南 ........................................................................ 211
    12.1. 需求分析 ............................................................................................. 211
    12.1.1. 解決方案 ................................................................................... 211
    12.2. AutoConfig的設計 .............................................................................. 214
    12.2.1. 角色與職責 ............................................................................... 214
    12.2.2. 分享二進位目標文件 .................................................................. 215
    12.2.3. 布署二進位目標文件 .................................................................. 215
    12.2.4. AutoConfig特性列表 ................................................................ 216
    12.3. AutoConfig的使用 —— 開發者指南 ..................................................... 217
    12.3.1. 建立AutoConfig目錄結構 .......................................................... 217
    12.3.2. 建立auto-config.xml描述文件 ................................................... 218
    12.3.3. 建立模板文件 ............................................................................ 221
    12.4. AutoConfig的使用 —— 布署者指南 ..................................................... 223
    12.4.1. 在命令行中使用AutoConfig ...................................................... 223
    12.4.2. 在maven中使用AutoConfig ..................................................... 224
    12.4.3. 運行並觀察AutoConfig的結果 ................................................... 226
    Webx框架指南
    viii
    12.4.4. 共享properties文件 ................................................................... 227
    12.4.5. AutoConfig常用命令 ................................................................ 229
    12.5. 本章總結 ............................................................................................. 231
    第 13 章 AutoExpand工具使用指南 ...................................................................... 232
    13.1. AutoExpand工具簡介 ......................................................................... 232
    13.1.1. Java、JavaEE打包的格式 ......................................................... 232
    13.1.2. 應用布署的方式 ......................................................................... 233
    13.1.3. AutoExpand的用武之地 ........................................................... 233
    13.2. AutoExpand的使用 ............................................................................. 234
    13.2.1. 取得AutoExpand ..................................................................... 234
    13.2.2. 執行AutoExpand ..................................................................... 234
    13.2.3. AutoExpand和AutoConfig的合作 ............................................ 235
    13.3. AutoExpand的參數 ............................................................................. 236
    13.4. 本章總結 ............................................................................................. 237
    ix
    引言
    1. 閱讀嚮導 ..................................................................................................................... ix
    2. Webx是什麼? ............................................................................................................ ix
    3. Webx的歷史 ............................................................................................................... ix
    4. 為什麼要用Webx而不是其它的開源框架? ..................................................................... x
    5. Webx的優勢 ................................................................................................................ x
    5.1. 成熟可靠性 ........................................................................................................ x
    5.2. 開放和擴展性 ..................................................................................................... x
    6. Webx還缺少什麼? ...................................................................................................... x
    1. 閱讀嚮導
    注意
    • 如果你希望馬上嘗試用webx來搭建應用,請轉至第 IV 部分 「Webx應用實
    作」。
    • 如果你想了解webx的整體設計思想,請轉至第 I 部分 「Webx框架概覽」。
    • 如果你想進一步了解webx的每個具體服務,請轉至第 II 部分 「Webx基礎設施
    服務」以及第 III 部分 「Webx應用支持服務」。
    • 如果你想了解一些常用的開發工具,請轉至第 V 部分 「輔助工具」。
    2. Webx是什麼?
    Webx是一套基於Java Servlet API的通用Web框架。它在Alibaba集團內部被廣泛使用。從
    2010年底,向社會開放源碼。
    3. Webx的歷史
    • 2001年,阿里巴巴內部開始使用Java Servlet作為WEB伺服器端的技術,以取代原先的
    Apache HTTPD server和mod_perl的組合。
    • 2002年,選擇Jakarta Turbine作為WEB框架,並開始在此之上進行擴展。
    • 2003年,經過大約一年的擴展,框架開始成熟。我們私下稱這個經過改進的Turbine框架為
    Webx 1.0。
    • 2004年,借著淘寶網的第一次改版,我們正式推出了Webx 2.0。由於Turbine開源項目發展
    過於緩慢,我們不得不放棄它。Webx 2.0是從零開始完全重寫的,僅管它仍然延續了Turbine
    的使用風格。
    • 2004年11月,Webx 2.0和Spring框架整合。
    • 從那以後,Webx 2.0一直在進化,但沒有作根本性的改動。
    • 2010年,Webx 3.0發布。Webx 3.0拋棄了Webx 2.0中過時的、從Turbine中發展而來的
    Service框架,直接採用Spring作為其基礎,並對Spring作了重大改進。Webx 3.0完全兼容
    Webx 2.0的代碼,只需要修改配置文件就可完成升級。
    • 2010年底,Webx 3.0開源。
    引言
    x
    4. 為什麼要用Webx而不是其它的開源框架?
    現在有很多Java的Web框架可供選擇,並且它們也都是免費的。例如,
    • Struts - http://struts.apache.org/
    • Webwork - http://www.opensymphony.com/webwork/
    • Tapestry - http://tapestry.apache.org/
    • Spring MVC - http://www.springsource.org/
    • ⋯⋯
    以上框架都是非常優秀的。說實話,如果阿里巴巴網站在2001年開始,就有這麼多可選擇的
    話,無論選擇哪一個都不會有問題。因為這些年來,所有的開源Web框架都在互相學習、並趨
    於相似。Webx也不例外,它吸收了其它框架的很多想法。因此,當你使用Webx的時候,你會
    覺得在很多方面,它和其它開源的框架非常類似。
    我並不是說所有的框架都一樣好,而是說只要假以時日,所有的框架在發展過程中,必然會積
    聚好的方面,淘汰壞的方面,從而變得足夠好。從這個角度看,的確沒有特別明顯的理由來選擇
    Webx,但也沒有明顯的理由不選擇Webx。
    另一方面,由於每一種框架採用不同的設計,必然會有各自的優勢。Webx也是如此 —— 它在
    某些方面有一些獨到的設計,超越了同類框架。Webx有哪些優勢呢?
    5. Webx的優勢
    5.1. 成熟可靠性
    這個優勢主要是針對阿里巴巴及屬下網站而言。因為Webx在阿里巴巴和淘寶用了很多年。對於
    這種超大訪問量的電子商務網站,Webx經受了考驗,被證明是成熟可靠的。
    5.2. 開放和擴展性
    • 對Spring的直接支持 —— Spring是當今主流的輕量級框架。Webx 3.0和Spring MVC一樣,
    完全建立在Spring框架之上,故可運用Spring的所有特性。
    • 擴展性 —— Webx 3.0對Spring做了擴展,使Spring Bean不再是「bean」,而是升級
    成「組件」。一個組件可以擴展另一個組件,也可以被其它組件擴展。這種機製造就了Webx
    的非常好的擴展性,且比未經擴展的Spring更易使用。
    • 開放性 —— Webx被設計成多個層次,層次間的分界線很清晰。每個層次都足夠開放和易於
    擴展。你可以使用全部的Webx,也可以僅僅使用到Webx的任何一個層次。
    6. Webx還缺少什麼?
    和目前快速發展的開源框架相比,Webx似乎不夠時髦,因為它還缺少對很多流行功能的直接支
    持 —— 並非不支持,而是沒有方便的方法來直接完成。例如:
    • 目前Webx只支持服務端的表單驗證,而沒有直接支持客戶端的JS驗證。
    • 目前Webx沒有直接支持AJAX編程。
    • 目前Webx沒有直接支持REST編程。
    引言
    xi
    • 目前Webx沒有直接支持Web Flow。
    凡是缺少的功能,我們將在未來的版本中陸續加上。
    部分 I. Webx框架概覽
    2
    第 1 章 Webx總體介紹 ..................................................................................................... 4
    1.1. 設計理念 ........................................................................................................... 4
    1.1.1. 框架的本質 ............................................................................................. 4
    1.1.2. 基礎框架 ................................................................................................ 4
    1.1.3. 層次化 ................................................................................................... 5
    1.2. Webx的層次 ..................................................................................................... 6
    1.2.1. 三個大層次 ............................................................................................. 6
    1.2.2. 剪裁和定製Webx .................................................................................... 7
    1.3. 本章總結 ........................................................................................................... 9
    第 2 章 SpringExt ........................................................................................................... 10
    2.1. 用SpringExt裝配服務 ....................................................................................... 10
    2.1.1. Spring Beans ....................................................................................... 11
    2.1.2. Spring Schema ................................................................................... 12
    2.1.3. SpringExt Schema ............................................................................... 15
    2.2. SpringExt原理 ................................................................................................. 16
    2.2.1. XML Schema中的秘密 ......................................................................... 16
    2.2.2. 擴展點,Configuration Point ............................................................... 17
    2.2.3. 捐獻,Contribution .............................................................................. 17
    2.2.4. 組件和包 .............................................................................................. 18
    2.2.5. 取得Schemas ...................................................................................... 19
    2.3. SpringExt其它特性 ........................................................................................... 22
    2.4. 本章總結 ......................................................................................................... 23
    第 3 章 Webx Framework .............................................................................................. 24
    3.1. Webx的初始化 ................................................................................................ 24
    3.1.1. 初始化級聯的Spring容器 ....................................................................... 24
    3.1.2. 初始化日誌系統 .................................................................................... 26
    3.2. Webx響應請求 ................................................................................................ 27
    3.2.1. 增強request、response、session的功能 ............................................... 27
    3.2.2. Pipeline流程機制 .................................................................................. 29
    3.2.3. 異常處理機制 ........................................................................................ 30
    3.2.4. 開發模式工具 ........................................................................................ 30
    3.2.5. 響應和處理請求的更多細節 .................................................................... 33
    3.3. 定製Webx Framework .................................................................................... 36
    3.3.1. 定製WebxRootController .................................................................... 36
    3.3.2. 定製WebxController ............................................................................ 36
    3.4. 本章總結 ......................................................................................................... 36
    第 4 章 Webx Turbine .................................................................................................... 37
    4.1. 設計理念 ......................................................................................................... 37
    4.1.1. 頁面驅動 .............................................................................................. 37
    4.1.2. 約定勝於配置 ........................................................................................ 38
    4.2. 頁面布局 ......................................................................................................... 39
    4.3. 處理頁面的基本流程 ......................................................................................... 40
    4.4. 依賴注入 ......................................................................................................... 42
    4.4.1. Spring原生注入手段 .............................................................................. 42
    4.4.2. 注入request、response和session對象 ................................................... 42
    Webx框架概覽
    3
    4.4.3. 參數注入 .............................................................................................. 43
    4.5. 定製Webx Turbine .......................................................................................... 43
    4.6. 本章總結 ......................................................................................................... 44
    4
    第 1 章 Webx總體介紹
    1.1. 設計理念 ................................................................................................................... 4
    1.1.1. 框架的本質 ..................................................................................................... 4
    1.1.2. 基礎框架 ........................................................................................................ 4
    1.1.3. 層次化 ........................................................................................................... 5
    1.2. Webx的層次 ............................................................................................................. 6
    1.2.1. 三個大層次 ..................................................................................................... 6
    1.2.2. 剪裁和定製Webx ............................................................................................ 7
    1.3. 本章總結 ................................................................................................................... 9
    本章概要地介紹了Webx框架的整體結構和設計。如果你想了解更多,請參考其它詳細文檔。
    1.1. 設計理念
    1.1.1. 框架的本質
    圖 1.1. 框架結構的建築
    應用框架(Application Framework),讓人聯想到建築的框架(Frame Structure)。
    • 建築框架確定了整個建築的結構;應用框架確定了應用的結構。
    • 建築框架允許你在不改變結構的基礎上,自由改變其內容。例如,你可以用牆體隨意分隔房
    間。應用框架允許你在不改變整體結構的基礎上,自由擴展功能。
    可以這樣說,框架的本質就是「擴展」。維基百科這樣定義描寫「軟體框架」,它說一個軟體框
    架必須符合如下要素:
    Inversion of Control 反轉控制應用的流程不是由應用控制的,而是由框架控制的。
    Default Behavior 默認行為框架會定義一系列默認的行為。
    Extensibility 擴展性應用可以擴展框架的功能,也可以修改框架的默認行
    為。
    Non-modifiable Framework Code 框架本身不可更改框架在被擴展時,自身的代碼無須被改變。
    在一個框架中,實現豐富的功能固然重要,然而更重要的是:建立良好的擴展機制。我們知
    道,Webx目前雖然欠缺一些流行的功能。然而Webx卻有一個良好的擴展機制,來支持開發者
    增加新的功能。
    1.1.2. 基礎框架
    縱觀開源的Web框架,做得比較好的框架,都有一個共性 —— 它們並不是簡單地實現Web應
    用所需要的功能(諸如Action、模板、表單驗證等),而是把框架建立在另一個基礎框架之
    Webx總體介紹
    5
    上。這個基礎框架的作用是:組裝模塊;提供擴展機制。建立在這種基礎上的Web框架有很好
    的適應性和擴展性,可以應對Web應用不斷變化和發展的需求。
    • 早期的Turbine,建立在Service框架之上。
    • Webwork,建立在Xwork框架之上。
    • Tapestry,建立在HiveMind或Tapestry IOC框架之上。
    • 早期的Struts 1.x由於沒有一個輕量框架作為基礎,因此很難擴展。而Struts 2.x使用了
    Webwork和Xwork,因此適用能力大為提高。
    • Spring MVC,建立在Spring框架之上。
    一個Web框架的好壞,往往不是由它所實現的具體功能的好壞決定的,而是由其所用的基礎框
    架的好壞決定的。
    Webx建立在SpringExt的基礎上 —— SpringExt是對Spring的擴展。Spring是當今主流的輕量級
    框架。SpringExt沒有損失任何Spring的功能,但它能夠提供比Spring自身更強大的擴展能力。
    1.1.3. 層次化
    設計良好的模塊,應該是層次化的。
    例如,模塊B擴展了模塊A,同時被模塊C擴展。這樣就形成了A、B、C三個層次。
    圖 1.2. 模塊的層次
    如圖所示,層次之間有如下的關係:
    • 上層定義規則,下層定義細節;(上層、下層也可稱為內層、外層)
    • 上層是抽象的,下層是具體的;
    • 越上層,越穩定(越少改變);越下層,越易變。
    • 依賴倒轉(Dependency Inversion)。下層(具體)依賴上層(抽象),而不是上層依賴下
    層。
    Webx總體介紹
    6
    • 下層擴展上層時,不需要修改到上層的任何代碼和配置。即符合開閉原則(Open-Closed
    Principle簡稱OCP – Open for extension, Closed for modification)。
    • 每一層均可被替換。
    層次化的設計,使軟體中的每一個部分都可被增強或替換。
    層次化不是自然而然的,而是需要精心的設計。設計一個層次化的組件,可以從下面幾方面來考
    慮:
    • 切分功能。每個組件專心做一件事。
    • 分析哪些會改變,哪些不會改變。不變部分固化在組件中,可能會改變的部分抽象成介面,以
    便擴展。
    • 考慮默認值和默認擴展。默認值和默認擴展應該是最安全、最常用的選擇。對於默認值和默認
    擴展,用戶在使用時不需要額外的配置。
    Webx鼓勵層次化的模塊設計,而SpringExt提供了創建和配置層次化組件的機制。
    1.2. Webx的層次
    1.2.1. 三個大層次
    很多用過Webx框架的人說起Webx,就想到:Webx如何處理頁面、如何驗證表單、如何渲染
    模板等等功能。事實上,這些只不過是Webx最外層、最易變、非本質的功能。
    Webx框架不僅鼓勵層次化設計,它本身也是層次化的。你既可以使用全部的Webx框架,也可
    以只使用部分的Webx框架。大體上,Webx框架可以劃分成三個大層次,如圖所示。
    圖 1.3. Webx的層次
    Webx總體介紹
    7
    1. SpringExt:基於Spring,提供擴展組件的能力。它是整個框架的基礎。
    2. Webx Framework:基於Servlet API,提供基礎的服務,例如:初始化Spring、初始化日
    志、接收請求、錯誤處理、開發模式等。Webx Framework只和servlet及spring相關 ——
    它不關心Web框架中常見的一些服務,例如Action處理、表單處理、模板渲染等。因此,事
    實上,你可以用Webx Framework來創建多種風格的Web框架。
    3. Webx Turbine:基於Webx Framework,實現具體的網頁功能,例如:Action處理、表單
    處理、模板渲染等。
    1.2.2. 剪裁和定製Webx
    並非所有的開發者都需要使用Webx的全部。下面列舉幾種情形。
    1.2.2.1. 級別一:僅使用SpringExt,適用於非Web應用、單元測試
    對於非Web應用和單元測試,但卻想擁有Spring和SpringExt的功能,可以直接創建SpringExt容
    器:
    例 1.1. 直接創建SpringExt容器
    import java.io.File;
    import org.springframework.core.io.FileSystemResource;
    import com.alibaba.citrus.springext.support.context.XmlApplicationContext;
    ...
    XmlApplicationContext parentContext = new XmlApplicationContext(
    new FileSystemResource(new File(srcdir, "parent.xml")));
    XmlApplicationContext context = new XmlApplicationContext(
    new FileSystemResource(new File(srcdir, "app.xml")), parentContext);
    Object mybean = context.getBean("mybean");
    請注意代碼所使用的ApplicationContext實現類為SpringExt擴展的類型
    (c.a.c.springext.s.c.XmlApplicationContext)。通過這個實現類,你除
    了可以使用原來Spring的所有功能以外,還可以使用SpringExt的所有功能,包括:
    Schema、Configuration Points和Contributions、ResourceLoadingService等。
    這行代碼從一個配置文件中創建容器。
    這行代碼創建了一個子容器。多個子容器和父容器之間可組成一個樹狀級聯的容器結構。
    在子容器中可以訪問到所有父容器中的beans和服務,但反過來是不成立的。
    1.2.2.2. 級別二:僅使用SpringExt及Web組件,在此基礎上運行Spring MVC、Struts等
    非webx框架
    非webx框架也可以使用SpringExt的全部功能。
    Webx總體介紹
    8
    例 1.2. 修改/WEB-INF/web.xml,讓非webx框架支持SpringExt
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    <!-- 初始化日誌系統,裝載/WEB-INF/log4j.xml或/WEB-INF/logback.xml -->
    <listener>
    <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class>
    </listener>
    <!-- 初始化Spring容器,裝載/WEB-INF/webx.xml, /WEB-INF/webx-*.xml -->
    <listener>
    <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class>
    </listener>
    <!-- 下面配置:Spring MVC、Struts的filter、servlet、mapping... -->
    ……
    </web-app>
    這裡使用了webx的WebxContextLoaderListener來初始化spring容器,而不是用spring
    原生的ContextLoaderListener。不用擔心,前者完全兼容後者。事實上前者是從後者派
    生而來的。
    1.2.2.3. 級別三:僅使用Webx Framework,創造新的Web框架
    也許你想做一個新的Web框架 —— 因為你並不想使用Webx Turbine中提供的頁面處理的方
    案,但你仍然可以使用Webx Framework所提供的服務,例如:錯誤處理、開發模式等。
    例 1.3. 修改/WEB-INF/webx.xml,以創建新的WEB框架
    <webx-configuration xmlns="http://www.alibaba.com/schema/services">
    <components defaultControllerClass="com.myframework.MyController">
    <rootController class="com.myframework.MyRootController" />
    </components>
    </webx-configuration>
    MyController擴展了AbstractWebxController。
    MyRootController擴展了AbstractWebxRootController。
    這個方案非常適合作為一個新Web框架的起點 —— 免去了創建Servlet/Filter、初始化Spring容
    器、處理request/response等繁雜事務,並且完全支持SpringExt的所有功能,此外還包含了錯
    誤處理、開發模式等Webx Framework中的一切便利。
    另一種以Webx Framework為基礎的創建新框架的方法,是從pipeline入手。通過pipeline,
    理論上可以實現任何框架的功能。
    1.2.2.4. 級別四:使用整個Webx框架,定製Turbine
    假如你想使用幾乎大部分Webx的功能,但希望對少數步驟進行改進,你可以修改pipeline。
    Webx Turbine本身定義了一套pipeline的實現,但是你完全可以去修改它:插入一些步驟、刪
    除一些步驟、修改一些步驟 —— 所有都取決於你。
    最常見的一個需求,是在Webx Turbine中添加許可權驗證 —— 只需要插入一個步驟就可以做到
    了。
    Webx總體介紹
    9
    1.3. 本章總結
    Webx框架是一個穩定、強大的Web框架。倒不是說它實現了所有的功能,而是它建立在
    SpringExt的基礎上,具有超強的擴展能力。你可以使用全部的Webx,也可以使用部分Webx。
    你也可以比較容易地用SpringExt做出自己的可擴展組件。
    10
    第 2 章 SpringExt
    2.1. 用SpringExt裝配服務 ............................................................................................... 10
    2.1.1. Spring Beans ............................................................................................... 11
    2.1.2. Spring Schema ........................................................................................... 12
    2.1.3. SpringExt Schema ....................................................................................... 15
    2.2. SpringExt原理 ......................................................................................................... 16
    2.2.1. XML Schema中的秘密 ................................................................................. 16
    2.2.2. 擴展點,Configuration Point ....................................................................... 17
    2.2.3. 捐獻,Contribution ...................................................................................... 17
    2.2.4. 組件和包 ...................................................................................................... 18
    2.2.5. 取得Schemas .............................................................................................. 19
    2.3. SpringExt其它特性 ................................................................................................... 22
    2.4. 本章總結 ................................................................................................................. 23
    Webx是一套基於Java Servlet API的通用Web框架。Webx致力於提供一套極具擴展性的機
    制,來滿足Web應用不斷變化和發展的需求。而SpringExt正是這種擴展性的基石。SpringExt擴
    展了Spring,在Spring的基礎上提供了一種擴展功能的新方法。
    本章將告訴你SpringExt是什麼?它能做什麼?本章不會涉及太深的細節,如果你想了解更多,
    請參考其它文檔。
    2.1. 用SpringExt裝配服務
    在Webx中有一個非常有用的ResourceLoadingService。現在我們以這個服務為例,來說明
    SpringExt的用途。
    ResourceLoadingService是一個可以從各種輸入源中(例如從File
    System、Classpath、Webapp中)查找和讀取資源文件的服務。有點像Linux的文件系統
    —— 你可以在一個統一的樹形目錄結構中,定位(mount)任意文件系統,而應用程序不需要
    關心它所訪問的資源文件屬於哪個具體的文件系統。
    ResourceLoadingService的結構如圖所示。這是一個既簡單又典型的面向對象的設計。
    圖 2.1. Resource Loading服務的設計
    SpringExt
    11
    下面我們嘗試在Spring容器中裝配ResourceLoadingService服務。為了更好地說明問題,下文
    所述的Spring配置是被簡化的,未必和ResourceLoadingService的真實代碼相吻合。
    2.1.1. Spring Beans
    在Spring 2.0以前,你只能裝配beans,就像下面這樣:
    例 2.1. 用Spring Beans裝配Resource Loading服務
    <bean id="resourceLoadingService" class="com.alibaba...ResourceLoadingServiceImpl">
    <property name="mappings">
    <map>
    <entry key="/file" value-ref="fileLoader" />
    <entry key="/webroot" value-ref="webappLoader" />
    </map>
    </property>
    </bean>
    <bean id="fileLoader" class="com.alibaba...FileResourceLoader">
    <property name="basedir" value="${user.home}" />
    </bean>
    <bean id="webappLoader" class=" com.alibaba...WebappResourceLoader" />
    以上是一個典型的Spring beans的配置方案。這種方案簡單易行,很好地體現了Spring的基
    礎理念:IoC(Inversion of Control,依賴反轉)。ResourceLoadingServiceImpl並不依賴
    FileResourceLoader和WebappResourceLoader,它只依賴它們的介面ResourceLoader。
    至於如何創建FileResourceLoader、WebappResourceLoader、需要提供哪些參數,這種瑣
    事全由spring包辦。
    然而,其實spring本身並不了解如何創建ResourceLoader的對象、需要用哪些參數、如何裝配
    和注入等知識。這些知識全靠應用程序的裝配者(assembler)通過上述spring的配置文件來告
    訴spring的。也就是說,儘管ResourceLoaderServiceImpl類的作者不需要關心這些瑣事,但
    還是有人得關心。
    為了說明問題,我先定義兩個角色:「服務提供者」和「服務使用者」(即「裝配
    者」)。在上面的例子中,ResourceLoadingService的作者就是服務的提供者,使
    用ResourceLoadingService的人,當然就是服務使用者。服務使用者利用spring把
    ResourceLoadingService和ResourceLoader等其它服務裝配在一起,使它們可以協同工作。
    當然這兩個角色有時會是同一個人,但多數情況下會是兩個人。因此有必要把這兩個角色的職責
    區分清楚,才能合作。
    SpringExt
    12
    圖 2.2. 服務提供者和使用者的關係
    如圖所示。虛線左邊代表「服務提供者」的職責,虛線右邊代表「服務使用者」(即「裝配
    者」)的職責。
    從圖中可以看到,Spring的配置文件會依賴於服務實現類的公開API。裝配者除非查看源代碼
    (如ResourceLoadingServiceImpl的源碼)或者API文檔才能精確地獲知這些API的細節。這
    有什麼問題呢?
    • 沒有檢驗機制,錯誤必須等到運行時才會被發現。裝配者僅從spring配置文件中,無法直觀地
    了解這個配置文件有沒有寫對?例如:應該從constructor args注入卻配成了從properties注
    入;寫錯了property的名稱;注入了錯誤的類型等等。
    • 無法了解更多約束條件。即使裝配者查看API源碼,也未必能了解到某些約束條件,例如:哪
    些properties是必須填寫的,哪些是可選的,哪些是互斥的?
    • 當服務的實現被改變時,Spring配置文件可能會失敗。因為Spring配置文件是直接依賴於服務
    的實現,而不是介面的。介面相對穩定,而實現是可被改變的。另一方面,這個問題也會阻礙
    服務提供者改進他們的服務實現。
    難怪有人詬病Spring說它只不過是用XML來寫程序代碼而已。
    2.1.2. Spring Schema
    這種情況直到Spring 2.0發布以後,開始有所改觀。因為Spring 2.0支持用XML Schema來定義
    配置文件。同樣的功能,用Spring Schema來定義,可能變成下面的樣子:
    SpringExt
    13
    例 2.2. 用Spring Schema裝配Resource Loading服務
    <resource-loading id="resourceLoadingService"
    xmlns="http://www.alibaba.com/schema/services/resource-loading">
    <resource pattern="/file">
    <file-loader basedir="${user.home}" />
    </resource>
    <resource pattern="/webroot">
    <webapp-loader />
    </resource>
    </resource-loading>
    怎麼樣?這個配置文件是不是簡單很多呢?和直接使用Spring Beans配置相比,這種方式有如
    下優點:
    • 很明顯,這個配置文件比起前面的Spring Beans風格的配置文件簡單易讀得多。因
    為在這個spring配置文件里,它所用的「語言」是「領域相關」的,也就是說,和
    ResourceLoadingService所提供的服務內容相關,而不是使用像bean、property這樣的編
    程術語。這樣自然易讀得多。
    • 它是可驗證的。你不需要等到運行時就能驗證其正確性。任何一個支持XML Schema的標準
    XML編輯器,包括Eclipse自帶的XML編輯器,都可以告訴你配置的對錯。
    • 包含更多約束條件。例如,XML Schema可以告訴你,哪些參數是可選的,哪些是必須填
    的;參數的類型是什麼等等。
    • 服務的實現細節對裝配者隱藏。當服務實現改變時,只要XML Schema是不變的,那麼
    Spring的配置就不會受到影響。
    以上優點中,最後一點是最重要優點。通過Spring Schema來定義配置文件,裝配者無須再了
    解諸如「ResourceLoadingService的實現類是什麼」、「需要什麼參數」等細節。那麼Spring
    是如何得知這些內容呢?
    奧秘在於所有的schema都會有一個「解釋器」和它對應(即BeanDefinitionParser)。這個解
    釋器負責將符合schema定義的XML配置,轉換成Spring能解讀的beans定義。解釋器是由服務
    的開發者來提供的 —— 在本例中,ResourceLoadingService的開發者會提供這個解釋器。
    SpringExt
    14
    圖 2.3. 用Schema改善服務角色之間的關係
    如圖所示,虛線右側的裝配者,不再需要了解服務具體實現類的API,它只要遵循標準的XML
    Schema定義來書寫spring配置文件,就可以得到正確的配置。這樣一來,虛線左側的服務提供
    者就有自由可以改變服務的實現類,他相信只要服務的介面和XML Schema不改變,服務的使
    用者就不會受影響。
    將和具體實現相關的工作,例如提供類名、property名稱和類型等工作,交還給服務的提供
    者,使服務的使用者(即裝配者)可以用它所能理解的語言來裝配服務,這是Spring Schema
    所帶來的核心價值。
    然而,Spring Schema有一個問題 —— 它是不可擴展的。
    仍以ResourceLoadingService為例。僅管在API層面, ResourceLoadingService支持任何對
    ResourceLoader介面的擴展,例如,你可以添加一種新的DatabaseResourceLoader,以便
    讀取資料庫中的資源。但在Spring配置文件上,你卻無法自由地添加新的元素。比如:
    例 2.3. 嘗試在Spring Schema所裝配的Resource Loading服務中,添加新的裝載器
    <resource-loading id="resourceLoadingService"
    xmlns="http://www.alibaba.com/schema/services/resource-loading">
    <resource pattern="/file">
    <file-loader basedir="${user.home}" />
    </resource>
    <resource pattern="/webroot">
    <webapp-loader />
    </resource>
    <resource pattern="/db">
    <database-loader connection="jdbc:mysql:mydb" />
    </resource>
    </resource-loading>
    裝配者希望在這裡添加一種新的裝載器:database-loader。然而,如果在設
    計<resource-loading>的schema時,並沒有預先考慮到database-loader這種情況,
    那麼這段配置就會報錯。
    SpringExt
    15
    使用Spring Schema時,裝配者無法自主地往Spring配置文件中增加新的Resource Loader類
    型,除非通知服務提供者去修改<resource-loading>的schema —— 然而這違反了面向對象
    設計中的基本原則 —— OCP(Open Closed Principle)。OCP原則是面向對象設計的強大之
    源。它使得我們可以輕易地添加新的功能,卻不需要改動老的代碼;它使設計良好的代碼成果可
    以被疊加和組合,以便實現更複雜的功能。
    從本質意義來講,Schema是API的另一種表現形式。你可以把Schema看作一種介面,而介面
    的實質是服務的提供者與使用者之間的合約(contract)。可惜的是,我們只能在傳統API層面
    來貫徹OCP原則,卻無法在Schema上同樣遵循它。我們無法做到不修改老的schema,就添
    加新的元素 —— 這導致Spring Schema的作用被大大削弱。
    2.1.3. SpringExt Schema
    SpringExt改進了Spring,使得Spring Schema可以被擴展。下面的例子對例 2.2 「用Spring
    Schema裝配Resource Loading服務」作了少許修改,使之能被擴展。
    例 2.4. 用SpringExt Schema裝配Resource Loading服務
    <resource-loading id="resourceLoadingService"
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    <resource pattern="/file">
    <loaders:file-loader basedir="${user.home}" />
    </resource>
    <resource pattern="/webroot">
    <loaders:webapp-loader />
    </resource>
    </resource-loading>
    重新定義namespaces —— 將ResourceLoader和<resource-loading>所屬的
    namespace分離。
    將file-loader和webapp-loader放在loaders名字空間中,表示它們是Resource
    Loaders的擴展。
    上面的配置文件和前例中使用Spring Schema的配置文件差別很小。沒錯,SpringExt Schema
    和Spring Schema是完全兼容的!唯一的差別是,我們把ResourceLoader和<resourceloading>
    所屬的namespace分開了,然後將ResourceLoader的配置放在專屬的namespace
    「loaders」中。例如:<loaders:file-loader>。這樣一來,我們就有辦法在不修
    改<resource-loading>的schema的前提下,添加新的ResourceLoader實現。例如我們要添
    加一種新的ResourceLoader擴展 —— DatabaseResourceLoader,只需要做以下兩件事:
    1. 將包含DatabaseResourceLoader所在的jar包添加到項目的依賴中。如果你是用maven來管
    理項目,那麼意味著你需要修改一下項目的pom.xml。
    2. 在spring配置文件中添加如下行:
    SpringExt
    16
    例 2.5. 在SpringExt Schema所裝配的Resource Loading服務中,添加新的裝載器
    <resource-loading id="resourceLoadingService"
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    <resource pattern="/file">
    <loaders:file-loader basedir="${user.home}" />
    </resource>
    <resource pattern="/webroot">
    <loaders:webapp-loader />
    </resource>
    <resource pattern="/db">
    <loaders:database-loader connection="jdbc:mysql:mydb" />
    </resource>
    </resource-loading>
    添加一個新的loader,而無須改變<resource-loading>的schema。
    完美!你無須通知ResourceLoadingService的作者去修改它的schema,一種全新的
    ResourceLoader擴展就這樣被注入到ResourceLoadingService中。正如同你在程序代碼里,
    無須通知ResourceLoadingService的作者去修改它的實現類,就可以創建一種新的、可被
    ResourceLoadingService調用的ResourceLoader實現類。這意味著,我們在Spring配置文件
    的層面上,也滿足了OCP原則。
    2.2. SpringExt原理
    2.2.1. XML Schema中的秘密
    下面這段配置是例 2.5 「在SpringExt Schema所裝配的Resource Loading服務中,添加新的
    裝載器」的spring配置文件的片段。
    <resource-loading>
    ...
    <resource pattern="/db">
    <loaders:database-loader connection="jdbc:mysql:mydb" />
    </resource>
    </resource-loading>
    其中,<resource-loading>是由resource-loading.xsd這個schema來定義的。而在開
    發resource-loading服務的時候,database-loader這種新的擴展還不存在 —— 也就是
    說,resource-loading.xsd對於database-loader一無所知。可為什麼以上配置能通過XML
    Schema的驗證呢?我們只需要查看一下resource-loading.xsd就可以知道答案了:
    例 2.6. Schema pian段:<resource-loading>中如何定義loaders
    <xsd:element name="resource" type="ResourceLoadingServiceResourceType">
    <xsd:complexType name="ResourceLoadingServiceResourceType">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
    <xsd:any namespace="http://www.alibaba.com/schema/services/resource-loading/loaders" />
    </xsd:choice>
    <xsd:attribute name="pattern" type="xsd:string" use="required" />
    </xsd:complexType>
    這裡運用了XML Schema中的<xsd:any>定義,相當於說:<resource> element下面,
    可以跟任意多個<loaders:*> elements。
    SpringExt
    17
    <xsd:any>定義只關心namespace,不關心element的名稱,自然可以接受未知
    的<database-loader> element,前提是<database-loader>的namespace是「http://
    www.alibaba.com/schema/services/resource-loading/loaders」。
    在這段配置中,<loaders:database-loader>標籤通知SpringExt:將database-loader的實
    現注入到resource-loading的服務中。這種對應關係是如何建立起來的呢?
    在XML里,loaders前綴代表namespace:「http://www.alibaba.com/schema/
    services/resource-loading/loaders」;但對SpringExt而言,它還代表一個更重要的
    意義:擴展點,或稱為ConfigurationPoint。ConfigurationPoint將namespace和可擴展
    的ResourceLoader介面關聯起來。
    在XML里,database-loader代表一個XML element;但對SpringExt而言,它還代表一個更重
    要的意義:捐獻,或稱為Contribution。Contribution將element和對ResourceLoader介面的
    具體擴展關聯起來。
    圖 2.4. SpringExt的概念:擴展點和捐獻
    2.2.2. 擴展點,Configuration Point
    SpringExt用「擴展點,Configuration Point」來代表一個可被擴展的介面。每個擴展點都:
    • 對應一個唯一的名稱,例如:services/resource-loading/loaders。
    • 對應一個唯一的namespace,例如:http://www.alibaba.com/schema/services/
    resource-loading/loaders。
    • 對應一個唯一的schema,例如:services-resource-loading-loaders.xsd。
    2.2.3. 捐獻,Contribution
    SpringExt把每一個對擴展點的具體擴展稱作「捐獻,Contriubtion」。每個捐獻都:
    SpringExt
    18
    • 在對同一擴展點的所有捐獻中,擁有一個唯一的名字,例如:file-loader,webapploader,
    database-loader等。
    • 對應一個唯一的schema,例如:
    • services/resource-loading/loaders/file-loader.xsd
    • services/resource-loading/loaders/webapp-loader.xsd
    • services/resource-loading/loaders/database-loader.xsd
    2.2.4. 組件和包
    在前面的例子中,resource-loading服務調用了loaders擴展點,而file-loader、webapploader
    等則擴展了loaders擴展點。然而事實上,resource-loading服務本身也是對另一個擴
    展點「services」的擴展。services擴展點是Webx內部定義了一個頂級擴展點。
    在SpringExt中,一個模塊既可以成為別的模塊的擴展,也可以被別的模塊來擴展。這樣的模塊
    被稱為「組件」。
    圖 2.5. 組件
    如圖所示,resource-loading組件既擴展了services擴展點,又可被其它組件所擴展。
    SpringExt
    19
    當你需要增加一種新的擴展時,你不需要改動原有包(例如resource-loadings.jar)中的任
    何內容,你只需要將新的擴展所在的jar包(例如database-loader.jar)加入到依賴表中即
    可。假如你使用maven來管理項目,意味著你需要修改項目的pom.xml描述文件,以便加入新
    的擴展包。
    2.2.5. 取得Schemas
    最後剩下的一個問題是,如何找到Schemas?為了找到schema,我們必須在Spring配置文件
    中指定Schema的位置。
    例 2.7. 在XML中指定Schema Location
    <?xml version="1.0" encoding="UTF-8" ?>
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:loaders="http://www.alibaba.com/schema/services/resource-loading/loaders"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
    http://www.alibaba.com/schema/services
    http://localhost:8080/schema/services.xsd
    http://www.alibaba.com/schema/services/resource-loading/loaders
    http://localhost:8080/schema/services-resource-loading-loaders.xsd
    http://www.springframework.org/schema/beans
    http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
    ">
    ...
    </beans:beans>
    指定schema的位置。
    這裡看起來有一點奇怪,因為它把schema的位置(xsi:schemaLocation)指向了一台本地服
    務器:localhost:8080。為什麼這樣做呢?要回答這個問題,先要搞清楚另一個問題:有哪些
    部件需要用到schema?
    2.2.5.1. XML編輯器需要讀取schemas
    XML編輯器通過訪問schema,可以實現兩大功能:
    • 語法提示的功能。
    圖 2.6. Eclipse XML編輯器彈出的語法提示
    • 驗證spring配置文件的正確性。
    圖 2.7. Eclipse XML編輯器驗證spring配置文件時,顯示的錯誤信息
    SpringExt
    20
    XML編輯器取得schema內容的途徑有兩條,一條途徑是訪問schemaLocation所指示的網址。
    因此,
    • 假如你聲明的schemaLocation為:http://www.alibaba.com/schema/services.xsd,那
    么XML編輯器就會嘗試訪問www.alibaba.com伺服器。
    • 假如你聲明的schemaLocation為: http://www.springframework.org/schema/beans/
    spring-beans.xsd,那麼XML編輯器就會嘗試訪問www.springframework.org伺服器。
    然而,在外部伺服器(例如www.alibaba.com和www.springframework.org)上維護一套
    schema是很困難的,因為:
    • 你未必擁有外部伺服器的控制權;
    • 你很難讓外部伺服器上的schema和你的組件版本保持一致;
    • 當你無法連接外部伺服器的時候(例如離線狀態),會導致XML編輯器無法幫你驗證spring配
    置文件的正確性,也無法幫你彈出語法提示。
    XML編輯器取得schema內容的另一條途徑是將所有的schema轉換成靜態文件,然後定義一個
    標準的XML Catalog來訪問這些schema文件。然而這種方法的難點類似於將schema存放在
    外部伺服器上 —— 你很難讓這些靜態文件和你的組件版本保持一致。
    SpringExt提供了一個解決方案,可以完全解決上述問題。你可以使用SpringExt所提供的
    maven插件,在localhost本機上啟動一個監聽8080埠的Schema Server,通過它就可以訪
    問到所有的schema:
    mvn springext:run
    上述命令執行以後,打開瀏覽器,輸入網址http://localhost:8080/schema就可以看到類似
    下面的內容:
    圖 2.8. 用SpringExt maven插件羅列schemas
    SpringExt
    21
    這就是為什麼例 2.7 「在XML中指定Schema Location」中,把schemaLocation指
    向localhost:8080的原因。只有這樣,才能讓任何普通的XML編輯器不需要任何特殊的設置,
    就可以讀到正確的schema。
    2.2.5.2. SpringExt需要讀取schemas
    當SpringExt在初始化容器時,需要讀取schema以驗證spring配置文件。
    請記住,SpringExt永遠不需要通過訪問網路來訪問schemas。事實上,即使你把例 2.7
    「在XML中指定Schema Location」中的schema的網址改成指向「外部伺服器」的鏈
    接,SpringExt也不會真的去訪問它們。例如:
    • 將:http://localhost:8080/schema/services.xsd
    改成:http://www.alibaba.com/schema/services.xsd
    • 將:http://localhost:8080/schema/services-resource-loading-loaders.xsd
    改成:http://www.alibaba.com/schema/services-resource-loading-loaders.xsd
    • 將:http://localhost:8080/schema/www.springframework.org/schema/beans/
    spring-beans.xsd
    改成:http://www.springframework.org/schema/beans/spring-beans.xsd(這個就是
    spring原來的schema網址了)
    以上修改在任何時候都不會影響Spring的正常啟動。Spring是通過一種SpringExt定製
    的EntityResolver來訪問schemas的。SpringExt其實只關注例子中加亮部分的schema網
    址,而忽略前面部分。
    然而,如前所述,上面兩種網址對於普通的XML編輯器來說是有差別的。因此,SpringExt推薦
    總是以「http://localhost:8080/schema」作為你的schemaLocation網址的前綴。下面的
    圖總結了SpringExt是如何取得schemas的。
    SpringExt
    22
    圖 2.9. SpringExt如何取得schemas
    2.3. SpringExt其它特性
    SpringExt實際上是一個增強了的Spring的ApplicationContext容器。除了提供前面所說
    的Schema擴展機制以外,SpringExt還提供了一個增強的Resource Loading機制。前文例
    子中所說的Resource Loading服務是Webx中的真實功能,而且它能完全取代Spring原有
    的ResourceLoader功能 —— 也就是說,應用程序並不需要直接調用ResourceLoading服務,
    它們可以直接使用Spring本身的ResourceLoader功能,其背後的ResourceLoading機制就會
    默默地工作。
    如果不加額外的配置,SpringExt context所用的ResourceLoader實現和Spring自帶的完全相
    同。然而,你只要添加類似下面的配置,Spring的ResourceLoader就會被增強:
    例 2.8. 配置Webx resource-loading服務
    <services:resource-loading xmlns="http://www.alibaba.com/schema/services">
    <resource-alias pattern="/" name="/webroot" />
    <resource-alias pattern="/myapp" name="/webroot/WEB-INF" />
    <resource pattern="/webroot" internal="true">
    <res-loaders:webapp-loader />
    </resource>
    <resource pattern="/classpath" internal="true">
    <res-loaders:classpath-loader />
    </resource>
    </services:resource-loading>
    SpringExt
    23
    一種典型的Resource Loading服務的用途是讀取CMS生成的模板。假設模板引擎從裝載模
    板/templates,默認情況下,/templates就在webapp的根目錄下。但是有一部分模板/
    templates/cms是由外部的內容管理系統(CMS)生成的,這些模板文件並不在webapp目錄
    下。對此,我們只需要下面的配置:
    例 2.9. 配置CMS目錄
    <resource pattern="/templates/cms">
    <res-loaders:file-loader basedir="${cms.dir}" />
    </resource>
    這樣,在模板引擎渾然不知的情況下,我們就把/templates/cms目錄指向webapp外部的一個
    文件系統目錄,而保持/templates下其它模板的位置不變。
    2.4. 本章總結
    至此,我們簡單領略了SpringExt所帶來的好處和便利。SpringExt完全兼容Spring原來schema
    的概念和風格,但是卻可以讓schema像程序代碼一樣被擴展。Webx完全建立在SpringExt的
    基礎上。這個基礎決定了Webx是一個高度可擴展的框架,其配置雖然靈活,卻又不失方便和直
    觀。
    24
    第 3 章 Webx Framework
    3.1. Webx的初始化 ........................................................................................................ 24
    3.1.1. 初始化級聯的Spring容器 ............................................................................... 24
    3.1.2. 初始化日誌系統 ............................................................................................ 26
    3.2. Webx響應請求 ........................................................................................................ 27
    3.2.1. 增強request、response、session的功能 ....................................................... 27
    3.2.2. Pipeline流程機制 .......................................................................................... 29
    3.2.3. 異常處理機制 ................................................................................................ 30
    3.2.4. 開發模式工具 ................................................................................................ 30
    3.2.5. 響應和處理請求的更多細節 ............................................................................ 33
    3.3. 定製Webx Framework ............................................................................................ 36
    3.3.1. 定製WebxRootController ............................................................................ 36
    3.3.2. 定製WebxController ................................................................................... 36
    3.4. 本章總結 ................................................................................................................. 36
    Webx是一套基於Java Servlet API的通用Web框架。整個Webx框架分成三個層次,本章將簡
    單介紹其第二個層次:Webx Framework。事實上,這是第一個真正涉足WEB技術的層次。前
    一個層次SpringExt只是提供了一個通用的擴展機制。
    Webx Framework負責完成一系列基礎性的任務,如下表所示:
    表 3.1. Webx Framework的任務
    系統初始化響應請求
    初始化Spring容器增強request/response/session的功能
    初始化日誌系統提供pipeline流程處理機制
    異常處理
    開發模式
    本章不會涉及太深的細節,如果你想了解更多,請參考其它文檔。
    3.1. Webx的初始化
    3.1.1. 初始化級聯的Spring容器
    Webx Framework將負責創建一組級聯的Spring容器結構。Webx所創建的Spring容器完全兼
    容於Spring MVC所創建的容器,可被所有使用Spring框架作為基礎的WEB框架所使用。
    Webx Framework
    25
    例 3.1. 初始化Spring容器 - /WEB-INF/web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    ...
    <listener>
    <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class>
    </listener>
    ...
    </web-app>
    Webx利用WebxContextLoaderListener來初始化Spring,用來取代Spring
    的ContextLoaderListener。事實上,前者是從後者派生的。
    Webx Framework將會自動搜索/WEB-INF目錄下的XML配置文件,並創建下面這種級聯的
    spring容器。
    圖 3.1. 級聯的Spring容器
    如圖所示。Webx Framework將一個WEB應用分解成多個小應用模塊:app1、app2,當然名字
    可以任意取。
    • 每個小應用模塊獨享一個Spring Sub Context子容器。兩個子容器之間的beans無法互相注
    入。
    • 所有小應用模塊共享一個Spring Root Context根容器。根容器中的bean可被注入到子容器
    的bean中;反之不可以。
    將一個大的應用分解成若干個小應用模塊,並使它們的配置文件相對獨立,這是一種很不錯的開
    發實踐。然而,如果你的應用確實很簡單,你不希望把你的應用分成多個小應用模塊,那麼,你
    還是需要配置至少一個小應用模塊(子容器)。
    Webx Framework
    26
    3.1.2. 初始化日誌系統
    每個現代的WEB應用,都需要日誌系統。流行的日誌系統包括Log4j、Logback。
    Webx Framework使用SLF4J作為它的日誌框架。因此Webx Framework理論上支持所有日誌
    系統。然而目前為止,它只包含了log4j和logback這兩種日誌系統的初始化模塊(如有需要,
    可以擴充)。初始化日誌系統很簡單。
    例 3.2. 初始化日誌系統 - /WEB-INF/web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    ...
    <listener>
    <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class>
    </listener>
    ...
    </web-app>
    Webx利用LogConfiguratorListener來初始化日誌系統。
    LogConfiguratorListener會根據你當前應用所依賴的日誌系統(通常配置在maven project
    中),來自動選擇合適的日誌配置文件。
    • 假設你的應用依賴了logback的jar包,那麼listener就會查找/WEB-INF/logback.xml,並用
    它來初始化logback;
    • 如果你的應用依賴了log4j的jar包,那麼listener也會很聰明地查找/WEB-INF/log4j.xml配置
    文件。
    • 假如以上配置文件不存在,listener會使用默認的配置 —— 把日誌列印在控制台上。
    • Listener支持對配置文件中的placeholders進行替換。
    • Listener支持同時初始化多種日誌系統。
    注意
    有關日誌系統的使用方法,另有文檔詳細講述。
    Webx Framework
    27
    3.2. Webx響應請求
    圖 3.2. Webx Framework如何響應請求
    當Webx Framework接收到一個來自WEB的請求以後,實際上它主要做了兩件事:
    1. 首先,它會增強request、response、session的功能,並把它們打包成更易使用
    的RequestContext對象。
    2. 其次,它會調用相應子應用的pipeline,用它來做進一步的處理。
    3. 假如在上面的過程中出現異常,則會觸發Webx Framework處理異常的過程。
    此外,Webx Framework還提供了一組輔助開發的功能,例如查看環境變數,查看schema
    等。這些功能只在開發模式生效,生產模式下自動關閉。
    3.2.1. 增強request、response、session的功能
    Webx Framework提供了一個request contexts服務。Request contexts服務利
    用HttpServletRequestWrapper和HttpServletResponseWrapper對request和response對象
    進行包裝,以實現新的功能。
    一個基本的request contexts的配置看起來是下面的樣子:
    Webx Framework
    28
    例 3.3. 配置request contexts服務
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <basic />
    <buffered />
    <lazy-commit />
    <parser />
    <set-locale defaultLocale="zh_CN" defaultCharset="UTF-8" />
    ...
    </services:request-contexts>
    <services:upload sizeMax="5M" />
    Request contexts所有的功能都是可配置、可擴展的 —— 它是基於SpringExt的擴展機制。
    Request contexts所增加的功能對於所有的基於標準Servlet API的應用都是透明的 —— 這些
    應用根本不需要知道這些擴展的存在。例如,假如你在request contexts服務中配置了增強的
    session框架,那麼所有通過標準的Servlet API取得session的應用,都將獲得新功能:
    例 3.4. 取得增強的session對象
    HttpSession session = request.getSession();
    再比如,只要你配置了upload服務,那麼下面的調用將同樣適用於multipart/form-data類型
    的請求(Servlet API本身是不支持upload表單的):
    例 3.5. 取得upload表單的參數
    String value = request.getParameter("myparam");
    注意
    有關Request Contexts的原理和使用方法的詳情,請參閱第 6 章 Filter、Request
    Contexts和Pipeline。
    3.2.1.1. Request contexts中可用的功能
    表 3.2. 可用的RequestContext擴展
    名稱說明
    <basic> 對輸入、輸出的數據進行安全檢查,排除可能的攻擊。例如:XSS過濾、CRLF換行回車過濾
    等。
    <buffered> 對寫入response中的數據進行緩存,以便於實現嵌套的頁面。
    <lazy-commit> 延遲提交response,用來支持基於cookie的session。
    <parser> 解析用戶提交的參數,無論是普通的請求,還是multipart/form-data這樣的用於上傳文件的
    請求。
    <set-locale> 設置當前請求的區域(locale)、編碼字符集(charset)。
    <rewrite> 改寫URL及參數,類似於Apache HTTPD Server中的rewrite模塊。
    <session> 增強的Session框架,可將session中的對象保存到cookie、資料庫或其它存儲中。
    注意
    有關以上所有Request Contexts的詳情,請參閱第 7 章 Request Contexts功能指
    南和第 8 章 Request Context之Session指南。
    Webx Framework
    29
    3.2.1.2. 注入特殊對象
    在Webx中,你可以這樣做,例如:
    例 3.6. 注入request、response、session
    public class LoginAction {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;
    @Autowired
    private HttpSession session;
    ...
    }
    在這個例子中,LoginAction類可以是一個singleton。一般來說,你不能
    把request scope的對象,注入到singleton scope的對象中。但你可以
    把HttpServletRequest、HttpServletResponse和HttpSession對象注入到singleton對象
    中。為什麼呢?原來,Request contexts服務對這幾個常用對象進行了特殊處理,將它們轉化
    成了singleton對象。
    如果沒有這個功能,那麼我們就不得不將上例中的LoginAction配置成request scope。這增
    加了系統的複雜性,也成倍地降低了性能。而將LoginAction設置成singleton,只需要在系統
    啟動時初始化一次,以後就可以快速引用它。
    3.2.2. Pipeline流程機制
    Webx Framework賦予開發者極大的自由,來定製處理請求的流程。這種機制就是pipeline。
    Pipeline的意思是管道,管道中有許多閥門(Valve),閥門可以控制水流的走向。Webx
    Framework中的pipeline可以控制處理請求的流程的走向。如圖所示。
    圖 3.3. Pipeline工作原理示意
    Webx Framework
    30
    Webx Framework並沒有規定管道的內容 —— 定製管道是應用開發者的自由。然而Webx
    Framework提供了一系列通用valves,你可以使用它們:
    表 3.3. 通用valves
    分類Valves 說明
    <while> 有條件循環
    循環
    <loop> 無條件循環
    <if> 單分支
    選擇分支
    <choose><when><otherwise> 多分支
    <break> 無條件中斷
    <break-if>
    <break-unless>
    中斷有條件中斷
    <exit> 無條件退出整個pipeline(結束所有的嵌套層次)
    異常捕獲<try-catch-finally> 類似Java中的try-catch-finally結構
    嵌套<sub-pipeline> 創建嵌套的子pipeline。
    注意
    有關Pipeline的原理和使用方法的詳情,請參閱第 6 章 Filter、Request Contexts
    和Pipeline。
    3.2.3. 異常處理機制
    當應用發生異常時,Webx Framework可以處理這些異常。
    表 3.4. Webx如何處理異常
    條件處理
    開發模式展示詳細出錯信息。
    假如存在exception pipeline 用exception pipeline來處理異常;
    生產模式
    不存在exception pipeline 顯示web.xml中定義的默認錯誤頁面。
    3.2.4. 開發模式工具
    Webx Framework提供了一個開關,可以讓應用運行於「生產模式(Production Mode)」或
    是「開發模式(Development Mode)」 。
    例 3.7. 配置運行模式
    <services:webx-configuration>
    <services:productionMode>${productionMode:true}</services:productionMode>
    </services:webx-configuration>
    使用這行配置,並且在啟動應用伺服器時指定參數「-DproductionMode=false」,就會
    讓Webx以開發模式啟動。
    在開發模式下,會有一系列不同於生產模式的行為。
    • 不同的主頁 —— 在開發模式的主頁中,可以查看和查詢系統內部的信息。
    Webx Framework
    31
    圖 3.4. 開發模式的主頁
    • 不同的詳細出錯頁面。
    圖 3.5. 開發模式的詳細出錯頁面
    • 開發模式下,可展示所有可用的schemas。
    Webx Framework
    32
    圖 3.6. 開發模式下展示所有可用的schemas
    • 開發模式下,可以查閱容器內部的信息。
    圖 3.7. 開發模式下查閱容器內部的信息
    可供查閱的信息包括:
    Webx Framework
    33
    表 3.5. 開發模式中可供查閱的容器信息
    名稱說明
    Beans 查看各Spring容器中的全部bean的定義。
    這個工具有助於開發者理解用schema所定義的services和spring beans之間的聯繫。
    Configurations 查看用來創建各Spring容器的配置文件。
    這個工具會以樹狀和語法高亮顯示配置文件以及所有被import的配置文件的內容。
    不同於Beans工具,Configurations工具只忠實地展現配置文件的內容。而Beans工具
    展現的是真實的Beans結構。
    Resolvable
    Dependencies
    查看所有由框架置入到容器中的對象,例如:HttpServletRequest對象。這些對象不
    需要在配置文件中定義,就可被注入到應用中。
    Resources 跟蹤Resources的裝載過程,顯示Resources的樹狀結構。
    這個工具有助於開發者理解ResourceLoadingService的工作原理。
    URIs 查看所有的URI brokers。
    Pull Tools 查看所有模板中可用的pull tools。
    事實上,Webx Framework提供了一套專用的內部框架,使你可以往開發模式中添加更多的開
    發工具。例如,創建下面的功能並非難事:
    • 查看session對象。
    • 提供各種編碼、解碼的工具,以方便開發、調試應用。例如:將UTF-8編碼的字元串轉換
    成GBK編碼;或者將字元串進行URL escape編碼、解碼等。
    Webx Framework提供了一個介面:ProductionModeAware。Spring context中的beans,
    如果實現了這個介面,就可以感知當前系統的運行模式,從而根據不同的模式選擇不同的行為
    —— 例如:在生產模式中打開cache,在開發模式中關閉cache。
    例 3.8. 利用ProductionModeAware介面感知運行模式,並自動開關cache
    public class ModuleLoaderServiceImpl implements ProductionModeAware {
    public void setProductionMode(boolean productionMode) {
    this.productionMode = productionMode;
    }
    @Override
    protected void init() {
    ……
    if (cacheEnabled == null) {
    cacheEnabled = productionMode;
    }
    ……
    }
    }
    實現ProductionModeAware介面。
    根據當前運行模式自動開關cache。
    3.2.5. 響應和處理請求的更多細節
    當一個HTTP請求到達時,首先由WebxFrameworkFilter接手這個請求:
    Webx Framework
    34
    例 3.9. 配置WebxFrameworkFilter - /WEB-INF/web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    ...
    <filter>
    <filter-name>webx</filter-name>
    <filter-class>com.alibaba.citrus.webx.servlet.WebxFrameworkFilter</filter-class>
    <init-param>
    <param-name>excludes</param-name>
    <param-value><!-- 需要被「排除」的URL路徑,以逗號分隔,如/static, *.jpg --></param-value>
    </init-param>
    <init-param>
    <param-name>passthru</param-name>
    <param-value><!-- 需要被「略過」的URL路徑,以逗號分隔,如/myservlet, *.jsp --></param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>webx</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
    </web-app>
    定義WebxFrameworkFilter。
    可選的參數:「排除」指定名稱的path,以逗號分隔,例如:/static, *.jpg。
    可選的參數:「略過」指定名稱的path,以逗號分隔,例如:/myservlet, *.jsp。
    匹配所有的path。
    為什麼使用filter而不是servlet呢?傳統的WEB框架的控制器一般都是用servlet實現的。原因
    是:
    • Filter可以「返還控制」 —— 上面的配置文件直接把「/*」映射到webx filter中,這意味
    著webx接管了這個應用的所有請求。靜態頁面和資源怎麼辦?沒關係,如果webx發現
    這個請求不應該由webx來處理,就會把控制「返還」給原來的控制器 —— 可能是另一個
    filter、servlet或者返回給servlet引擎,以默認的方式來處理。而Servlet是不具備「返還控
    制」的機制的。
    • Servlet/Filter mapping的局限性 —— 標準的servlet引擎將URL映射到filter或servlet時,只支
    持前綴映射和後綴映射兩種方式,非常局限。而實際情況往往複雜得多。Webx建議將所有請
    求都映射給webx來處理,讓webx對請求做更靈活的映射。
    如果你的web.xml中還有一些其它的servlet mappings,為了避免和Webx的URL起衝突,你可
    以把這些mapping加在excludes或passthru參數里。這樣,WebxFrameworkFilter就會排除
    或略過指定的URL。例如:
    <init-param>
    <param-name>excludes</param-name>
    <param-value>/static, *.jpg</param-value>
    </init-param>
    <init-param>
    <param-name>passthru</param-name>
    <param-value>/myservlet, *.jsp</param-value>
    </init-param>
    Webx Framework
    35
    「passthru略過」和「excludes排除」的區別在於,如果一個servlet或filter接手被webx
    passthru的請求時,它們還是可以訪問到webx的部分服務,包括:
    • RequestContext服務,例如:解析參數、解析upload請求、重寫請求、設置字符集編碼和
    區域、基於cookie的session等。
    • 開發模式及工具。
    • 異常處理。
    • 共享webx的spring容器。
    也就是說,對於一個被passthru的請求,webx的行為更像是一個普通的filter。而「排除」則
    不同,如果一個請求被「排除」,webx將會立即放棄控制,將請求交還給伺服器。接手控制的
    servlet或filter將無法訪問webx一切的服務。
    下圖是WebxFrameworkFilter處理一個WEB請求的過程。
    圖 3.8. WebxFrameworkFilter處理請求的詳細過程
    如圖所示,WebxFrameworkFilter接到請求以後,就會調
    用WebxRootController。從這裡開始,進入Spring的世界 —— 此後所有的對
    象:WebxRootController、WebxController、RequestContext、Pipeline等,全部是通過
    SpringExt配置在Spring Context中的。
    WebxRootController對象存在於root context中,它被所有子應用所共享。它會創
    建RequestContext實例 —— 從而增強request、response、session的功能。接下
    來,WebxController對象會被調用。
    Webx Framework
    36
    WebxController對象是由每個子應用獨享的,子應用app1和app2可以有不同
    的WebxController實現。默認的實現,會調用pipeline。
    Pipeline也是由各子應用自己來配置的。假如pipeline碰到無法處理的請求,如靜態頁面、圖片
    等,pipeline應當執行<exit/> valve強制退出。然後WebxRootController就會「放棄控制」
    —— 這意味著request將被返還給/WEB-INF/web.xml中定義的servlet、filter或者返還給servlet
    engine本身來處理。
    3.3. 定製Webx Framework
    3.3.1. 定製WebxRootController
    WebxRootController是被所有子應用所共享的邏輯。 假如你想創建一種新的WEB框架,可以
    自己定義一個新的WebxRootController的實現。這個方案非常適合作為一個新Web框架的起
    點。
    例 3.10. 自定義WebxRootController
    <webx-configuration xmlns="http://www.alibaba.com/schema/services">
    <components>
    <rootController class="com.myframework.MyRootController" />
    </components>
    </webx-configuration>
    創建自己的WebxRootController。最簡便的方法是:擴
    展AbstractWebxRootController,免去了創建Servlet/Filter、初始化Spring容器、處理
    request、response等繁雜事務,並且完全支持SpringExt的所有功能,此外還包含了錯誤
    處理、開發模式等Webx Framework中的一切便利。。
    3.3.2. 定製WebxController
    WebxController是用來控制子應用的。每個子應用可以擁有不同的WebxController實現。
    Webx Framework默認的WebxController是調用pipeline。假如你不想用pipeline,而希
    望實現自己的針對子應用的邏輯,那麼最簡單的方法就是實現自己的WebxController或者擴
    展AbstractWebxController。
    例 3.11. 自定義WebxController
    <webx-configuration xmlns="http://www.alibaba.com/schema/services">
    <components defaultControllerClass="com.myframework.MyController">
    <component name="app1">
    <controller class="com.myframework.MyController" />
    </component>
    </components>
    </webx-configuration>
    指定默認的WebxController實現類。
    對特定子應用明確指定WebxController實現類。
    3.4. 本章總結
    Webx Framework提供了一個可剪裁、可擴展的處理WEB請求基本框架。它所提供的基本功能
    事實上是每個WEB框架都需要用到的。Webx Framework為進一步實現WEB框架提供了堅實的
    基礎。
    37
    第 4 章 Webx Turbine
    4.1. 設計理念 ................................................................................................................. 37
    4.1.1. 頁面驅動 ...................................................................................................... 37
    4.1.2. 約定勝於配置 ................................................................................................ 38
    4.2. 頁面布局 ................................................................................................................. 39
    4.3. 處理頁面的基本流程 ................................................................................................ 40
    4.4. 依賴注入 ................................................................................................................. 42
    4.4.1. Spring原生注入手段 ...................................................................................... 42
    4.4.2. 注入request、response和session對象 ........................................................... 42
    4.4.3. 參數注入 ...................................................................................................... 43
    4.5. 定製Webx Turbine .................................................................................................. 43
    4.6. 本章總結 ................................................................................................................. 44
    Webx是一套基於Java Servlet API的通用Web框架。整個Webx框架分成三個層次,本章將簡
    單介紹其第三個層次:Webx Turbine。Webx Turbine建立在Webx Framework的基礎上,實
    現了頁面渲染、布局、數據驗證、數據提交等一系列工作。
    Webx Turbine之所以叫這個名字,是因為Webx最早的版本,是從Apache Turbine項目上發
    展而來的。到現在,Turbine的代碼已經蕩然無存,然而Turbine中的一些風格和想法依賴保存在
    Webx框架中。
    4.1. 設計理念
    Webx Turbine所遵循下面的設計理念包括:
    • 頁面驅動
    • 約定勝於配置
    4.1.1. 頁面驅動
    創建一個WEB應用,一般會經歷三個階段:產品設計、用戶界面設計、功能實現。分別由產品
    設計師、用戶界面設計師和程序員協作完成。如下圖所示。
    Webx Turbine
    38
    圖 4.1. 協作圖:創建一個WEB應用
    通常,界面設計師只完成純靜態頁面的設計,需要由程序員來把靜態頁面轉換、分解成模板,才
    能在最終的WEB應用中被使用。為什麼不讓界面設計師直接創建模板呢?這樣一定可以提高很
    多效率。然而在一般的WEB框架中,由於模板不能獨立於程序元素(如action)而存在,因此
    在程序員介入以前,界面設計師是沒有辦法展示模板的效果的。
    Webx Turbine推崇頁面驅動的理念。它的意思是,在程序員介入以前,讓界面設計師可以直接
    創建模板,並展示模板的效果。頁面驅動的反面,是程序驅動,或者是Action驅動 —— 這是多
    數WEB框架的模式。
    頁面驅動不止提高了開發的效率,也使界面設計師在早期階段,就可以利用框架所提供的工具,
    做一些以前做不到的事,例如:頁面跳轉、簡單的表單驗證、字元串操作等。這些工具是通過
    Webx Turbine中的一個服務來完成的:pull tools。Pull tools服務預先準備了很多模板中可用的
    工具,讓模板可以「按需」取得這些對象 —— 這就是pull這個單詞的意思。
    4.1.2. 約定勝於配置
    Webx Turbine的另一個理念,是約定勝於配置。「約定」即規則。規則是預先定義的,工程師
    只需要按著規則來做事,就不需要額外的「配置」。對比其它一些框架 —— 往往每增加一個頁
    面,都需要在配置文件中增加若干行內容。
    Webx Turbine的規則主要是指一系列映射規則。
    Webx Turbine
    39
    表 4.1. Webx Turbine映射規則
    映射規則說明
    將URL映射成target target是一個抽象的概念,指明當前請求要完成的任務。Target由pipeline來解釋,
    它可能被解釋成模板名,也可能被解釋成別的東西。
    將target轉換成模板名模板用來展現頁面的內容。Velocity、Freemarker、JSP都可以作為模板的格式,但
    在Webx建議使用velocity模板。
    將target轉換成layout布局你可以為一組頁面選擇相同的布局(菜單、導航欄、版權信息等),為另一組頁面選
    擇另一種布局。
    將target轉換成module 在Webx Turbine中,module是指screen、action、control等,大致相當於其它框
    架中的action或者controller。
    工程師只需要根據上述規則,將模板放在指定的目錄、按照預定的方式命名module(也就是
    screen、action、control等),就不再需要額外的配置。
    4.2. 頁面布局
    Webx Turbine的頁面,由以下幾個部分組成:
    圖 4.2. Webx Turbine頁面的構成
    Webx Turbine
    40
    其中:
    • Screen,代表頁面的主體。
    • Layout,代表頁面的布局。
    • Control,代表嵌在screen和layout中的頁面片段。
    4.3. 處理頁面的基本流程
    Webx Turbine的處理流程被定義在pipeline中。Webx Framework沒有規定Pipeline的內容,
    但Webx Turbine卻定義了一系列valves。下面是一個Webx Turbine推薦的pipeline配置:
    例 4.1. Webx Turbine推薦的pipeline配置 - pipeline.xml
    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    <!-- 初始化turbine rundata,並在pipelineContext中設置可能會用到的對象(如rundata、utils),以便valve取得。 -->
    <prepareForTurbine />
    <!-- 設置日誌系統的上下文,支持把當前請求的詳情列印在日誌中。 -->
    <setLoggingContext />
    <!-- 分析URL,取得target。 -->
    <analyzeURL homepage="homepage" />
    <!-- 檢查csrf token,防止csrf攻擊和重複提交。假如request和session中的token不匹配,則出錯,或顯示expired頁
    面。 -->
    <checkCsrfToken />
    <loop>
    <choose>
    <when>
    <!-- 執行帶模板的screen,默認有layout。 -->
    <pl-conditions:target-extension-condition extension="null, vm, jsp" />
    <performAction />
    <performTemplateScreen />
    <renderTemplate />
    </when>
    <when>
    <!-- 執行不帶模板的screen,默認無layout。 -->
    <pl-conditions:target-extension-condition extension="do" />
    <performAction />
    <performScreen />
    </when>
    <otherwise>
    <!-- 將控制交還給servlet engine。 -->
    <exit />
    </otherwise>
    </choose>
    <!-- 假如rundata.setRedirectTarget()被設置,則循環,否則退出循環。 -->
    <breakUnlessTargetRedirected />
    </loop>
    </services:pipeline>
    假設用戶以URL:http://localhost:8081/來訪問Webx應用。域名和埠不重要,取
    決於應用伺服器的配置,這裡假設為localhost:8081。Webx Framework的處理流程,
    從WebxFrameworkFilter接收請求,並且一路順利到達pipeline。然後Pipeline開始依次執行
    它的valves。(下面的描述略過一些相對次要的步驟。)
    Webx Turbine
    41
    1. <analyzeURL> - 分析URL
    分析URL的目的是取得target。由於用戶訪問的URL中並沒有提供path信息,通常被理解
    為:用戶想要訪問「主頁」。AnalyzeURL valve提供了一個可選的參數「homepage」,即
    是在這種情況下起作用 —— http://localhost:8081/對應的target為「homepage」。
    需要注意的是,target不代表模板名,也不代表類名。Target只是一個抽象的概念 —— 當
    前頁面需要達成的目標。Target可能被後續的valves解釋成模板名、類名或者其它東西。
    2. 進入<choose> - 多重分支
    很明顯,「homepage」滿足了第一個<when>所附帶的條件:<target-extensioncondition
    extension="null, vm, jsp">,意思是target的後綴不存在(null)或
    為「jsp」或為「vm」。
    3. <performAction> - 執行action
    和其它框架中的action概念不同,在Webx Turbine中,action是用來處理用戶提交的表單
    的。
    因為本次請求未提供action參數,所以跳過該步驟。
    4. <performTemplateScreen> - 查找並執行screen。
    這裡要用到一個規則:target映射成screen module類名的規則。
    假設target為xxx/yyy/zzz,那麼Webx Turbine會依次查找下面的screen模塊:
    • screen.xxx.yyy.Zzz,
    • screen.xxx.yyy.Default,
    • screen.xxx.Default,
    • screen.Default。
    本次請求的target為homepage,因此它會嘗試查
    找screen.Homepage和screen.Default這兩個類。
    如果找到screen類,Webx Turbine就會執行它。Screen類的功能,通常是讀取資料庫,
    然後把模板所需要的對象放到context中。
    如果找不到,也沒關係 —— 這就是「頁面優先」:像homepage這樣的主頁,通常沒有業務
    邏輯,因此不需要screen類,只需要有模板就可以了。
    5. <renderTemplate> - 渲染模板
    這裡用到兩個規則:target映射成screen template,以及target映射成layout
    template。
    假設target為xxx/yyy/zzz,那麼Webx Turbine會查找下面的screen模板:/templates/
    screen/xxx/yyy/zzz。Screen模板如果未找到,就會報404 Not Found錯誤。 找到
    screen模板以後,Webx Turbine還會試著查找下面的layout模板:
    • /templates/layout/xxx/yyy/zzz
    Webx Turbine
    42
    • /templates/layout/xxx/yyy/default
    • /templates/layout/xxx/default
    • /templates/layout/default
    Layout模板如果找不到,就直接渲染screen模板;如果存在,則把渲染screen模板后的結
    果,嵌入到layout模板中。
    Layout模板和screen模板中,都可以調用control。每個頁面只有一個screen,卻可以有
    任意多個controls。
    6. <breakUnlessTargetRedirected> - 內部重定向
    在screen和action中,可以進行「內部重定向」。內部重定向實質上就是
    由<breakUnlessTargetRedirected>實施的 —— 如果沒有重定向標記,就退出;否則循
    環到<loop>標籤。
    和外部重定向不同,外部重定向是向瀏覽器返回一個302或303 response,其中包
    含Location header,瀏覽器看到這樣的response以後,就會發出第二個請求。而內部重
    定向發生在pipeline內部,瀏覽器並不了解內部重定向。
    4.4. 依賴注入
    4.4.1. Spring原生注入手段
    依賴注入是Spring的重要特性,Webx既然建立在Spring基礎上,當然支持Spring原有的依賴注
    入手段,例如,你可以在Screen/control/action module類中這樣寫:
    例 4.2. 通過@Autowired annotation注入
    public class LoginAction {
    @Autowired
    private UserManager userManager;
    ...
    }
    UserManager是在spring context中配置的bean。
    在使用Spring原生注入手段時,需要注意beans的scope。你只能注入相同scope或
    較大的scope中的bean。例如,screen/action/control的scope為singleton,因此
    用@Autowired注入時,只能注入singleton的對象,不能注入諸如request、session等較小的
    scope對象。
    4.4.2. 注入request、response和session對象
    在Webx Framework中,你可以這樣做:
    Webx Turbine
    43
    例 4.3. 注入request、response和session對象
    public class LoginAction {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;
    @Autowired
    private HttpSession session;
    ...
    }
    前面我們剛講過,你不能把request scope的對象,注入到singleton scope的對象中。但在
    Webx中,你可以將HttpServletRequest、HttpServletResponse和HttpSession對象注入
    到singleton對象中。為什麼呢?原來,<request-contexts>對這幾個常用對象進行了特殊處
    理,將它們轉化成了singleton對象。
    4.4.3. 參數注入
    有一些對象,是無法通過Spring的bean來注入的,例如:用戶提交的參數、表單等。好在
    Webx Turbine提供了一種可擴展的機制(DataResolver service),通過它,我們可以在
    screen/control/action的方法中注入任意對象。
    表 4.2. 參數注入
    功能代碼示例
    適用於module類

    注入一個query參數void doGetInt(@Param("aaa") int i) screen、
    action、 control
    將query參數注入
    bean properties void doSetData(@Params MyData data) screen、
    action、 control
    void doGetNavigator(Navigator nav) screen、 action
    void doGetContext(Context context) screen、
    注入框架對象action、 control
    void execute(ControlParameters params) control
    注入context和control
    參數void execute(@ContextValue("myvalue") int value) screen、
    action、 control
    void doGetGroup(@FormGroup("myGroup1") Group group) action
    注入表單對象void doGetGroups(@FormGroups("myGroup1") Group[]
    groups) action
    將表單值注入bean
    properties
    void doGetGroupsBeans(@FormGroups("myGroup1") MyData[]
    data) action
    4.5. 定製Webx Turbine
    通過改進pipeline中的valves,我們很容易改變webx turbine的行為。
    最常見的一種需求,是要對頁面進行授權 —— 只有符合條件的用戶才能訪問相應的頁面。在
    pipeline中,很容易添加這樣的邏輯:
    Webx Turbine
    44
    例 4.4. 改進pipeline,增加頁面授權功能 - pipeline.xml
    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    <prepareForTurbine />
    <setLoggingContext />
    <analyzeURL homepage="homepage" />
    <checkCsrfToken />
    <valve class="com.mycompany.auth.PageAuthorizationValve" />
    ...
    </services:pipeline>
    插入用於驗證許可權的valve。
    事實上,你甚至可以重寫整個pipeline,以實現另一種風格的WEB框架。
    4.6. 本章總結
    Webx Turbine建立在pipeline的基礎上,基於頁面驅動和約定勝於配置的理念,定義了一組處
    理頁面的流程。Webx Turbine的靈活性在於,你可以輕易定製pipeline,以改變它的任何一個
    方面。
    部分 II. Webx基礎設施服務
    46
    第 5 章 Resource Loading服務指南 ............................................................................... 49
    5.1. 資源概述 ......................................................................................................... 49
    5.1.1. 什麼是資源? ........................................................................................ 49
    5.1.2. 如何表示資源? .................................................................................... 50
    5.1.3. 如何訪問資源? .................................................................................... 50
    5.1.4. 如何遍歷資源? .................................................................................... 51
    5.1.5. 有什麼問題? ........................................................................................ 52
    5.2. Spring的ResourceLoader機制 ......................................................................... 53
    5.2.1. Resource介面 ...................................................................................... 53
    5.2.2. ResourceLoader和ResourcePatternResolver介面 .............................. 53
    5.2.3. 在代碼中取得資源 ................................................................................. 54
    5.2.4. Spring如何裝載資源? ........................................................................... 55
    5.2.5. Spring ResourceLoader的缺點 ............................................................. 57
    5.3. Resource Loading服務 ................................................................................... 58
    5.3.1. 替換Spring ResourceLoader ................................................................. 58
    5.3.2. 定義新資源 ........................................................................................... 59
    5.3.3. 重命名資源 ........................................................................................... 60
    5.3.4. 重定向資源 ........................................................................................... 61
    5.3.5. 匹配資源 .............................................................................................. 62
    5.3.6. 在多個ResourceLoader中查找 .............................................................. 63
    5.3.7. 裝載parent容器中的資源 ...................................................................... 64
    5.3.8. 修改資源文件的內容 .............................................................................. 64
    5.3.9. 直接使用ResourceLoadingService ...................................................... 65
    5.3.10. 在非Web環境中使用Resource Loading服務 ....................................... 67
    5.4. ResourceLoader參考 ..................................................................................... 68
    5.4.1. FileResourceLoader ........................................................................... 68
    5.4.2. WebappResourceLoader ....................................................................... 69
    5.4.3. ClasspathResourceLoader .................................................................. 69
    5.4.4. SuperResourceLoader ......................................................................... 69
    5.4.5. 關於ResourceLoader的其它考慮 ........................................................... 70
    5.5. 本章總結 ......................................................................................................... 70
    第 6 章 Filter、Request Contexts和Pipeline ................................................................... 71
    6.1. Filter ............................................................................................................... 71
    6.1.1. Filter的用途 .......................................................................................... 71
    6.1.2. Filter工作原理 ....................................................................................... 72
    6.1.3. Filter的限制 .......................................................................................... 73
    6.1.4. Webx對filter功能的補充 ........................................................................ 73
    6.2. Request Contexts服務 ................................................................................... 74
    6.2.1. Request Contexts工作原理 .................................................................. 74
    6.2.2. Request Contexts的用途 ..................................................................... 75
    6.2.3. Request Contexts的使用 ..................................................................... 76
    6.3. Pipeline服務 ................................................................................................... 79
    6.3.1. Pipeline工作原理 .................................................................................. 79
    6.3.2. Pipeline的用途 ..................................................................................... 80
    6.3.3. Pipeline的使用 ..................................................................................... 82
    Webx基礎設施服務
    47
    6.4. 本章總結 ......................................................................................................... 91
    第 7 章 Request Contexts功能指南 ................................................................................ 93
    7.1. <basic> - 提供基礎特性 .................................................................................. 94
    7.1.1. 攔截器介面 ........................................................................................... 94
    7.1.2. 默認攔截器 ........................................................................................... 95
    7.2. <set-locale> -設置locale區域和charset字符集編碼 ....................................... 95
    7.2.1. Locale基礎 .......................................................................................... 95
    7.2.2. Charset編碼基礎 .................................................................................. 96
    7.2.3. Locale和charset的關係 ....................................................................... 97
    7.2.4. 設置locale和charset ............................................................................ 97
    7.2.5. 使用方法 .............................................................................................. 98
    7.3. <parser> - 解析參數 ..................................................................................... 101
    7.3.1. 基本使用方法 ...................................................................................... 101
    7.3.2. 上傳文件 ............................................................................................ 102
    7.3.3. 高級選項 ............................................................................................ 104
    7.4. <buffered> - 緩存response中的內容 ............................................................. 107
    7.4.1. 實現原理 ............................................................................................ 107
    7.4.2. 使用方法 ............................................................................................ 109
    7.5. <lazy-commit> - 延遲提交response .............................................................. 111
    7.5.1. 什麼是提交 ......................................................................................... 111
    7.5.2. 實現原理 ............................................................................................ 111
    7.5.3. 使用方法 ............................................................................................ 112
    7.6. <rewrite> -重寫請求的URL和參數 .................................................................. 112
    7.6.1. 概述 ................................................................................................... 112
    7.6.2. 取得路徑 ............................................................................................ 114
    7.6.3. 匹配rules ............................................................................................ 114
    7.6.4. 匹配conditions ................................................................................... 115
    7.6.5. 替換路徑 ............................................................................................ 117
    7.6.6. 替換參數 ............................................................................................ 117
    7.6.7. 後續操作 ............................................................................................ 118
    7.6.8. 重定向 ................................................................................................ 119
    7.6.9. 自定義處理器 ...................................................................................... 120
    7.7. 本章總結 ....................................................................................................... 120
    第 8 章 Request Context之Session指南 ........................................................................ 121
    8.1. Session概述 ................................................................................................... 121
    8.1.1. 什麼是Session ..................................................................................... 121
    8.1.2. Session數據存在哪? ........................................................................... 121
    8.1.3. 創建通用的session框架 ........................................................................ 123
    8.2. Session框架 ................................................................................................... 124
    8.2.1. 最簡配置 ............................................................................................ 124
    8.2.2. Session ID .......................................................................................... 124
    8.2.3. Session的生命期 ................................................................................. 125
    8.2.4. Session Store ..................................................................................... 127
    8.2.5. Session Model ................................................................................... 129
    8.2.6. Session Interceptor ............................................................................ 129
    Webx基礎設施服務
    48
    8.3. Cookie Store ................................................................................................ 130
    8.3.1. 多值Cookie Store .............................................................................. 131
    8.3.2. 單值Cookie Store .............................................................................. 134
    8.4. 其它Session Store ......................................................................................... 137
    8.4.1. Simple Memory Store ........................................................................ 137
    8.5. 本章總結 ....................................................................................................... 138
    49
    第 5 章 Resource Loading服務指南
    5.1. 資源概述 ................................................................................................................. 49
    5.1.1. 什麼是資源? ................................................................................................ 49
    5.1.2. 如何表示資源? ............................................................................................ 50
    5.1.3. 如何訪問資源? ............................................................................................ 50
    5.1.4. 如何遍歷資源? ............................................................................................ 51
    5.1.5. 有什麼問題? ................................................................................................ 52
    5.2. Spring的ResourceLoader機制 ................................................................................. 53
    5.2.1. Resource介面 .............................................................................................. 53
    5.2.2. ResourceLoader和ResourcePatternResolver介面 ...................................... 53
    5.2.3. 在代碼中取得資源 ......................................................................................... 54
    5.2.4. Spring如何裝載資源? ................................................................................... 55
    5.2.5. Spring ResourceLoader的缺點 ..................................................................... 57
    5.3. Resource Loading服務 .......................................................................................... 58
    5.3.1. 替換Spring ResourceLoader ......................................................................... 58
    5.3.2. 定義新資源 ................................................................................................... 59
    5.3.3. 重命名資源 ................................................................................................... 60
    5.3.4. 重定向資源 ................................................................................................... 61
    5.3.5. 匹配資源 ...................................................................................................... 62
    5.3.6. 在多個ResourceLoader中查找 ...................................................................... 63
    5.3.7. 裝載parent容器中的資源 .............................................................................. 64
    5.3.8. 修改資源文件的內容 ...................................................................................... 64
    5.3.9. 直接使用ResourceLoadingService .............................................................. 65
    5.3.10. 在非Web環境中使用Resource Loading服務 ............................................... 67
    5.4. ResourceLoader參考 ............................................................................................. 68
    5.4.1. FileResourceLoader ................................................................................... 68
    5.4.2. WebappResourceLoader ............................................................................... 69
    5.4.3. ClasspathResourceLoader .......................................................................... 69
    5.4.4. SuperResourceLoader ................................................................................. 69
    5.4.5. 關於ResourceLoader的其它考慮 ................................................................... 70
    5.5. 本章總結 ................................................................................................................. 70
    Webx框架中,包含了一套用來查找和裝載資源的服務 —— Resource Loading服務。
    Resource Loading服務從Spring ResourceLoader機制中擴展而來,並且和Spring框架融為一
    體。因此,你不需要寫特別的Java代碼,就可以讓所有利用Spring ResourceLoader機制的代
    碼,直接享用Webx所提供的新的Resource Loading機制。
    5.1. 資源概述
    5.1.1. 什麼是資源?
    在一個稍具規模的應用程序中,經常要做的一件事,就是查找資源、讀取資源的內容。這裡所謂
    的「資源」,是指存放在某一介質中,可以被程序利用的文件、數據。例如,基於Java的WEB
    應用中,常用到下面的資源:
    Resource Loading服務指南
    50
    • 配置文件:*.xml、*.properties等。
    • Java類文件:*.class。
    • JSP頁面、Velocity模板文件:*.jsp、*.vm等。
    • 圖片、CSS、JavaScript文件:*.jpg、*.css、*.js等。
    5.1.2. 如何表示資源?
    在Java中,有多種形式可以表示一個資源:
    表 5.1. 資源的表示
    可表示資源的對象說明
    java.io.File
    可代表文件系統中的文件或目錄。例如:
    • 文件系統中的文件:「c:\config.sys」。
    • 文件系統中的目錄:「c:\windows\」。
    java.net.URL
    統一資源定位符。例如:
    • 文件系統中的文件:c:\config.sys,可以表示成URL:「file:///c:/config.sys」。
    • 文件系統中的目錄:c:\windows\,可以表示成URL:「file:///c:/windows/」。
    • 遠程WEB伺服器上的文件:「http://www.springframework.org/schema/
    beans.xml」。
    • Jar包中的某個文件,可以表示成URL:「jar:file:///c:/my.jar!/my/file.txt」。
    java.io.
    InputStream
    輸入流對象,可用來直接訪問資源的內容。例如:
    • 文件系統中的文件:c:\config.sys,可以用下面的代碼來轉換成輸入流:
    new FileInputStream("c:\\config.sys");
    • 遠程WEB伺服器上的文件,可以用下面的代碼來轉換成輸入流:
    new URL("http://www.springframework.org/schema/beans.xml").openStream();
    • Jar包中的某個文件,可以用下面的代碼來轉換成輸入流:
    new URL("jar:file:///c:/my.jar!/my/file.txt").openStream();
    然而,並不是所有的資源,都可以表現成上述所有的形式。比如,
    • Windows文件系統中的目錄,無法表現為輸入流。
    • 而遠程WEB伺服器上的文件無法轉換成File對象。
    • 多數資源都可以表現成URL形式。但也有例外,例如,如果把資料庫中的數據看作資源,那麼
    一般來說這種資源無法表示成URL。
    5.1.3. 如何訪問資源?
    不同類型的資源,需要用不同的方法來訪問。
    訪問CLASSPATH中的資源
    將資源放在CLASSPATH是最簡單的做法。我們只要把所需的資源文件打包到Jar文件中,或
    是在運行java時,用-classpath參數中指定的路徑中。接下來我們就可以用下面的代碼來
    訪問這些資源:
    Resource Loading服務指南
    51
    例 5.1. 訪問CLASSPATH中的資源
    URL resourceURL = getClassLoader().getResource("java/lang/String.class"); // 取得URL
    InputStream resourceContent = getClassLoader().getResourceAsStream("java/lang/String.class"); // 取得輸入流
    訪問文件系統中的資源
    下面的代碼從文件資源中讀取信息:
    例 5.2. 訪問文件系統中的資源
    File resourceFile = new File("c:\\test.txt"); // 取得File
    InputStream resourceContent = new FileInputStream(resourceFile); // 取得輸入流
    訪問Web應用中的資源
    Web應用既可以打包成war文件,也可以展開到任意目錄中。因此Web應用中的資源
    (JSP、模板、圖片、Java類、配置文件)不總是可以用文件的方式存取。雖然Servlet API
    提供了ServletContext.getRealPath()方法,用來取得某個資源的實際文件路徑,但該
    方法很可能返回null —— 這取決於應用伺服器的實現,以及Web應用的布署方式。更好的
    獲取WEB應用資源的方法如下:
    例 5.3. 訪問Web應用中的資源
    URL resourceURL = servletContext.getResource("/WEB-INF/web.xml"); // 取得URL
    InputStream resourceContent = servletContext.getResourceAsStream("/WEB-INF/web.xml"); // 取得輸入流
    訪問Jar/Zip文件中的資源
    下面的代碼讀取被打包在Jar文件中的資源信息:
    例 5.4. 訪問Jar/Zip文件中的資源
    URL jarURL = new File(System.getProperty("java.home") + "/lib/rt.jar").toURI().toURL();
    URL resourceURL = new URL("jar:" + jarURL + "!/java/lang/String.class"); // 取得URL
    InputStream resourceContent = resourceURL.openStream(); // 取得輸入流
    訪問其它資源
    還可以想到一些訪問資源的方法,例如從資料庫中取得資源數據。
    5.1.4. 如何遍歷資源?
    有時候,我們不知道資源的路徑,但希望能找出所有符合條件的資源,這個操作叫作遍歷。例
    如,找出所有符合pattern 「/WEB-INF/webx-*.xml」的配置文件。
    遍歷文件系統
    例 5.5. 遍歷文件系統
    File parentResource = new File("c:\\windows");
    File[] subResources = parentResource.listFiles();
    Resource Loading服務指南
    52
    遍歷WEB應用中的資源
    例 5.6. 遍歷WEB應用中的資源
    Set<String> subResources = servletContext.getResourcePaths("/WEB-INF/");
    遍歷Jar/zip文件中的資源
    例 5.7. 遍歷Jar/zip文件中的資源
    File jar = new File("myfile.jar");
    ZipInputStream zis = new ZipInputStream(new FileInputStream(jar));
    try {
    for (ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()) {
    // visit entry
    }
    } finally {
    zis.close();
    }
    並非所有類型的資源都支持遍歷操作。通常遍歷操作會涉及比較複雜的遞歸演算法。
    5.1.5. 有什麼問題?
    應用程序訪問資源時,有什麼問題呢?
    首先,資源表現形式的多樣性,給應用程序的介面設計帶來一點麻煩。假如,我寫一
    個ConfigReader類,用來讀各種配置文件。那麼我可能需要在介面中列出所有的資源的形式:
    例 5.8. 用來讀取配置文件的介面
    public interface ConfigReader {
    Object readConfig(File configFile);
    Object readConfig(URL configURL);
    Object readConfig(InputStream configStream);
    }
    特別是當一個通用的框架,如Spring和Webx,需要在對象之間傳遞各種形式的資源的時候,這
    種多樣性將導致很大的編程困難。
    其次,有這麼多種查找資源和遍歷資源的方法,使我們的應用程序和資源所在的環境高度耦合。
    這種耦合會妨礙代碼的可移植性和可測試性。
    比如,我希望在非WEB的環境下測試一個模塊,但這個模塊因為要存取Web應用下的資源,
    而引用了ServletContext對象。在測試環境中並不存在ServletContext而導致該模塊難以
    被測試。再比如,我希望測試的一個模塊,引用了classpath下的某個配置文件(這也是一種
    耦合)。而我希望用另一個專為該測試打造的配置文件來代替這個文件。由於原配置文件是在
    classpath中,因此是難以替換的。
    對於不打算重用的應用程序來說,這個問題還不太嚴重:大不了我預先設定好,就從這個地方,
    以固定的方式存取資源。然而就算這樣,也是挺麻煩的。有的人喜歡把資源放在某個子目錄下,
    有的人喜歡把資源放在CLASSPATH下,又有人總是通過ServletContext來存取Web應用下的
    資源。當你要把這些不同人寫的模塊整合起來時,你會發現很難管理。
    Resource Loading服務指南
    53
    一種可能發生的情形是,因為某些原因,環境發生改變,導致資源的位置、存取方式不得不跟著
    改變。比如將老系統升級為新系統。但一些不得不繼續使用的老代碼,由於引用了舊環境的資源
    而不能工作 —— 除非你去修改這些代碼。有時修改老代碼是很危險的,可能導致不可預知的錯
    誤。又比如,由於存儲硬體的改變或管理的需要,我們需要將部分資源移到另一個地方(我們曾
    經將Web頁面模板中的某個子目錄,移動到一個新的地方,因為這些模板必須由新的CMS系統
    自動生成)。想要不影響現有代碼來完成這些事,是很困難的。
    5.2. Spring的ResourceLoader機制
    Spring內置了一套ResourceLoader機制,很好地解決了訪問資源的大部分問題。
    5.2.1. Resource介面
    Spring將所有形式的資源表現概括成一個Resource介面。如下所示(下面的介面定義是被簡化
    的,有意省略了一些東西,以便突出重點):
    例 5.9. Spring的Resource介面(簡化)
    public interface Resource {
    InputStream getInputStream();
    URL getURL();
    File getFile();
    boolean exists();
    }
    Resource介面嚮應用程序屏蔽了資源表現形式的多樣性。於是,前面例子中的ConfigReader就
    可以被簡化成下面的樣子:
    例 5.10. 用來讀取配置文件的介面(簡化后)
    public interface ConfigReader {
    Object readConfig(Resource configResource);
    }
    事實上,Spring正是利用Resource介面來初始化它的ApplicationContext的:
    例 5.11. Spring用Resource介面來代表用來初始化ApplicationContext的配置文件
    public abstract class AbstractXmlApplicationContext extends ... {
    ...
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) {
    Resource[] configResources = getConfigResources();
    ...
    }
    protected Resource[] getConfigResources();
    }
    5.2.2. ResourceLoader和ResourcePatternResolver介面
    Spring不僅可以通過ResourceLoader介面來取得單一的資源對象,還可以通
    過ResourcePatternResolver遍歷並取得多個符合指定pattern的資源對象。這個設計嚮應用
    程序屏蔽了查找和遍歷資源的複雜性。
    Resource Loading服務指南
    54
    圖 5.1. ResourceLoader和ResourcePatternResolver介面
    5.2.3. 在代碼中取得資源
    5.2.3.1. 通過ResourceLoader取得資源
    例 5.12. 通過ResourceLoader取得資源
    public class MyBean implements ResourceLoaderAware {
    private ResourceLoader loader;
    public void setResourceLoader(ResourceLoader loader) {
    this.loader = loader;
    }
    public void func() {
    Resource resource = loader.getResource("myFile.xml");
    ...
    }
    }
    實現了ResourceLoaderAware介面。要取得資源,必須要拿到ResourceLoader對象。而
    通過ResourceLoaderAware介面拿到ResourceLoader是最簡單的方法。
    調用所取得的ResourceLoader來取得資源。
    5.2.3.2. 直接注入資源
    另一種更簡便的方法是,將資源直接「注入」到bean中 —— 你不需要手工調
    用ResourceLoader來取得資源的方式來設置資源。例如:
    Resource Loading服務指南
    55
    例 5.13. 直接注入資源
    public class MyBean {
    private URL resource;
    public void setLocation(URL resource) {
    this.resource = resource;
    }
    ……
    }
    Spring配置文件可以這樣寫:
    <bean id="myBean" class="MyBean">
    <property name="location" value="myFile.xml" />
    </bean>
    此處注入資源的URL
    這樣,Spring就會把適當的myFile.xml所對應的資源注入到myBean對象中。此外,Spring
    會自動把Resource對象轉換成URL、File等普通對象。在上面的例子中,MyBean並不依賴
    於Resource介面,只依賴於URL類。
    將代碼稍作修改,就可以注入一組資源:
    例 5.14. 注入一組資源
    public void setLocations(URL[] resources) {
    this.resources = resources;
    }
    配置文件:
    <property name="locations" value="WEB-INF/webx-*.xml" />
    此處注入資源的URL的數組。
    上例中,可以直接得到所有符合pattern 「WEB-INF/webx-*.xml」的配置文件。顯然這是通
    過ResourcePatternResolver取得的。
    5.2.4. Spring如何裝載資源?
    Spring是如何裝載資源文件的呢?Spring裝載資源的方案是由ApplicationContext決定的。不
    同的ApplicationContext類,實現了不同的資源裝載方案。
    Resource Loading服務指南
    56
    圖 5.2. Spring ApplicationContext實現了資源裝載的具體方案
    5.2.4.1. ClassPathXmlApplicationContext
    ClassPathXmlApplicationContext支持從classpath中裝載資源。
    例 5.15. ClassPathXmlApplicationContext - 從classpath中裝載資源
    假如我以下面的方式啟動Spring,那麼系統將支持從classpath中裝載資源。
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    ClassPathXmlApplicationContext裝載資源文件myFile.xml的邏輯,相當於如下代碼:
    URL resource = getClassLoader().getResource("myFile.xml");
    5.2.4.2. FileSystemXmlApplicationContext
    FileSystemXmlApplicationContext支持從文件系統中裝載資源。
    例 5.16. FileSystemXmlApplicationContext - 從文件系統中裝載資源
    假如我以下面的方式啟動Spring,那麼系統將支持從文件系統中裝載資源。
    ApplicationContext context = new FileSystemXmlApplicationContext("beans.xml");
    FileSystemXmlApplicationContext裝載資源文件myFile.xml的邏輯,相當於如下代碼:
    File resource = new File("myFile.xml");
    Resource Loading服務指南
    57
    5.2.4.3. XmlWebApplicationContext
    XmlWebApplicationContext支持從webapp上下文中(也就是ServletContext對象中)裝
    載資源。
    例 5.17. XmlWebApplicationContext - 從Web應用的根目錄中裝載資源
    假如我以下面的方式啟動Spring,那麼系統將支持從Web應用的根目錄中裝載資源。
    ApplicationContext context = new XmlWebApplicationContext();
    context.setConfigLocation("/WEB-INF/beans.xml");
    context.setServletContext(servletContext);
    context.refresh();
    也可以讓ContextLoaderListener來創建XmlWebApplicationContext,只需要在/WEB-INF/
    web.xml中添加如下配置:
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/beans.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    XmlWebApplicationContext裝載資源文件myFile.xml的邏輯,相當於如下代碼:
    URL resource = servletContext.getResource("myFile.xml");
    5.2.4.4. Classpath和Classpath*前綴
    除了用ClassPathXmlApplicationContext以外,事實上所有的Spring
    ApplicationContext實現也都支持裝載classpath中的資源。可以用下面兩種方法:
    表 5.2. Spring ApplicationContext裝載classpath資源的方法
    方法說明
    使用classpath:前綴例如:「classpath:myFile.xml」 —— 在classpath中裝載資源myFile.xml。
    使用classpath*:前綴例如:「classpath*:/META-INF/my*.xml」 —— 在classpath中裝載所有符合
    pattern的資源。
    5.2.5. Spring ResourceLoader的缺點
    魚和熊掌不可得兼
    Spring ResourceLoader是由ApplicationContext來實現的。而你一次只能選擇一
    種ApplicationContext的實現 —— 如果你選擇了XmlWebApplicationContext,你就放
    棄了FileSystemXmlApplicationContext;反之亦然。
    在WEB應用中,由於Spring使用了XmlWebApplicationContext,因此你就無法裝載文件
    系統下的資源。
    不透明性
    你必須用「絕對路徑」來引用Spring中的資源。
    Resource Loading服務指南
    58
    假如你使用FileSystemXmlApplicationContext來訪問資源,你必須使用絕對路徑來訪問
    文件或目錄資源。這妨礙了應用程序在不同系統中布署的自由。因為在不同的系統中,例如
    Windows和Linux,文件的絕對路徑是不同的。為了系統管理的需要,有時也需要將文件或
    目錄放在不同於開發環境的地方。
    即便是訪問WEB應用下的資源,或者是classpath下的資源,你也必須明確指出它們
    的位置,例如:WEB-INF/myFile.xml、classpath:myFile.xml等。如果我希望
    把classpath:myFile.xml挪到另一個物理位置,就必須修改所有的引用。
    無擴展性
    我無法在Spring ResourceLoader機制中增加一種新的裝載資源的方法。例如,我希望把資
    源文件保存在資料庫中,並用ResourceLoader來取得它。用Spring很難做到這點。
    5.3. Resource Loading服務
    5.3.1. 替換Spring ResourceLoader
    Webx Resource Loading服務可作為Spring ResourceLoader機制的替代品(Drop-in
    Replacement,投入既替換):
    • 當你不使用它時,Spring原有的ResourceLoader功能不受影響;
    • 當你在spring配置文件中添加Resource Loading服務時,ResourceLoader即被切換到新的
    機制。新的機制可兼容原有的Spring配置和代碼,但支持更多的資源裝載方式,以及更多的功
    能,如資源重命名、資源重定向等。
    你只需要在配置文件中增加以下內容,就可以將Spring ResourceLoader機制替換成Webx的
    Resource Loading服務:
    例 5.18. Resource Loading服務的基本配置(/WEB-INF/webx.xml)
    <resource-loading
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    <resource-alias pattern="/" name="/webroot" />
    <resource pattern="/webroot" internal="true">
    <res-loaders:webapp-loader />
    </resource>
    <resource pattern="/classpath" internal="true">
    <res-loaders:classpath-loader />
    </resource>
    </ resource-loading>
    關於這段配置的具體含義,請參見本章其它小節:
    請參見:第 5.3.3 節 「重命名資源」。
    請參見:第 5.3.2 節 「定義新資源」。
    請參見:第 5.4.2 節 「WebappResourceLoader」。
    請參見:第 5.4.3 節 「ClasspathResourceLoader」。
    Resource Loading服務指南
    59
    這段配置使得Resource Loading服務的行為和原來的Spring ResourceLoader完全兼容:
    • 仍然支持classpath:和classpath*:前綴所定義的資源。
    • 如不加前綴,則代表訪問WEB應用根目錄下的文件。例如:
    • /myFile.xml代表著Web應用根目錄下的/myFile.xml。
    • /WEB-INF/myFile.xml代表著Web應用根目錄下的/WEB-INF/myFile.xml。
    加上這段配置以後,雖然功能和原來相比並沒有變化,然而它已經準備好向系統中添加新的資源
    裝載的功能了。
    5.3.2. 定義新資源
    定義一種新資源,需要回答兩個問題:
    1. 資源的名稱是什麼?
    2. 資源在哪裡(或如何裝載資源)?
    下面的例子定義了一種新的資源,它的名稱是「/jdk/*」,通過「file-loader」從文件系
    統${java.home}文件夾中裝載。
    例 5.19. 定義新資源:/jdk/*
    <resource-loading
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
    <resource pattern="/jdk">
    <res-loaders:file-loader basedir="${java.home}" />
    </resource>
    </resource-loading>
    定義新資源,資源名以/jdk為前綴。
    <file-loader>表示從文件系統中裝載資源。詳見:第 5.4.1 節
    「FileResourceLoader」。
    ${java.home}是Java提供的system property,它的值指向當前Java運行環境的根目
    錄。
    前文講過,Spring可以直接把資源注入到對象中。使用Resource Loading服務以後,你
    仍然可以這樣做。下面的配置把JDK目錄下的tools.jar文件(如果存在的話)的URL注入
    到myBean中:
    例 5.20. 注入JAVA_HOME/lib/tools.jar
    <bean id="myBean" class="MyBean">
    <property name="location" value="/jdk/lib/tools.jar" />
    </bean>
    Resource Loading服務指南
    60
    5.3.3. 重命名資源
    重命名資源是指對於即有的資源,改變其名字。
    為什麼需要修改資源的名字?理由是:取消資源名稱和環境的關聯性。有一些資源的名稱,具有
    明顯的環境相關性,比如:
    • classpath:myFile.xml或者/classpath/myFile.xml —— 從資源的名稱就可以看出,這
    些資源是從classpath中裝載的。
    • /WEB-INF/myFile.xml或者/webroot/WEB-INF/myFile.xml —— 從資源的名稱可以看出,
    這些資源是從web應用中裝載的。
    使用和環境相關的資源名稱有什麼問題?問題就是,當環境改變時,應用代碼會受到影響。最
    常見的一種狀況是:單元測試時,用於測試的資源文件往往被放在專供測試的目錄中,這些
    目錄和應用運行時的環境是不同的 —— 你可能希望將classpath:myFile.xml或/WEB-INF/
    myFile.xml改成/src/test/config/myFile.xml。
    對資源重命名就可以解決這類問題:
    • 將classpath:myFile.xml或者/WEB-INF/myFile.xml重命名成:myapp/conf/
    myFile.xml。
    • 在測試環境中,將myapp/conf/myFile.xml名稱指向另一個物理地址src/test/config/
    myFile.xml。
    重命名資源是通過alias別名實現的:
    例 5.21. 重命名資源
    <resource-loading
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
    <resource-alias pattern="/myapp/conf" name="/webroot/WEB-INF" />
    <resource pattern="/webroot" internal="true">
    <res-loaders:webapp-loader />
    </resource>
    </resource-loading>
    定義了一個資源的別名:/myapp/conf。
    當你查找/myapp/conf/myFile.xml時,Resource Loading服務實際上會去找/webroot/
    WEB-INF/myFile.xml。而/webroot/*則是由 所定義的資源。
    定義以/webroot為前綴的新資源。
    其中,attribute internal=true是一個可選項,當它的值為true時,代表它所修飾的
    資源是不能被外界所直接訪問的。例如,你想直接在myBean中注入/webroot/WEB-INF/
    myFile.xml是不行的。把internal選項設成true,可以讓強制用戶轉向新的資源名
    稱。Internal參數的默認值為false,意味著,新舊兩種名稱同時可用。
    Resource Loading服務指南
    61
    <webapp-loader>表示從Web應用中裝載資源。詳見:第 5.4.2 節
    「WebappResourceLoader」。
    5.3.4. 重定向資源
    重定向資源的意思是,將部分資源名稱,指向另外的地址。
    一個常見的需求是這樣的:通常我們會把頁面模板保存在WEB應用的/templates目錄下。但是
    有一批模板是由外部的CMS系統生成的,這些模板文件不可能和WEB應用打包在一起,而是存
    放在某個外部的目錄下的。我們希望用/templates/cms來引用這些模板。
    由於/templates/cms只不過是/templates的子目錄,所以如果沒有Resource Loading服務所
    提供的重定向功能,是不可能實現上述功能的。用Resource Loading服務重定向的配置如下:
    例 5.22. 重定向資源
    <resource-loading
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
    <resource-alias pattern="/templates" name="/webroot/templates" />
    <resource pattern="/templates/cms">
    <res-loaders:file-loader basedir="${cms_root}" />
    </resource>
    <resource pattern="/webroot" internal="true">
    <res-loaders:webapp-loader />
    </resource>
    ...
    </resource-loading>
    定義了一個資源的別名:/templates,指向internal資源:/webroot/templates。
    將/templates的子目錄/templates/cms重定向到某個外部的文件目錄${cms_root}中。
    其中cms_root是啟動伺服器時所指定的system property(-Dcms_root=...)或者spring
    所定義的placeholder。
    通過上述配置,可以達到如下效果:
    表 5.3. 訪問/templates目錄下的資源
    資源名如何裝載?
    /templates/xxx.vm 不受重定向影響。訪問/webroot/templates/xxx.vm,繼而通過webapploader
    訪問Web應用根目錄下的/templates/xxx.vm文件。
    /templates/cms/yyy.vm 被重定向。通過file-loader訪問${cms_root}目錄下的文件:${cms_root}/
    yyy.vm。
    /templates/subdir/zzz.vm 不受重定向影響。訪問/webroot/templates/subdir/zzz.vm,繼而通
    過webapp-loader訪問Web應用根目錄下的/templates/subdir/zzz.vm文
    件。
    最重要的是,訪問/templates目錄的應用程序並不知道這個資源重定向的存在,當cms所對
    應的實際目錄被改變時,應用程序也不會受到任何影響 —— 這個正是Resource Loading服務
    的「魔法」。
    Resource Loading服務指南
    62
    5.3.5. 匹配資源
    無論是定義新資源(<resource>)或是重命名資源(資源別名、<resource-alias>),都需
    要指定一個pattern attribute來匹配資源的名稱。
    5.3.5.1. 匹配絕對路徑和相對路徑
    資源或資源別名的pattern支持對絕對路徑和相對路徑的匹配:
    表 5.4. 資源或別名的pattern格式
    pattern類型格式說明
    匹配絕對路徑/absolute/path
    以/開頭的pattern代表一個絕對路徑的匹配。
    例如:pattern="/absolute/path"可以匹配資源名/abslute/path/xxx/
    yyy,但不能匹配資源名/xxx/abslute/path/yyy。
    匹配相對路徑relative/path
    不以/開頭的pattern代表一個相對路徑的匹配。
    例如:pattern="relative/path"可以匹配資源名/relative/path/xxx/
    yyy,也可以匹配資源名/xxx/relative/path/yyy。
    5.3.5.2. 匹配通配符
    表 5.5. 通配符格式
    格式說明
    星號 * 匹配0-n個字元,但不包括「/」。即,「*」只匹配一級目錄或文件中的零個或多個字元。
    雙星號 ** 匹配0-n個字元,包括「/」。即,「**」匹配多級目錄或文件。
    問號 ? 匹配0-1個字元,但不包括「/」。即,「?」匹配一級目錄或文件中的零個或一個字元。
    所有被通配符匹配的內容,將被按順序賦給變數「$1」、「$2」、「$3」、「$4」、……。這
    些變數可以在其它地方被引用。
    通配符匹配的名稱既可以是絕對路徑,也可以是相對路徑。把相對路徑和通配符結合起來的最常
    見用法,就是匹配文件名後綴,例如:pattern="*.xml"。
    下面是一些使用通配符的例子:
    例 5.23. 用通配符來匹配資源名稱或資源別名
    重命名WEB-INF及其子目錄下的所有的xml文件
    例如,將/myapp/conf/my/file.xml轉換成/webroot/WEB-INF/my/file.xml。
    <resource-alias pattern="/myapp/conf/**/*.xml" name="/webroot/WEB-INF/$1/$2.xml" />
    修改文件名後綴
    例如,將/myapp/conf/myfile.conf轉換成/webroot/WEB-INF/myfile.xml。
    <resource-alias pattern="/myapp/conf/*.conf" name="/WEB-INF/$1.xml"/>
    按首字母劃分子目錄
    將a開頭的文件名放到a子目錄下,b開頭的文件名放到b子目錄下,以此類推。
    Resource Loading服務指南
    63
    例如,將/profiles/myname轉換成文件路徑${profile_root}/m/myname;將/
    profiles/othername轉換成文件路徑${profile_root}/o/othername。
    <resource pattern="/profiles/?*">
    <res-loaders:file-loader basedir="${profile_root}">
    <res-loaders:path>$1/$1$2</res-loaders:path>
    </res-loaders:file-loader>
    </resource>
    5.3.6. 在多個ResourceLoader中查找
    假如,在我的Web應用中,我有一些配置文件放在/WEB-INF目錄中,另外一部分配置放在
    classpath中。我可以這樣做:
    例 5.24. 在多個ResourceLoader中查找
    <resource-loading
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    ...
    <resource pattern="/myapp/conf">
    <res-loaders:super-loader name="/webroot/WEB-INF" />
    <res-loaders:super-loader name="/classpath" />
    </resource>
    <resource pattern="/webroot" internal="true">
    <res-loaders:webapp-loader />
    </resource>
    <resource pattern="/classpath" internal="true">
    <res-loaders:classpath-loader />
    </resource>
    ...
    </resource-loading>
    依次嘗試兩個loaders。
    定義internal資源/webroot/*,從Web應用中裝載資源。詳見第 5.4.2 節
    「WebappResourceLoader」。
    定義internal資源/classpath/*,從classpath中裝載資源。詳見第 5.4.3 節
    「ClasspathResourceLoader」。
    Resource Loading服務根據上面的配置,會這樣查找資源「/myapp/conf/myFile.xml」:
    1. 先查找:/webroot/WEB-INF/myFile.xml,如果找不到,
    2. 則再查找:/classpath/myFile.xml,如果找不到,則放棄。
    在上例中,<super-loader>(詳見第 5.4.4 節 「SuperResourceLoader」)是一種特殊
    的ResourceLoader,它等同於<resource-alias>。下面的兩種寫法是完全等同的:
    例 5.25. <super-loader>和等效的<resource-alias>
    <resource pattern="/myapp/conf">
    <res-loaders:super-loader name="/webroot/WEB-INF" />
    </resource>
    <resource-alias pattern="/myapp/conf " name="/webroot/WEB-INF" />
    Resource Loading服務指南
    64
    但是用<resource-alias>沒有辦法實現上面所述的多重查找的功能。
    5.3.7. 裝載parent容器中的資源
    在Webx中,Spring容器被安排成級聯的結構。
    圖 5.3. Spring容器的級聯結構
    如圖所示,每個Spring容器都可以配置自己的Resource Loading服務。當調用子容器的
    Resource Loading服務時,遵循這樣的邏輯:
    1. 先在子容器的Resource Loading服務中查找資源,如果找不到,
    2. 則再到parent容器的Resource Loading服務中查找,如果找不到,則放棄。
    運用這種級聯裝載資源的方法,子應用可以把共享的資源定義在root context中,而把自己獨享
    的資源定義在自己的容器當中。
    前文所述的<super-loader>也支持級聯裝載資源。<super-loader>會先在當前容器的
    Resource Loading服務中查找,如果找不到,就到parent容器的Resource Loading服
    務中查找。利用<super-loader>,你甚至可以改變資源搜索的順序。例如,你可以命令
    Resource Loading服務先查找parent容器中的Resource Loading服務,再查找當前容器中
    的ResourceLoaders:
    例 5.26. 利用<super-loader>改變資源搜索的順序
    <resource pattern="...">
    <res-loaders:super-loader />
    <res-loaders:file-loader />
    </resource>
    先找parent容器中的Resource Loading服務。
    再找當前容器中的ResourceLoaders。
    5.3.8. 修改資源文件的內容
    Resource Loading服務支持內容過濾 —— 你可以在獲取資源以前讀取甚至修改資源文件的內
    容。一種常見的情形是,將XML格式的資源文件用XSLT轉換格式:
    Resource Loading服務指南
    65
    例 5.27. 將XML格式的資源文件用XSLT轉換格式
    <resource-loading
    xmlns="http://www.alibaba.com/schema/services"
    xmlns:res-loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
    xmlns:res-filters="http://www.alibaba.com/schema/services/resource-loading/filters">
    ...
    <resource-filters pattern="test-*.xml">
    <res-filters:xslt-filter xslt="/stylesheet.for.test/test.xsl" saveTo="/tempdir" />
    </resource-filters>
    <resource pattern="/tempdir">
    <loaders:file-loader basedir="${project.home}/target/test" />
    </resource>
    </resource-loading>
    將所有目錄下(因為是相對路徑)的名稱為test-*.xml文件,用指定的XSL文件進行轉
    換。
    這裡引進了一種新的擴展點:ResourceFilter。ResourceFilter可以在應用獲取資源之
    前,取得控制,以便對資源做一點事。
    <xslt-filter>是對ResourceFilter的擴展,它能夠把XML資源用指定的xsl文件轉換成
    新的格式。假如指定了saveTo參數,就可以把轉換的結果保存下來,避免每次訪問都重新
    轉換。
    此處定義tempdir目錄資源,以便保存xslt轉換的結果。
    注意
    <xslt-filter>的參數xslt所指向的xsl文件,以及參數saveTo所指向的目錄,它
    們本身也是由Resource Loading服務裝載的。
    有哪些情況需要這種內容過濾的功能呢?
    • 單元測試 —— 我們可能需要對單元測試的資源文件進行特殊的轉換。
    • 高速緩存 —— 有一些ResourceLoader可能會有性能的開銷,例如:從資料庫中裝載資源。
    利用ResourceFilter功能,就可以把裝載的資源緩存在高速cache中,以提高系統的性能。
    5.3.9. 直接使用ResourceLoadingService
    前面所講的Resource Loading服務的用法,對應用程序而言,是完全透明的。也就是說,應
    用程序並不需要關心Resource Loading服務的存在,而是按照Spring ResourceLoader的老用
    法,就可以工作。
    但是你也可以直接注入ResourceLoadingService對象,以取得更多的功能。
    例 5.28. 注入ResourceLoadingService對象
    public class MyClass {
    @Autowired
    private ResourceLoadingService resourceLoadingService;
    }
    下面列舉了可通過ResourceLoadingService介面實現的功能。
    Resource Loading服務指南
    66
    取得資源
    例 5.29. 通過ResourceLoadingService介面取得資源
    Resource resource = resourceLoadingService.getResource("/myapp/conf/myFile.xml");
    Resource resource = resourceLoadingService.getResource("/myapp/conf/myFile.xml",
    ResourceLoadingService.FOR_CREATE);
    和Spring不同的是,如果你直接調用ResourceLoadingService取得資源,當資源文
    件不存在時,你會得到一個ResourceNotFoundException。而Spring無論如何都會取
    得Resource對象,但隨後你需要調用Resource.exists()方法來判斷資源存在於否。
    ResourceLoadingService.getResource()方法還支持一個選項:FOR_CREATE。
    如果提供了這個選項,那麼對於某些類型的資源(如文件系統的資源),即使文件
    或目錄不存在,仍然會返回結果。這樣,你就可以創建這個文件或目錄 —— 這就
    是FOR_CREATE參數的意思。
    取得特定類型的資源
    例 5.30. 通過ResourceLoadingService介面取得特定類型的資源
    // 取得資源文件
    File file = resourceLoadingService.getResourceAsFile("/myapp/conf/myFile.xml");
    // 取得資源URL
    URL url = resourceLoadingService.getResourceAsURL("/myapp/conf/myFile.xml");
    // 取得資源輸入流
    InputStream stream = resourceLoadingService.getResourceAsStream("/myapp/conf/myFile.xml");
    判斷資源存在於否
    例 5.31. 通過ResourceLoadingService介面判斷資源存在於否
    if (resourceLoadingService.exists("/myapp/conf/myFile.xml")) {
    ...
    }
    列舉子資源
    例 5.32. 通過ResourceLoadingService介面列舉子資源
    String[] resourceNames = resourceLoadingService.list("/myapp/conf");
    Resource[] resources = resourceLoadingService.listResources("/myapp/conf");
    相當於列出當前目錄下的所有子目錄和文件。
    不是所有的ResourceLoader都支持這個操作 ——
    FileResourceLoader和WebappResourceLoader支持列舉子資
    源,ClasspathResourceLoader則不支持。
    跟蹤取得資源的過程
    例 5.33. 通過ResourceLoadingService介面跟蹤取得資源的過程
    ResourceTrace trace = resourceLoadingService.trace("/myapp/conf/webx.xml");
    for (ResourceTraceElement element : trace) {
    System.out.println(element);
    }
    Resource Loading服務指南
    67
    這是用來方便調試的功能。有點像Throwable.getStackTrace()方法,可以得到每一個方
    法調用的歷史記錄 —— ResourceLoadingService.trace()方法可以將取得資源的步驟記
    錄下來。上面代碼會在console中輸出類似下面的內容:
    "/myapp/conf/webx.xml" matched [resource-alias pattern="/myapp/conf"], at "resources.xml",
    beanName="resourceLoadingService"
    "/webroot/WEB-INF/webx.xml" matched [resource pattern="/webroot"], at "resources.xml",
    beanName="resourceLoadingService"
    列出所有可用的資源定義和別名的pattern
    例 5.34. 通過ResourceLoadingService介面列出所有可用的資源定義和別名的pattern
    String[] patterns = resourceLoadingService.getPatterns(true);
    5.3.10. 在非Web環境中使用Resource Loading服務
    圖 5.4. 在非Web環境中使用的ResourceLoadingXmlApplicationContext
    在非Web環境中使用Resource Loading服務的最好方法,是創
    建ResourceLoadingXmlApplicationContext作為Spring容器。
    例 5.35. 創建ResourceLoadingXmlApplicationContext容器
    ApplicationContext context = new ResourceLoadingXmlApplicationContext(
    new FileSystemResource("beans.xml"));
    只要beans.xml中包含<resource-loading>的配置,就會自動啟用Resource Loading服務,
    並取代Spring原來的ResourceLoader機制。
    Resource Loading服務指南
    68
    5.4. ResourceLoader參考
    Resource Loading服務的核心是ResourceLoader。和Spring ResourceLoader不
    同,Resource Loading服務的ResourceLoader是可擴展的輕量級對象,擔負著裝
    載某一種類型的資源的具體任務。例如FileResourceLoader負責裝載文件系統的資
    源;WebappResourceLoader負責裝載WEB應用中的資源等等。
    當你需要新的資源裝載方式時,你所要做的,就是實現一種新的ResourceLoader。例如,你想
    從資料庫中裝載資源,那麼就可以實現一個DatabaseResourceLoader。
    5.4.1. FileResourceLoader
    FileResourceLoader的功能是:從文件系統中裝載資源。
    基本用法
    例 5.36. FileResourceLoader的基本用法
    <resource pattern="/my/virtual">
    <res-loaders:file-loader />
    </resource>
    這樣,file-loader會從哪裡裝載資源呢?
    答案是:從當前配置文件所在的目錄中裝載。假如上述資源配置所在的配置文件是c:/
    myapp/conf/resources.xml,那麼file-loader就會從c:/myapp/conf/myFile.xml文
    件中裝載/my/virtual/myFile.xml資源。
    這樣做的思路源自於Apache的一個項目:Ant。Ant是一個廣為使用的build工具。每一個
    Ant項目,都有一個build.xml腳本,在裡面定義了很多target,諸如編譯項目、打包等。
    通常我們都會把build.xml這個文件放在項目的根目錄中,然後build.xml中的命令全是使
    用相對於build.xml所在的項目根目錄計算出來的相對路徑。例如:
    例 5.37. Ant腳本(build.xml)
    <project basedir=".">
    ...
    <target ...>
    <copy todir="bin">
    <fileset dir="src"/>
    </copy>
    </target>
    ...
    </project>
    在上面的Ant腳本中,bin、src目錄全是相對於build.xml所在目錄的相對目錄。這樣做的
    好處是,當你把項目移到不同的環境中,你也無需改變配置文件和腳本。
    FileResourceLoader採用了和Ant完全類似的想法。
    指定basedir
    例 5.38. 在FileResourceLoader中指定basedir
    <resource pattern="/my/virtual">
    <res-loaders:file-loader basedir="${my.basedir}" />
    </resource>
    Resource Loading服務指南
    69
    FileResourceLoader當然也支持指定basedir根目錄。這樣,它就會從指定的basedir的
    子目錄中查找資源。
    一般來說,我們需要利用Spring Property Placeholder來設置basedir。在上面的例子
    中,我們可以在系統啟動時,指定JVM參數:-Dmy.basedir=c:/mydata。在不同的系統環
    境中,必須指定正確的basedir,否則,<file-loader>有可能找不到資源。
    搜索多個路徑
    例 5.39. 在FileResourceLoader中指定多個搜索路徑
    <resource pattern="/my/virtual">
    <res-loaders:file-loader basedir="...">
    <res-loaders:path>relativePathToBasedir</res-loaders:path>
    <res-loaders:path type="absolute">c:/absolutePath</res-loaders:path>
    </res-loaders:file-loader>
    </resource>
    搜索路徑默認為相對路徑,相對於指定的basedir。如果basedir未指定,則相對於當
    前resource-loading所在的配置文件的路徑。
    搜索路徑也可以是絕對路徑。
    FileResourceLoader支持搜索多個路徑,類似於操作系統在PATH環境變數所指定的路徑
    中,搜索可執行文件;也類似於Java在CLASSPATH參數所指定的路徑中,搜索classes。
    5.4.2. WebappResourceLoader
    WebappResourceLoader的功能是:從當前WEB應用中裝載資源,也就是從ServletContext對
    象中裝載資源。
    例 5.40. 配置WebappResourceLoader
    <resource pattern="/my/virtual">
    <res-loaders:webapp-loader />
    </resource>
    5.4.3. ClasspathResourceLoader
    ClasspathResourceLoader的功能是:從classpath中裝載資源,也就是從當前
    的ClassLoader對象中裝載資源。
    例 5.41. 配置ClasspathResourceLoader
    <resource pattern="/my/virtual">
    <res-loaders:classpath-loader />
    </resource>
    5.4.4. SuperResourceLoader
    SuperResourceLoader的功能是:調用Resource Loading服務來取得資源。它有點像Java里
    面的super操作符。
    Resource Loading服務指南
    70
    取得新名字所代表的資源
    例 5.42. 用SuperResourceLoader取得新名字所代表的資源
    <resource pattern="/my/virtual">
    <res-loaders:super-loader basedir="/webroot/WEB-INF" />
    </resource>
    這個操作類似於<resource-alias>。
    如果在當前context的Resource Loading服務中找不到資源,它會前往parent context中
    查找。
    在parent context中查找資源
    例 5.43. 用SuperResourceLoader查找parent context中的資源
    <resource pattern="/my/virtual">
    <res-loaders:super-loader />
    </resource>
    如果你不指定name參數,那麼SuperResourceLoader會直接去parent context中查找資
    源,而不會在當前context的Resource Loading服務中找。
    5.4.5. 關於ResourceLoader的其它考慮
    以上所有的ResourceLoader都被設計成可以在任何環境中工作,即使當前環境不適用,也不會
    報錯。
    WebappResourceLoader可以兼容非WEB環境
    在非WEB環境中,例如單元測試環境、你直接通過XmlApplicationContext創建的Spring
    環境,WebappResourceLoader也不會出錯 —— 只不過它找不到任何資源而已。
    SuperResourceLoader可以工作於非級聯的環境
    也就是說,即使parent context不存在,或者parent context中沒有配置Resource
    Loading服務,SuperResourceLoader也是可以工作的。
    這樣,同一套資源配置文件,可以被用於所有環境。
    5.5. 本章總結
    Resource Loading服務提供了一套高度可擴展的、強大的資源裝載機制。這套機制和Spring
    ResourceLoader無縫連接。使用它並不需要特殊的技能,只要掌握Spring的風格即可。
    71
    第 6 章 Filter、Request Contexts和
    Pipeline
    6.1. Filter ....................................................................................................................... 71
    6.1.1. Filter的用途 .................................................................................................. 71
    6.1.2. Filter工作原理 ............................................................................................... 72
    6.1.3. Filter的限制 .................................................................................................. 73
    6.1.4. Webx對filter功能的補充 ................................................................................ 73
    6.2. Request Contexts服務 ........................................................................................... 74
    6.2.1. Request Contexts工作原理 .......................................................................... 74
    6.2.2. Request Contexts的用途 ............................................................................. 75
    6.2.3. Request Contexts的使用 ............................................................................. 76
    6.3. Pipeline服務 ........................................................................................................... 79
    6.3.1. Pipeline工作原理 .......................................................................................... 79
    6.3.2. Pipeline的用途 ............................................................................................. 80
    6.3.3. Pipeline的使用 ............................................................................................. 82
    6.4. 本章總結 ................................................................................................................. 91
    Filter是Servlet規範2.3版及更新版所支持的一種機制。和Servlet/JSP不同,Filter自己往往不會
    直接產生response,相反,它提供了一種「符加」的功能,可以作用在任何一個servlet、JSP以
    及其它filter之上。然而,在實際的應用中,我們發現filter有很多不足之處。
    Webx框架提供了兩種機制(Request Contexts和Pipeline)來作為filter機制的補充。在大
    多數情況下,它們都可以實現類似filter的功能,但比filter更容易擴展、更容易配置、也更輕
    量。Webx並沒有打算完全替代filter,相反它還是可以和任何filter搭配使用。
    本章先簡略介紹filter的功能和不足,再向你介紹Request Contexts和Pipeline的工作原理,及
    使用方法。
    6.1. Filter
    6.1.1. Filter的用途
    Filter這種機制常被用來實現下面的功能:
    頁面授權根據登錄用戶的許可權,阻止或許可用戶訪問特定的頁面。
    日誌和審計記錄和檢查用戶訪問WEB應用的情況。
    圖片轉換改變圖片的格式、精度、尺寸等。
    頁面壓縮壓縮頁面內容,加快下載速度。
    本地化顯示本地語言和風格的頁面。
    XSLT轉換對XML內容進行XSLT轉換,使之適用於多種客戶端。
    高速緩存高速緩存頁面,提高響應速度。
    當然還有更多種的應用,我們不可能一一列舉。
    Filter的通用性很好。任何filter均獨立於其它filter和servlet,因此它可以和任意其它filter和
    servlet組合搭配。下面是一段配置示例 ── 通過SetLoggingContextFilter,日誌系統可以
    記錄當前請求的信息,例如:URL、referrer URL、query string等。
    Filter、Request
    Contexts和Pipeline
    72
    例 6.1. Filter配置示例(/WEB-INF/web.xml)
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    <filter>
    <filter-name>mdc</filter-name>
    <filter-class>com.alibaba.citrus.webx.servlet.SetLoggingContextFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>mdc</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
    </web-app>
    6.1.2. Filter工作原理
    圖 6.1. Filter Chain
    如圖所示。多個filter和至多一個servlet被串聯成一個鏈,被稱為Filter Chain。執行的時候,引
    擎將控制權交給鏈條中的頭一個filter(如果有的話)。然後,就像擊鼓傳花一樣,控制權被依
    次傳遞給filter chain中的下一個filter或servlet。每一個得到控制權的filter可以做下面的事:
    • 繼續傳遞控制權或立即終止filter chain。
    • Filter可將控制權傳遞給鏈條中的下一個filter或者最終的servlet。
    • Filter也可以不將控制權傳遞給下一個filter或servlet,這樣便中止了整個filter chain的執
    行。
    Filter、Request
    Contexts和Pipeline
    73
    • 預處理。在傳遞控制權給下一個filter或servlet之前,filter可以預先做一些事情:
    • 設置request、response中的參數,例如:character encoding、content type等。
    • 將HttpServletRequestWrapper傳遞給鏈條中的下一位,filter可以通過wrapper改變
    request中的任意值。
    • 將HttpServletResponseWrapper傳遞給鏈條中的下一位,filter可以通過wrapper來攔截
    後續filter或servlet對response的修改。
    • 提交。在控制權從filter chain中返回以後,filter還可以做一些後續提交的操作。
    • 例如,將response中攔截而來的數據,壓縮或轉換格式,併發送給客戶端或filter chain的
    上一級。
    • 通過try、catch還可以捕獲filter chain下一級所有的異常,並做處理。
    6.1.3. Filter的限制
    Filter是很有用的。作為servlet的補充,filter也是很成功的。但是filter並沒有被設計用來完成一
    切事情。事實上,filter的設計限制了filter的用途。每個filter具有下面的限制:
    • Filter可以訪問和修改數據。但它只能訪問和修
    改HttpServletRequest、HttpServletResponse、ServletContext等容器級的對象,而不
    能(或很難)訪問應用程序中的狀態。所以filter無法實現和應用邏輯密切相關的功能。
    • Filter可以影響執行流程。但它不能改變filter chain的結構和順序。Filter chain的結構和順序
    是由web.xml中定義的。當filter得到控制權以後,它只能選擇繼續下去,或者立即結束,而沒
    法進行循環、分支、條件判斷等更複雜的控制。因此,filter只能用來實現粗粒度的流程式控制制
    功能(例如,當用戶未獲授權時,停止執行filter chain),難以應付更細緻的應用程序內的
    控制需求。
    • Filter與其它filter和servlet之間,除了request和response對象以外,無法共享其它的狀態。
    這既是優點又是缺點。優點是使filter更獨立、更通用;缺點是filter與其它filter、servlet之間
    難以協作,有時甚至會引起無謂的性能損失。
    6.1.4. Webx對filter功能的補充
    綜上所述,一個filter常常做的兩件事是:
    • 改變request/response對象(通
    過HttpServletRequestWrapper和HttpServletResponseWrapper);
    • 改變應用執行的流程。
    其實,大部分filter只做其中一件事。例如:
    • 頁面壓縮filter僅僅改變response,並不改變應用的流程。
    • 頁面授權filter根據當前請求用戶的身份,判定他是否有許可權訪問當前頁面。這個filter會影響
    應用流程,卻不會去改變request和response。
    當然也有例外。有一些filter不做上面兩件事中任何一件。例如,日誌filter僅僅讀取request對象
    並記錄日誌而已,既不改變request/response,也不影響應用的流程。還有一些filter同時做上
    面兩件事。比如高速緩存頁面的filter不僅要修改response,而且當cache被命中時,不再執行
    下一步的流程,而是直接返回cache中的內容,以提高性能。
    Filter、Request
    Contexts和Pipeline
    74
    Webx框架提供了兩個服務,正好吻合了上述兩個最常用的filter的功能。
    Request Contexts服務該服務負責訪問和修改request和response,但不負責改變應用執行的流程。
    Pipeline服務提供應用執行的流程,但不關心request和response。
    雖然這兩個服務看起來和filter的功能類似,但是它們遠比filter要強大和方便 ── 它們克服了上
    述filter的幾個限制:
    • 和Filter不同,Request Contexts和Pipeline服務可以訪問應用內部的狀態和資源,效率更
    高,功能更強。
    • 和Filter不同,Pipeline服務可以定義靈活(但仍然簡單)地控制應用的流程 。Pipeline不僅可
    以控制流程的中斷或繼續,還可以實現子流程、循環、條件轉移、異常處理等更精細的流程式控制
    制。Pipeline服務甚至可以運用在非WEB的環境中。
    • 和Filter不同,Request Contexts服務中的每一個環節(Request Context)之間並非完全
    獨立、互不干涉的。每個request context可以訪問它所依賴的其它request context中的狀
    態。
    6.2. Request Contexts服務
    6.2.1. Request Contexts工作原理
    Request Context,顧名思義,就是一個請求的上下文。事實上,你可以把Request Context
    看作是HttpServletRequest和HttpServletResponse這兩個對象的總和。除此之外,多個
    Request Context可以被串接起來,被稱為Request Context Chain,類似於filter chain。
    圖 6.2. Request Context Chain
    Filter、Request
    Contexts和Pipeline
    75
    如上圖所示,每一個Request Context都可以包括兩個基本的操作:「預處理」和「提交」。
    • 在一個請求開始的時候,每個Request Context的「預處理」過程被依次調用。最內層的
    (即最先的)Request Context最先被調用,最外層的(即最後的)Request Context最後
    被調用;
    • 在一個請求結束的時候,每個Request Context的「提交」過程被依次調用。和「預處
    理」的順序相反,最外層的(即最後的)Request Context最先被調用,最內層的(即最先
    的)Request Context最後被調用。
    Request Context在預處理的時候,可以利
    用HttpServletRequestWrapper和HttpServletResponseWrapper來包裝和修改request和
    response ── 這一點和filter相同。每一層Request Context,都會增加一個新的特性。最先的
    Request Context成為最內層的包裝,最後的Request Context成為最外層的包裝。如下圖所
    示。
    圖 6.3. Request Contexts的嵌套
    和filter原理中的圖進行對比,你會發現,儘管Request Contexts和Filter的執行方案有明顯的不
    同,但是Request Contexts預處理和提交的順序是和filter chain完全一致的。預處理時,由內
    層執行到外層;提交時,反過來由外層執行到內層。不同的是,filter能夠決定是否繼續傳遞控
    制權給filter chain中的下一位,而Request Context則沒有這個權利。
    6.2.2. Request Contexts的用途
    Webx目前提供了以下幾種request context的實現,每個都有獨特的功能。
    表 6.1. Request Contexts的功能
    名稱功能
    <basic> 提供基礎安全特性,例如:過濾response headers、cookies,限制cookie的
    大小等。
    Filter、Request
    Contexts和Pipeline
    76
    名稱功能
    <buffered> 緩存response中的內容。
    <lazy-commit> 延遲提交response。
    <parser> 解析參數,支持multipart/form-data(即上傳文件請求)。
    <rewrite> 重寫請求的URL和參數。
    <session> 一套可擴展的session框架,重新實現了HttpSession介面。
    <set-locale> 設置locale區域和charset字符集編碼。
    注意
    本章對以上所有的request contexts的功能和用法不作具體的介紹,詳情請參
    閱第 7 章 Request Contexts功能指南和第 8 章 Request Context之Session指南。
    需要特別指出的是,你還可以擴展出更多的Request Context,以實現新的功能。
    6.2.3. Request Contexts的使用
    6.2.3.1. 配置
    除了下面例子所示的一段配置之外,你不需要做太多的事,就可以使用Request
    Contexts。因為Request Contexts對於應用來說是透明的 ── 多數應用只需要依賴
    於HttpServletRequest和HttpServletResponse就可以了。
    例 6.2. Request Context的配置(/WEB-INF/webx.xml)
    <?xml version="1.0" encoding="UTF-8" ?>
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:request-contexts="http://www.alibaba.com/schema/services/request-contexts"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
    http://www.alibaba.com/schema/services
    http://localhost:8080/schema/services.xsd
    http://www.alibaba.com/schema/services/request-contexts
    http://localhost:8080/schema/services-request-contexts.xsd
    http://www.springframework.org/schema/beans
    http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
    ">
    ...
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <basic />
    <buffered />
    <lazy-commit />
    <parser />
    <set-locale defaultLocale="zh_CN" defaultCharset="UTF-8" />
    <!-- Optional -
    <session />
    <rewrite />
    -->
    </services:request-contexts>
    <services:upload sizeMax="5M" />
    </beans:beans>
    由於使用了SpringExt的schema機制,所以在支持schema的XML編輯器的幫助下,很容易書
    寫和驗證Request Contexts的配置。
    Filter、Request
    Contexts和Pipeline
    77
    6.2.3.2. 排序
    Request Contexts之間,有時會有依賴關係,所以Request Contexts出現的先後順序是非常重
    要的。例如,
    • <session>提供了基於cookie的session支持。然而cookie屬於response header。一旦
    response被提交,header就無法再修改了。因此<session>依賴於<lazy-commit>,以阻止
    response過早提交。也就是說,<lazy-commit>必須排在<session>之前。
    • <rewrite>需要訪問參數,而參數是能過<parser>解析的,所以<parser>要排
    在<rewrite>之前。
    類似的約束還有很多。如果把Request Contexts的順序排錯,可能會導致某項功能錯誤或失
    效。然而,對於一般的應用開發者而言,這些約束往往是神秘的、並非顯而易見的,需要經過細
    致地分析才能了解它們。
    好在Request Contexts內部提供了一個機制,可以根據預定義的約束條件,對所有的Request
    Contexts進行自動排序。和Filter不同,應用開發者不需要在意Request Contexts在配置文件中
    的排列順序,就可以保證所有的Request Contexts能夠正常工作。下面的兩種配置文件是等效
    的:
    例 6.3. Request Contexts等效配置1
    <services:request-contexts>
    <basic />
    <buffered />
    <lazy-commit />
    <parser />
    <set-locale />
    <session />
    <rewrite />
    </services:request-contexts>
    例 6.4. Request Contexts等效配置2
    <services:request-contexts>
    <rewrite />
    <session />
    <set-locale />
    <parser />
    <lazy-commit />
    <buffered />
    <basic />
    </services:request-contexts>
    6.2.3.3. 訪問特定的Request Context
    一般來說,Request Contexts對於應用程序是透明的 ── 也就是說,應用程序最多只需要訪問
    Servlet API中的介面:HttpServletRequest和HttpServletResponse即可,就好像Request
    Contexts不存在一樣。
    比如,Request Context <parser>能夠解析multipart/form-data類型的請求
    (即上傳圖片請求)。但你不需要用另一個API來訪問請求中的普通數據,你只需要
    用HttpServletRequest中定義的方法就可以訪問,彷彿這是一個普通的請求:
    例 6.5. 訪問任意類型的請求中的參數
    String value = request.getParameter("myparam");
    再比如,Request Context <session>重新實現了HttpSession的介面,但是應用程序並不需
    要關心這些,他們還是和原來一樣訪問session:
    例 6.6. 訪問session
    HttpSession session = request.getSession();
    String value = (String) session.getAttribute("myattr");
    session.setAttribute("myattr", newValue);
    然而,有一些功能在原有的Servlet API中是不存在的。對於這一類功能,你必須訪問特定
    的RequestContext介面,才能使用它們。例如,你只能用另一個API才能讀取用戶上傳的文
    件。下面的代碼可以用來取得上傳文件的信息:
    Filter、Request
    Contexts和Pipeline
    78
    例 6.7. 訪問特定的RequestContext介面
    ParserRequestContext parserRequestContext =
    RequestContextUtil.findRequestContext(request, ParserRequestContext.class);
    ParameterParser params = parserRequestContext.getParameters();
    FileItem myfile = params.getFileItem("myfile");
    String filename = myfile.getName();
    InputStream istream = myfile.getInputStream();
    另外有一些功能,使用Request Context介面比原來的Servlet API介面更方便。
    例如,原來的request.getParameter()方法只能取得字元串的參數值,但是利
    用ParserRequestContext所提供的介面,就可以直接取得其它類型的值:
    例 6.8. 通過ParserRequestContext介面訪問參數比HttpServletRequest更方便
    ParameterParser params = parserRequestContext.getParameters();
    String stringValue = params.getString("myparam"); // 取得字元串值,默認為null
    int intValue = params.getInt("myparam"); // 取得整數值,默認為0
    boolean booleanValue = params.getBoolean("myparam", true); // 取得boolean值,指定默認值為true
    6.2.3.4. 注入request作用域的對象
    Spring最強大的功能是依賴注入。但是依賴注入有一個限制:小作用域的對象不能被注入到大作
    用域的對象。你不能夠把request和session作用域的對象注入到singleton對象中。前者在每次
    WEB請求時,均會創建新的實例,每個線程獨享這個request/session作用域的對象;後者是在
    Spring初始化或第一次使用時被創建,然後被所有的線程共享。假如你把某個request/session
    作用域的對象意外注入到singleton對象中,將可能產生致命的應用錯誤,甚至導致資料庫的錯
    亂。
    表 6.2. Webx中的重要對象及其作用域
    對象類型作用域
    ServletContext Singleton scope
    HttpServletRequest Request scope
    HttpServletResponse Request scope
    HttpSession Session scope
    所有RequestContext對象,
    如:ParserRequestContext、SessionRequestContext等Request scope
    在一般的情況下,對於一個singleton對象而言,例如,Webx中的action module、pipeline
    valve對象等,下面的代碼是錯誤的:
    例 6.9. 在action(singleton對象)中注入request scope的對象
    public class MyAction {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;
    @Autowired
    private ParserRequestContext parser;
    }
    Filter、Request
    Contexts和Pipeline
    79
    因為你不能把一個短期的對象如request、response和request context注入到MyAction這個
    singleton對象。然而,在Webx中,這樣做是可以的!奧秘在於Request Contexts服務對上表
    所列的這些短期對象作了特殊的處理,使它們可以被注入到singleton對象中。事實上,被注入
    的只是一個「空殼」,真正的對象是在被訪問到的時候才會從線程中取得的。
    Webx鼓勵應用程序使用singleton作用域的對象,不僅更簡單,也更高效。經過上述技術處理
    以後,singleton對象訪問request作用域對象的方法被大大簡化了。
    6.3. Pipeline服務
    6.3.1. Pipeline工作原理
    Pipeline的意思是管道,管道中有許多閥門(Valve),閥門可以控制水流的走向。在Webx
    中,pipeline的作用就是控制應用程序流程的走向。
    圖 6.4. Pipeline和Valves
    Pipeline的設計和filter非常相似,也是擊鼓傳花式的流程式控制制。但是有幾點不同:
    • Pipeline只能控制流程,不能改變request和response。
    • Pipeline是輕量級組件,它甚至不依賴於WEB環境。Pipeline既可以在程序中直接裝配,也可
    以由spring和schema來配置。
    Filter、Request
    Contexts和Pipeline
    80
    • Pipeline支持更複雜的流程結構,例如:子流程、條件分支、循環等。
    6.3.2. Pipeline的用途
    Pipeline可以說是Webx框架的核心功能之一。利用pipeline,你可以定製一個請求處理過程的
    每一步。
    Filter、Request
    Contexts和Pipeline
    81
    例 6.10. 一個典型的Webx應用的pipeline配置文件(pipeline.xml)
    <?xml version="1.0" encoding="UTF-8" ?>
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:pl-conditions="http://www.alibaba.com/schema/services/pipeline/conditions"
    xmlns:pl-valves="http://www.alibaba.com/schema/services/pipeline/valves"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
    http://www.alibaba.com/schema/services
    http://localhost:8080/schema/services.xsd
    http://www.alibaba.com/schema/services/pipeline/conditions
    http://localhost:8080/schema/services-pipeline-conditions.xsd
    http://www.alibaba.com/schema/services/pipeline/valves
    http://localhost:8080/schema/services-pipeline-valves.xsd
    http://www.springframework.org/schema/beans
    http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
    ">
    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    <!-- 初始化turbine rundata,並在pipelineContext中設置可能會用到的對象(如rundata、utils),以便valve取得。
    -->
    <prepareForTurbine />
    <!-- 設置日誌系統的上下文,支持把當前請求的詳情列印在日誌中。 -->
    <setLoggingContext />
    <!-- 分析URL,取得target。 -->
    <analyzeURL homepage="homepage" />
    <!-- 檢查csrf token,防止csrf攻擊和重複提交。 -->
    <checkCsrfToken />
    <loop>
    <choose>
    <when>
    <!-- 執行帶模板的screen,默認有layout。 -->
    <pl-conditions:target-extension-condition extension="null, vm, jsp" />
    <performAction />
    <performTemplateScreen />
    <renderTemplate />
    </when>
    <when>
    <!-- 執行不帶模板的screen,默認無layout。 -->
    <pl-conditions:target-extension-condition extension="do" />
    <performAction />
    <performScreen />
    </when>
    <otherwise>
    <!-- 將控制交還給servlet engine。 -->
    <exit />
    </otherwise>
    </choose>
    <!-- 假如rundata.setRedirectTarget()被設置,則循環,否則退出循環。 -->
    <breakUnlessTargetRedirected />
    </loop>
    </services:pipeline>
    </beans:beans>
    Filter、Request
    Contexts和Pipeline
    82
    6.3.3. Pipeline的使用
    6.3.3.1. 創建一個valve
    例 6.11. 一個簡單的valve實現
    public class MyValve implements Valve {
    public void invoke(PipelineContext pipelineContext) throws Exception {
    System.out.println("valve started.");
    pipelineContext.invokeNext(); // 調用後序valves
    System.out.println("valve ended.");
    }
    }
    配置(pipeline.xml)
    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    ...
    <valve class="com.alibaba.myapp.pipeline.MyValve" />
    ...
    </services:pipeline>
    上面的代碼和配置創建了一個基本的valve ── 事實上,它只是列印了一些消息,然後把控制
    權傳遞給後序的valves。
    6.3.3.2. 執行一個pipeline
    例 6.12. 在代碼中執行pipeline
    @Autowired
    private Pipeline myPipeline;
    public void invokePipeline() {
    PipelineInvocationHandle invocation = myPipeline.newInvocation();
    invocation.invoke();
    System.out.println(invocation.isFinished());
    System.out.println(invocation.isBroken());
    }
    從spring容器中取得一個pipeline對象以後(一般是通過注入取得),我們就可以執行它。上面
    代碼中,PipelineInvocationHandle對象代表此次執行pipeline的狀態。Pipeline執行結束以
    后,訪問invocation對象就可以了解到pipeline的執行情況 ── 正常結束還是被中斷?
    Pipeline對象是線程安全的,可被所有線程所共享。但PipelineInvocationHandle對象不是線
    程安全的,每次執行pipeline時,均需要取得新的invocation對象。
    6.3.3.3. 調用子流程
    Pipeline支持子流程。事實上,子流程不過是另一個pipeline對象而已。
    Filter、Request
    Contexts和Pipeline
    83
    圖 6.5. Pipeline和子流程
    子流程是從valve中發起的。下面的Valve代碼啟動了一個子流程。
    Filter、Request
    Contexts和Pipeline
    84
    例 6.13. 在valve中發起一個子流程
    public class MyNestableValve implements Valve {
    private Pipeline subPipeline;
    public void setSubPipeline(Pipeline subPipeline) {
    this.subPipeline = subPipeline;
    }
    public void invoke(PipelineContext pipelineContext) throws Exception {
    // 發起子流程,以當前流程的pipelineContext為參數
    PipelineInvocationHandle subInvocation = subPipeline.newInvocation(pipelineContext);
    subInvocation.invoke();
    System.out.println(subInvocation.isFinished());
    System.out.println(subInvocation.isBroken());
    pipelineContext.invokeNext(); // 別忘了調用後序的valves
    }
    }
    配置文件(pipeline.xml)
    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    ...
    <valve class="com.alibaba.myapp.pipeline.MyNestableValve" p:subPipeline-ref="subPipeline" />
    ...
    </services:pipeline>
    6.3.3.4. 中斷一個pipeline
    Pipeline可以被中斷。當有多級子pipeline時,你可以中斷到任何一級pipeline。
    例 6.14. 中斷一個pipeline
    pipelineContext.breakPipeline(0); // level=0,中斷當前pipeline
    pipelineContext.breakPipeline(1); // level=1,中斷上一級pipeline
    pipelineContext.breakPipeline("label"); // 中斷到指定label的上級pipeline
    // 以上調用相當於:
    pipelineContext.breakPipeline(pipelineContext.findLabel("label"));
    pipelineContext.breakPipeline(Pipeline.TOP_LABEL); // 終止所有pipelines
    Filter、Request
    Contexts和Pipeline
    85
    圖 6.6. 中斷一個pipeline
    6.3.3.5. 條件分支、循環
    條件分支和循環其實只不過是子流程的運用而已:
    條件分支根據一定的條件,來決定是否要執行子流程、執行哪一個子流程(多條件分支)。
    循環多次執行子流程。
    下面的valve將子流程執行了至多10遍。如果子流程內部中斷了流程,則循環終止。
    Filter、Request
    Contexts和Pipeline
    86
    例 6.15. 將子流程循環執行10次
    public class Loop10 implements Valve {
    private Pipeline loopBody;
    public void setLoopBody(Pipeline loopBody) {
    this.loopBody = loopBody;
    }
    public void invoke(PipelineContext pipelineContext) throws Exception {
    PipelineInvocationHandle handle = loopBody.newInvocation(pipelineContext);
    for (int i = 0; i < 10 && !handle.isBroken(); i++) {
    handle.invoke();
    }
    pipelineContext.invokeNext();
    }
    }
    6.3.3.6. 存取pipeline的狀態
    當一個pipeline在運行時,你可以通過PipelineContext取得一些上下文信息:
    例 6.16. 在valve中存取pipeline的狀態
    pipelineContext.index(); // 當前valve在pipeline中的序號
    pipelineContext.level(); // 當前pipeline在所有子pipeline中的級別
    pipelineContext.isBroken(); // 當前pipeline是否已經被中斷
    pipelineContext.isFinished(); // 當前pipeline的所有valves是否已經執行完
    // 存取任意數據
    pipelineContext.getAttribute(key);
    pipelineContext.setAttribute(key, value);
    6.3.3.7. 現成可用的valves
    一般情況下,你並不需要寫前面例子中的代碼,因為Webx已經為你提供了一系列現成的valves
    來實現同樣的功能。
    無條件循環 - <loop>
    例 6.17. 無條件循環
    <services:pipeline>
    <loop loopCounterName="count" maxLoopCount="10">
    <valve />
    <break-if test="..." />
    </loop>
    </services:pipeline>
    定義循環變數loopCounterName,這個變數值將被保存在PipelineContext中,且可
    被其它的valve所訪問。
    定義maxLoopCount=10最大循環圈數,以避免循環失控。
    無條件循環一定要和<break>、<break-if>或<break-unless>等valve相配合。
    Filter、Request
    Contexts和Pipeline
    87
    條件循環 - <while>
    例 6.18. 條件循環
    <services:pipeline>
    <while loopCounterName="count" test="count &lt;= 2">
    <valve />
    </while>
    <while maxLoopCount="10">
    <conditions:condition class="..." />
    <valve />
    </while>
    </services:pipeline>
    定義循環變數loopCounterName,這個變數值將被保存在PipelineContext中,且可
    被其它的valve所訪問。
    通過判斷循環變數「count <= 2」,循環2次。
    定義maxLoopCount=10,以避免循環失控。
    可以自定義任意條件。
    單條件分支 - <if>
    例 6.19. 單條件分支
    <services:pipeline>
    <if test="1 == 2">
    <valve />
    </if>
    <if>
    <conditions:condition class="..." />
    <valve />
    </if>
    </services:pipeline>
    JEXL條件表達式。
    自定義任意條件。
    多條件分支 - <choose><when><otherwise>
    例 6.20. 多條件分支
    <services:pipeline>
    <choose>
    <when test="1 == 2">
    <valve />
    </when>
    <when>
    <conditions:condition class="..." />
    <valve />
    </when>
    <otherwise>
    <valve />
    </otherwise>
    </choose>
    </services:pipeline>
    條件分支1,用JEXL表達式來判斷。
    Filter、Request
    Contexts和Pipeline
    88
    條件分支2,用任意條件判斷。
    分支3,當所有條件均不符合時,選擇該分支。
    無條件中斷 - <break>
    例 6.21. 無條件中斷
    <services:pipeline>
    <loop>
    <valve />
    <break />
    <valve />
    </loop>
    <loop>
    <valve />
    <loop>
    <break levels="1" />
    </loop>
    <valve />
    </loop>
    <loop label="MY_LOOP">
    <valve />
    <loop>
    <break toLabel="MY_LOOP" />
    </loop>
    <valve />
    </loop>
    </services:pipeline>
    無條件中止當前的pipeline(即loop循環)。
    無條件中止上一層(levels=1)的pipeline(即loop循環)。
    無條件中止指定label的pipeline(即loop循環)。
    有條件中斷 - <break-if>、<break-unless>
    有條件中斷是<break>和<if>的組合。
    Filter、Request
    Contexts和Pipeline
    89
    例 6.22. 有條件中斷
    <services:pipeline>
    <loop loopCounterName="count">
    <valve />
    <break-if test="count &gt; 2" />
    <valve />
    </loop>
    <loop label="MY_LOOP">
    <valve />
    <break-if toLabel="MY_LOOP">
    <conditions:condition class="..." />
    </break-if>
    <valve />
    </loop>
    <loop loopCounterName="count">
    <valve />
    <break-unless test="count &lt;= 2" />
    <valve />
    </loop>
    </services:pipeline>
    當count>2時中斷。
    <break-if>和<break-unless>均支持和<break>類似的其它選
    項:levels和toLabel。
    和<if>類似,也支持任意condition。
    <break-unless>和<break-if>的條件相反:除非count<=2,否則中斷。
    無條件退出整個pipeline - <exit>
    退出整個pipeline,意思是結束所有的嵌套層次。
    例 6.23. 無條件退出整個pipeline
    <services:pipeline>
    <loop>
    <valve />
    <loop>
    <exit />
    </loop>
    <valve />
    </loop>
    </services:pipeline>
    對於Webx而言,<exit>還有一層特殊的含義:放棄WebxFrameworkFilter的控制權,把
    它交還給servlet engine。以URL http://localhost:8081/myapp/myimage.jpg為例,把
    控制權交還給servlet engine,意味著讓servlet engine去顯示myapp應用目錄下的靜態圖
    片:myimage.jpg。
    異常捕獲和finally處理 - <try-catch-finally>
    類似Java中的try/catch/finally結構。
    Filter、Request
    Contexts和Pipeline
    90
    例 6.24. 異常捕獲和finally處理
    <services:pipeline>
    <try-catch-finally>
    <try>
    <valve />
    </try>
    <catch exceptionName="myexception">
    <valve />
    </catch>
    <finally>
    <valve />
    </finally>
    </try-catch-finally>
    </services:pipeline>
    <catch>標籤可以將捕獲的異常以指定名稱保存在PipelineContext中,以便其它
    valve取得。
    創建子流程 - <sub-pipeline>
    單純使用這個valve,對執行結果不會有任何影響。但可用來對較長的pipeline進行分段管
    理。
    例 6.25. 創建子流程
    <services:pipeline>
    <valve />
    <sub-pipeline label="mylabel">
    <valve />
    </sub-pipeline>
    <valve />
    </services:pipeline>
    6.3.3.8. 條件
    在前文所述的各種條件valve(例如<if>、<when>、<while>、<break-if>、<breakunless>
    等)中,都用到一個共同的對象:condition。Condition是一個簡單的介面。
    例 6.26. Condition介面
    public interface Condition {
    /**
    * 如滿足條件,則返回<code>true</code>。
    */
    boolean isSatisfied(PipelineStates pipelineStates);
    }
    為了方便起見,Webx默認提供了一個JexlCondtion。
    例 6.27. 使用JexlCondition
    <if>
    <conditions:jexl-condition expr="loopCount == 2" />
    <break />
    </if>
    以上配置可以簡化為:
    <if test="loopCount == 2">
    <break />
    </if>
    Filter、Request
    Contexts和Pipeline
    91
    JEXL表達式是Apache的一個小項目,表達式語法詳見:http://
    commons.apache.org/jexl/reference/syntax.html。在JEXL表達式中,你可以使
    用pipelineContext.getAttribute()所能取得的所有狀態值。例如,loop循環時,如果你設
    置了loopCounterName,那麼循環計數器就可以被JEXL表達式所訪問。
    除此之外,Webx還提供了三個組合式的條件。
    <all-of>
    要求所有條件均滿足,相當於Java中的&&操作符。
    例 6.28. 組合式的條件:<all-of>
    <all-of>
    <condition1 />
    <condition2 />
    <condition3 />
    </all-of>
    <any-of>
    只要求任一條件滿足,相當於Java中的||操作符。
    例 6.29. 組合式的條件:<any-of>
    <any-of>
    <condition1 />
    <condition2 />
    <condition3 />
    </any-of>
    <none-of>
    要求所有條件均不滿足,相當於Java中的!操作符。
    例 6.30. 組合式的條件:<none-of>
    <none-of>
    <condition1 />
    <condition2 />
    <condition3 />
    </none-of>
    這三個組合式條件可以互相組合,以構成任意複雜的條件判斷語句。
    6.4. 本章總結
    Request Contexts和Pipeline是Webx框架中的兩個核心服務。它們分別從兩個方面實現了原
    本需要由Filter來實現的功能 ── Request Contexts提供了包裝和修改request/response的
    機制,而pipeline則提供了流程式控制制的能力。Request contexts和pipeline組合起來的功能比
    servlet filter機制更加強大。因為它們是基於Spring的輕量組件,其性能、配置的方便性、擴展
    性都優於filter。
    當然,Request Contexts和Pipeline並不想取代filter。在好幾種場合,filter仍然是唯一的選
    擇:
    • 如果你既想要修改request/response,又想要控制流程;
    • 如果你希望獨立於任何框架。
    Filter、Request
    Contexts和Pipeline
    92
    但在你接到一個需求,正打算用filter來實現之前,請考慮一下,是否可以採用Webx所提供的這
    兩種機制來取代。倘若可行,必然會帶來更多的好處。
    93
    第 7 章 Request Contexts功能指南
    7.1. <basic> - 提供基礎特性 .......................................................................................... 94
    7.1.1. 攔截器介面 ................................................................................................... 94
    7.1.2. 默認攔截器 ................................................................................................... 95
    7.2. <set-locale> -設置locale區域和charset字符集編碼 ............................................... 95
    7.2.1. Locale基礎 .................................................................................................. 95
    7.2.2. Charset編碼基礎 .......................................................................................... 96
    7.2.3. Locale和charset的關係 ............................................................................... 97
    7.2.4. 設置locale和charset .................................................................................... 97
    7.2.5. 使用方法 ...................................................................................................... 98
    7.3. <parser> - 解析參數 ............................................................................................. 101
    7.3.1. 基本使用方法 .............................................................................................. 101
    7.3.2. 上傳文件 .................................................................................................... 102
    7.3.3. 高級選項 .................................................................................................... 104
    7.4. <buffered> - 緩存response中的內容 ..................................................................... 107
    7.4.1. 實現原理 .................................................................................................... 107
    7.4.2. 使用方法 .................................................................................................... 109
    7.5. <lazy-commit> - 延遲提交response ...................................................................... 111
    7.5.1. 什麼是提交 ................................................................................................. 111
    7.5.2. 實現原理 .................................................................................................... 111
    7.5.3. 使用方法 .................................................................................................... 112
    7.6. <rewrite> -重寫請求的URL和參數 .......................................................................... 112
    7.6.1. 概述 ........................................................................................................... 112
    7.6.2. 取得路徑 .................................................................................................... 114
    7.6.3. 匹配rules .................................................................................................... 114
    7.6.4. 匹配conditions ........................................................................................... 115
    7.6.5. 替換路徑 .................................................................................................... 117
    7.6.6. 替換參數 .................................................................................................... 117
    7.6.7. 後續操作 .................................................................................................... 118
    7.6.8. 重定向 ........................................................................................................ 119
    7.6.9. 自定義處理器 .............................................................................................. 120
    7.7. 本章總結 ............................................................................................................... 120
    在第 6 章 Filter、Request Contexts和Pipeline中,我們已經介紹了Request Contexts服務的
    作用和原理。本章我們將介紹除了session機制以外,每一個可用的Request Context的功能
    和用法。由於Session機制比較複雜,所以我們另闢單獨的一章(第 8 章 Request Context之
    Session指南)來解釋它。
    本章涉及的內容包括:
    名稱介面功能
    <basic> BasicRequestContext 提供基礎安全特性,例如:過濾response
    headers、cookies,限制cookie的大小等。
    <set-locale> SetLocaleRequestContext 設置locale區域和charset字符集編碼。
    <parser> ParserRequestContext 解析參數,支持multipart/form-data(即上傳文件請
    求)。
    Request Contexts功能指南
    94
    名稱介面功能
    <buffered> BufferedRequestContext 緩存response中的內容。
    <lazycommit>
    LazyCommitRequestContext 延遲提交response。
    <rewrite> RewriteRequestContext 重寫請求的URL和參數。
    7.1. <basic> - 提供基礎特性
    7.1.1. 攔截器介面
    BasicRequestContext提供了一組interceptors攔截器介面,通過它們,你可以攔截並干預一
    些事件。
    圖 7.1. BasicRequestContext所提供的攔截器
    你可以在<basic>中指定上圖所示的任何一個Interceptor介面,以便干預特定的事件:
    表 7.1. BasicRequestContext所提供的攔截器
    攔載器介面說明
    RequestContextLifecycleInterceptor 攔截「預處理(prepare)」和「提交(commit)」事件。
    ResponseHeaderInterceptor 攔截所有對response header的修改。
    ➥ HeaderNameInterceptor 攔截所有對header的修改、添加操作。可修改header name,
    或拒絕對header的修改。
    ➥ HeaderValueInterceptor 攔截所有對header的修改、添加操作。可修改header value,
    或拒絕對header的修改。
    ➥ CookieInterceptor
    攔截所有對cookie的添加操作。可修改或拒絕cookie對象。
    需要注意的是,有兩種方法可以添加cookie:通過cookie
    對象,或者直接寫response header。對於後者,需要使
    用CookieHeaderValueInterceptor才能攔截得到。
    ➥ CookieHeaderValueInterceptor 攔截所有通過添加header來創建cookie的操作。可修改或拒絕
    該cookie。
    ➥ RedirectLocaitonInterceptor 攔截所有外部重定向的操作。可修改或拒絕重定向URL。
    ➥ StatusMessageInterceptor 攔截所有設置status message的操作。可以修改或拒絕該
    message。
    Request Contexts功能指南
    95
    通過下面的配置,就可以指定任意多個interceptor的實現。
    例 7.1. 配置interceptors(/WEB-INF/webx.xml)
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <basic>
    <request-contexts:interceptors
    xmlns="http://www.alibaba.com/schema/services/request-contexts/basic/interceptors">
    <interceptor class="...Interceptor1" />
    <interceptor class="...Interceptor2" />
    </request-contexts:interceptors>
    </basic>
    ...
    </services:request-contexts>
    7.1.2. 默認攔截器
    即使你不加說明,BasicRequestContext也總是會啟用一個默認的interceptor實
    現:ResponseHeaderSecurityFilter。這個類實現了下列功能:
    • 避免header name和value中出現CRLF字元 ── 在header中嵌入CRLF(回車換行)字元
    是一種常見的攻擊手段。攻擊者嵌入CRLF以後,使伺服器對HTTP請求發生錯誤判斷,從而執
    行攻擊者的惡意代碼。事實上,現在的servlet引擎如tomcat已經可以防禦這種攻擊。但作為
    框架,並不能依賴於特定的servlet引擎,所以加上這個額外的安全檢查,確保萬無一失。
    • 將status message用HTML entity編碼重寫 ── 通常status message會被顯示在HTML頁面
    中。攻擊者可以利用這一點在頁面中嵌入惡意代碼。將status message以HTML entity編碼重
    寫以後,就可以避免這個問題。
    • 限制cookie的總大小 ── 過大的cookie可能使WEB伺服器拒絕響應請求。攻擊者同樣可以
    利用這一點使用戶無法正常訪問網站。限制cookie的總大小可以部分地解決這種危機。
    如果需要,你可以對ResponseHeaderSecurityFilter指定一些參數。
    例 7.2. 配置ResponseHeaderSecurityFilter(/WEB-INF/webx.xml)
    <request-contexts:interceptors
    xmlns="http://www.alibaba.com/schema/services/request-contexts/basic/interceptors">
    <interceptor class="...Interceptor1" />
    <interceptor class="...Interceptor2" />
    <response-header-security-filter maxSetCookieSize="5K" />
    </request-contexts:interceptors>
    7.2. <set-locale> -設置locale區域和charset字符集編碼
    區域和編碼問題(尤其是後者)是每個WEB應用都必須處理好的基本問題。它雖然本身並
    不複雜,但是在現實開發中,由於涉及面很廣,一旦發生問題(例如亂碼)經常讓人手足無
    措。<set-locale>提供了一個機制,確保Web應用能夠設置正確的區域和編碼。
    7.2.1. Locale基礎
    Locale是國際化的基礎。
    一個locale的格式是:language_country_variant,例
    如:zh_CN、zh_TW、en_US、es_ES_Traditional_WIN等。
    Request Contexts功能指南
    96
    Java和框架根據不同的locale,可以取得不同的文本、對象。下面的Java代碼根據不同的
    locale,取得不同語言版本的文字:
    例 7.3. 利用ResourceBundle和locale取得國際化字元
    Locale.setDefault(Locale.US);
    String s1 = getResourceBundle(Locale.CHINA).getString("happy"); // 快樂
    String s2 = getResourceBundle(Locale.TAIWAN).getString("happy"); // 快樂
    String s3 = getResourceBundle(Locale.US).getString("happy"); // happy
    ...
    ResourceBundle getResourceBundle(Locale locale) {
    return ResourceBundle.getBundle("ApplicationResources", locale);
    }
    其中所用到的ResourceBundle文件定義如下:
    ApplicationResources.properties happy = happy
    ApplicationResources_zh_CN.properties happy = \u5FEB\u4E50
    ApplicationResources_zh_TW.properties happy = \u5FEB\u6A02
    7.2.2. Charset編碼基礎
    Charset全稱Character Encoding或字符集編碼。Charset是將字元(characters)轉換
    成位元組(bytes)或者將位元組轉換成字元的演算法。Java內部採用unicode來表示一個字元。
    將unicode字元轉換成位元組的過程,稱為「編碼」;將位元組恢復成unicode字元的過程,稱
    為「解碼」。
    瀏覽器發送給WEB應用的request參數,是以位元組流的方式來表示的。Request參數必須經過解
    碼才能被Java程序所解讀。用來解碼request參數的charset被稱為「輸入字符集編碼(Input
    Charset)」;
    WEB應用返回給瀏覽器的response響應內容必須編碼成位元組流,才能被瀏覽器或客戶端解讀。
    用來編碼response內容的charset被稱為「輸出字符集編碼(Output Charset)」。
    一般情況下,input charset和output charset是相同的。因為瀏覽器發送表單數據時,總是采
    用當前頁面的charset來編碼的。例如,有一個表單頁面,它的「contentType=text/html;
    charset=GBK」,那麼用戶填完全表單並提交時,瀏覽器會以GBK來編碼用戶所輸入的表單數
    據。如果input charset和output charset不相同,伺服器就不能正確解碼瀏覽器根據output
    charset所發回給WEB應用的表單數據。
    然而有一些例外情況下面,輸入和輸出的charset可能會不同:
    • 通過Java Script發送的表單,總是用UTF-8編碼的。這意味著你必須用UTF-8作為input
    charset方能正確解碼參數。這樣,除非output charset也是UTF-8,否則兩者就是不同的。
    • 應用間互相用HTTP訪問時,可能採用不同的編碼。例如,應用A以UTF-8訪問應用B,而應用B
    是以GBK作為input/output charset的。此時會產生參數解碼的錯誤。
    • 直接在瀏覽器地址欄里輸入包含參數的URL,根據不同的瀏覽器和操作系統的設置,會有不同
    的結果:
    • 例如,中文Windows中,無論ie還是firefox,經試驗,默認都以GBK來編碼參數。IE對直接
    輸入的參數,連URL encoding也沒做。
    Request Contexts功能指南
    97
    • 而在mac系統中,無論safari還是firefox,經試驗,默認都是以UTF-8來編碼參數。
    框架必須要能夠應付上面各種不確定的charset編碼。
    7.2.3. Locale和charset的關係
    Locale和charset是相對獨立的兩個參數,但是又有一定的關係。
    Locale決定了要顯示的文字的語言,而charset則將這種語言的文字編碼成bytes或從bytes解
    碼成文字。因此,charset必須能夠涵蓋locale所代表的語言文字,如果不能,則可能出現亂
    碼。下表列舉了一些locale和charset的組合:
    表 7.2. Locale和Charset的關係
    英文字符集中文字符集全字符集
    Locale
    ISO-8859-1 GB2312 Big5 GBK GB18030 UTF-8
    en_US(美國英文) √ √ √ √ √ √
    zh_CN(簡體中文) √ √ √ √
    zh_TW、zh_HK(台灣中文、香港中文) √ √ √ √
    在所有charset中,有幾個「全能」編碼:
    UTF-8
    涵蓋了unicode中的所有字元。然而用UTF-8來編碼中文為主的頁面時,每個中文會佔用3
    個位元組。建議以非中文為主的頁面採用UTF-8編碼。
    GB18030
    中文國際標準,和UTF-8一樣,涵蓋了unicode中的所有字元。用GB18030來編碼中文
    為主的頁面時有一定優勢,因為絕大多數常用中文僅佔用2個位元組,比UTF-8短1/3。然
    而GB18030在非中文的操作系統中,有可能不能識別,其通用性不如UTF-8好。因此僅建議
    以中文為主的頁面採用GB18030編碼。
    GBK
    嚴格說,GBK不是全能編碼(例如對很多西歐字元就支持不好),也不是國際標準。但它支
    持的字元數量接近於GB18030。
    7.2.4. 設置locale和charset
    在Servlet API中,以下API是和locale和charset有關的。
    表 7.3. 和locale、charset相關的servlet API
    HttpServletRequest
    .getCharacterEncoding() 讀取輸入編碼
    .setCharacterEncoding(charset) 設置輸入編碼
    • 必須在第一次調
    用request.getParameter()
    和request.getParameterMap()前設
    置,否則無效。
    • 如果不設置,則默認以ISO-8859-1來解
    碼參數。
    Request Contexts功能指南
    98
    HttpServletRequest
    • 一般隻影響POST請求參數的解碼,
    但這裡有一些複雜性,參見第 7.3 節
    「<parser> - 解析參數」。
    .getLocale() 取得Accept-Language中
    瀏覽器首選的locale
    .getLocales()
    取得所有Accept-
    Language中所指定的
    locales
    HttpServletResponse
    .getCharacterEncoding() 取得輸出編碼
    .setCharacterEncoding(charset) 設置輸出編碼• Since Servlet 2.4
    .getContentType() 取得content type • Since Servlet 2.4
    .setContentType(contentType) 設置content type • Content type中可能包含charset定
    義,例如:text/html; charset=GBK
    .getLocale() 取得輸出locale
    .setLocale(locale) 設置輸出locale
    • 必須在response被commit之前調用,
    否則無效。
    • 它同時也會設置charset,除非content
    type已經被設置過,並用包含了
    charset的定義。
    設置locale和charset是一件看起來容易,做起來不容易的事:
    • 輸入編碼必須在第一個讀取request參數的調用之前設置好,否則就無效。只有把<setlocale>
    作為Request Contexts服務的一環,才有可能確保讀取request參數之前,設置好輸
    入編碼。
    • 在Servlet 2.3之前,設置輸出參數的唯一方法,是通過設置帶有charset定義的content
    type。這一點在Servlet 2.4以後得到改進,添加了獨立的設置輸出編碼的方法。<setlocale>
    彌補了Servlet 2.3和Servlet 2.4之間的差異,使WEB應用在所有的環境下,都可以獨
    立設置content type和charset。
    7.2.5. 使用方法
    7.2.5.1. 使用默認值
    例 7.4. 設置默認的locale和charset
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <set-locale defaultLocale="zh_CN" defaultCharset="GB18030" />
    ...
    </services:request-contexts>
    上面的配置,將WEB應用的輸入charset、輸出charset均設置成GB18030,將輸出locale設置
    成zh_CN。
    7.2.5.2. 臨時覆蓋默認的charset
    前面講到在一些情況下面,伺服器所收到的參數(表單數據)不是用應用默認的charset來編碼
    的。例如Java Script總是以UTF-8來提交表單;系統間通過HTTP協議通信;或者用戶直接在瀏
    覽器地址欄中輸入參數。
    Request Contexts功能指南
    99
    如何應付這些不確定的charset呢?<set-locale>提供的方法是,在URL中指定輸入編碼,並覆
    蓋默認值。
    假設當前應用的默認值是defaultLocale=zh_CN、defaultCharset=GB18030,那麼下面的請
    求將使用默認的GB18030來解碼參數,並用默認的GB18030來輸出頁面:
    http://localhost:8081/myapp/myform
    假如你希望改用UTF-8來解碼參數,那麼可以使用下面的URL來覆蓋默認值:
    例 7.5. 在URL中覆蓋默認的input charset
    http://localhost:8081/myapp/myform?_input_charset=UTF-8
    這樣,Webx將採用UTF-8來解碼參數,但仍然使用默認的GB18030來輸出頁面。
    需要注意的是,對於POST請求,你必須把_input_charset這個特殊的參數寫在URL中,而不能
    寫成普通的表單欄位,例如:
    例 7.6. 在POST表單中覆蓋默認的input charset
    <form action="http://localhost:8081/myapp/myform?_input_charset=UTF-8" method="POST">
    <input type="hidden" name="param1" value="value1"/>
    <input type="hidden" name="param2" value="value2"/>
    </form>
    必須把_input_charset這個特殊的參數寫在URL中,即便是POST類型的表單。
    在寫AJAX Java Script代碼時,也要注意:
    例 7.7. 在AJAX代碼中覆蓋默認的input charset
    var xhreq = new XMLHttpRequest();
    xhreq.open("post", "/myapp/myform?_input_charset=UTF-8", true);
    ...
    xhreq.send("a=1&b=2");
    必須把_input_charset這個特殊的參數寫在URL中。
    此外,<set-locale>也提供了臨時覆蓋輸出編碼的方法:
    例 7.8. 在URL中覆蓋默認的output charset
    http://localhost:8081/myapp/myform?_output_charset=UTF-8
    臨時覆蓋的輸入、輸出編碼只會影響當前請求,它不會被記住。當一個不帶有覆蓋參數的請求進
    來時,將仍然按照默認值來設置輸入、輸出編碼。
    7.2.5.3. 持久覆蓋默認的locale和charset
    還有一種需求,就是多語言網頁的支持。用戶可以選擇自己的語言:簡體中文、繁體中文
    等。一旦用戶作出選擇,那麼後續的網頁將全部以用戶所選擇的語言和編碼來顯示。<setRequest
    Contexts功能指南
    100
    locale>直接支持這個功能。只要你按下面的URL訪問頁面,用戶的語言和編碼即被切換成簡體
    中文和UTF-8編碼。
    例 7.9. 持久覆蓋默認的locale和charset
    http://localhost:8081/myapp?_lang=zh_CN:UTF-8
    參數值_lang=zh_CN:UTF-8將被保存在session中,後續的請求不需要再次指定_lang參數。用
    戶所作出的選擇將一直持續在整個session中,直到session被作廢。
    需要說明的是,假如我們採用了<session> request context來取代原來的session機制,那
    么該參數實際的保存位置將取決於session框架的設置 ── 例如:你可以把參數值保存在某
    個cookie中。然而,<set-locale>並不需要關心於session的實現細節或是用來保存參數的
    cookie的細節。
    7.2.5.4. <set-locale>的影響力
    <set-locale>所設置的輸出locale和輸出charset值將會被保存在當前線程中,從而對整個線
    程產生影響。
    表 7.4. 被<set-locale>影響的API
    API 說明
    LocaleUtil.getContext().getLocale()
    LocaleUtil.getContext().getCharset()
    可以通過這兩個方法取得當前線程的輸出locale和
    charset。Webx框架中凡是要用到默認locale和charset的地
    方,都會從這裡去取得值。
    StringEscapeUtil.escapeURL()
    StringEscapeUtil.unescapeURL()
    Webx調用這兩個方法進行URL編碼、解碼時,不需要指定
    charset(不同於JDK的URLEncoder/URLDecoder)。這兩個函
    數將從LocaleUtil.getContext().getCharset()中取得當前
    線程的輸出charset。
    TemplateService TemplateService如果指定
    了searchLocalizedTemplates=true參數,那麼它會利用
    當前線程的locale來搜索本地化的模板,例如: screen/
    myTemplate_zh_CN.vm
    7.2.5.5. <set-locale>的配置參數
    例 7.10. <set-locale>的配置參數
    <set-locale defaultLocale="..."
    defaultCharset="..."
    inputCharsetParam="_input_charset"
    outputCharsetParam="_output_charset"
    paramKey="_lang"
    sessionKey="_lang" />
    表 7.5. <set-locale>配置參數說明
    參數名說明
    defaultLocale 默認locale。
    defaultCharset 默認charset。
    inputCharsetParam 用來臨時改變輸入charset的參數名,支持多個名稱,以「|」分隔,例
    如「_input_charset|ie」。 默認值為「_input_charset」。
    outputCharsetParam 用來臨時改變輸出charset的參數名,支持多個名稱,以「|」分隔,例
    如「_output_charset|oe」。 默認為「_output_charset」。
    Request Contexts功能指南
    101
    參數名說明
    paramKey 用來持久改變輸出locale和charset的參數名,默認為「_lang」。
    sessionKey 用來在session中保存用戶所選擇的locale和charset的key,默認
    為「_lang」。
    7.3. <parser> - 解析參數
    7.3.1. 基本使用方法
    7.3.1.1. 基本配置
    例 7.11. <parser>基本配置
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <parser />
    ...
    </services:request-contexts>
    <services:upload sizeMax="5M" fileSizeMax="2M" />
    絕大多數情況,你只需要上面的配置就足夠了 ── <parser>會自動解析所有類型的請求,包
    括:
    • GET請求
    • 普通的POST請求(Content Type:application/x-www-form-urlencoded)
    • 可上傳文件的POST請求(Content Type:multipart/form-data)
    7.3.1.2. 通過HttpServletRequest介面訪問參數
    <parser>對於大部分應用是透明的。也就是說,你不需要知道<parser>的存在,就可以訪問所
    有的參數,包括訪問multipart/form-data請求的參數。
    例 7.12. 通過HttpServletRequest介面訪問參數
    @Autowired
    HttpServletRequest request;
    ...
    String s = request.getParameter("myparam");
    7.3.1.3. 通過ParserRequestContext介面訪問參數
    你也可以選擇使用ParserRequestContext介面。
    例 7.13. 通過ParserRequestContext介面訪問參數
    @Autowired
    ParserRequestContext parser;
    ...
    String s = parser.getParameters().getString("myparam");
    Request Contexts功能指南
    102
    和HttpServletRequest介面相比,ParserRequestContext提供了如下便利:
    直接取得指定類型的參數,例如:直接取得int、boolean值等。
    例 7.14. 直接取得指定類型的參數
    // myparam=true, myparam=false
    parser.getParameters().getBoolean("myparam");
    // myparam=123
    parser.getParameters().getInt("myparam");
    如果參數值未提供,或者值為空,則返回指定默認值。
    例 7.15. 取得參數的默認值
    parser.getParameters().getBoolean("myparam", false);
    parser.getParameters().getString("myparam", "no_value");
    parser.getParameters().getInt("myparam", -1);
    取得上傳文件的FileItem對象(這是Apache Jakarta 項目commons-fileupload所定義的接
    口)。
    例 7.16. 取得FileItem上傳文件
    FileItem fileItem = parser.getParameters().getFileItem("myfile");
    FileItem[] fileItems = parser.getParameters().getFileItems("myfile");
    ParserRequestContext還提供了比較方便的訪問cookie值的方法。
    例 7.17. 訪問cookie值
    parser.getCookies().getString("mycookie");
    7.3.2. 上傳文件
    用於上傳文件的請求是一種叫作multipart/form-data的特殊請求,它的格式類似於富文本電
    子郵件的樣子。下面HTML創建了一個支持上傳文件的表單:
    例 7.18. 創建multipart/form-data表單
    <form action="..." method="post" enctype="multipart/form-data">
    <input type="file" name="myfile" value="" />
    ...
    </form>
    提示:不是只有需要上傳文件時,才可以用multipart/form-data表單。假如你的表單中包含
    富文本欄位(即欄位的內容是以 HTML或類似的技術描述的),特別是當欄位的內容比較長的時
    候,用multipart/form-data比用普通的表單更高效,生成的HTTP請求也更短。
    只要upload服務存在,那麼<parser>就可以解析multipart/form-data(即上傳文件)的請
    求。Upload服務擴展於Apache Jakarta的一個項目:commons-fileupload。
    Request Contexts功能指南
    103
    7.3.2.1. 配置Upload服務
    例 7.19. Upload服務的配置參數
    <services:upload sizeMax="5M"
    fileSizeMax="2M"
    repository="/tmp"
    sizeThreshold="10K"
    keepFormFieldInMemory="true" />
    各參數的說明如下:
    表 7.6. Upload服務配置參數說明
    參數名稱說明
    sizeMax HTTP請求的最大尺寸(位元組,支持K/M/G),超過此尺寸的請求將被拋棄。
    值-1表示沒有限制。
    fileSizeMax 單個文件允許的最大尺寸(位元組,支持K/M/G),超過此尺寸的文件將被拋
    棄。值-1表示沒有限制。
    repository 暫存上傳文件的目錄。 注意,這個目錄是用Spring ResourceLoader裝載
    的,而不是一個物理路徑。關於ResourceLoader,詳見ResourceLoading
    服務的文檔。
    sizeThreshold 將文件放在內存中的閾值(位元組,支持K/M/G),小於此值的文件被保存在
    內存中。
    keepFormFieldInMemory 是否將普通的form field保持在內存里? 默認為false,但
    當sizeThreshold為0時,默認為true。
    注意
    當上傳文件的請求的總尺寸超過sizeMax的值時,整個請求將被拋棄 ——
    這意味著你不可能讀到請求中的其它任何參數。而當某個上傳文件的尺寸超
    出fileSizeMax的限制,但請求的總尺寸仍然在sizeMax的範圍內時,只有超出該
    尺寸的單個上傳文件被拋棄,而你還是可以讀到其餘的參數。
    假如有多個upload服務(當然這種情況極少),你也可以明確指定<parser>使用哪個upload
    服務:
    例 7.20. 明確指定upload服務
    <parser uploadServiceRef="myUpload" />
    7.3.2.2. 手工解析上傳請求
    在默認情況下,當<parser>收到一個上傳文件的請求時,會立即解析並取得所有的參數和文
    件。然而你可以延遲這個過程,在需要的時候,再手工解析上傳請求。
    例 7.21. 手工解析upload請求
    首先,你需要關閉自動上傳
    <parser autoUpload="false">
    可選參數autoUpload默認值為true,當你把它改成false時,就可以實現延遲手工解析請求。
    在你需要解析請求時,只需要調用下面的語句即可:
    parser.getParameters().parseUpload();
    Request Contexts功能指南
    104
    手工調用parseUpload可以指定和默認不同的參數:
    UploadParameters params = new UploadParameters();
    params.applyDefaultValues();
    params.setSizeMax(new HumanReadableSize("10M"));
    params.setFileSizeMax(new HumanReadableSize("1M"));
    params.setRepository(new File("mydir"));
    parser.getParameters().parseUpload(params);
    7.3.3. 高級選項
    7.3.3.1. 參數名稱大小寫轉換
    在默認情況下,假設有一個參數名為:myProductId,那麼你可以使用下列任意一種方法來訪
    問到它:
    例 7.22. 取得參數myProductId的值的方法
    request.getParameter("MyProductId");
    request.getParameter("myProductId");
    request.getParameter("my_product_id");
    request.getParameter("MY_PRODUCT_ID");
    request.getParameter("MY_productID");
    假如你不希望具備這種靈活性,則需要修改配置以關閉大小寫轉換功能:
    例 7.23. 關閉大小寫轉換功能
    <parser caseFolding="none">
    7.3.3.2. 參數值去空白
    在默認情況下,假設有一個參數:id=" 123 "(兩端有空白字元),那麼<parser>會把它轉化
    成"123"(兩端沒有空白字元)。 假如你不希望<parser>做這件事,則需要修改配置:
    例 7.24. 關閉參數值去空白功能
    <parser trimming="false">
    這樣,所有的參數值將會保持原狀,不會被去除空白。
    7.3.3.3. 參數值entity解碼
    瀏覽器在提交表單時,如果發現被提交的字元不能以當前的charset來編碼,瀏覽器就會把該字
    符轉換成&#unicode;這樣的形式。例如,假設一個表單頁面的content type為:text/html;
    charset=ISO-8859-1。在這個頁面的輸入框中輸入漢字「你好」,然後提交。你會發現,提交
    的漢字變成了這個樣子:param="&#20320;&#22909;"。
    在默認情況下,<parser>會對上述參數進行entity解碼,使之恢復成「你好」。但是,其它的
    entity如「&lt;」、「&amp;」等並不會被轉換。 如果你不希望<parser>還原上述內容,則需
    要修改配置:
    例 7.25. 關閉參數值entity解碼功能
    <parser unescapeParameters="false">
    Request Contexts功能指南
    105
    7.3.3.4. 取得任意類型的參數值
    前面提到,ParserRequestContext支持直接取得boolean、int等類型的參數值。事實上,它
    還支持取得任意類型的參數值 —— 只要Spring中有相應的PropertyEditor支持即可。
    假設MyEnum是一個enum類型,這是Spring原生支持的一種類型。你可以用下面的代碼來取得
    它:
    例 7.26. 將參數值轉換成enum類型
    MyEnum myEnum = params.getObjectOfType("myparam", MyEnum.class);
    但是,下面的語句就不是那麼順利了 —— 因為Spring不知道怎麼把一個參數值,例
    如:「1975-12-15」,轉換成java.util.Date類型。
    例 7.27. 將參數值轉換成java.util.Date類型
    Date birthday = params.getObjectOfType("birthday", Date.class);
    好在<parser>提供了一種擴展機制,可以添加新的類型轉換機制。對於Date類型,你只需要添
    加下面的配置,就可以被支持了。
    <parser>
    <property-editor-registrar
    class="com.alibaba.citrus.service.configuration.support.CustomDateRegistrar"
    p:format="yyyy-MM-dd" p:locale="zh_CN" p:timeZone="GMT+8" />
    </parser>
    PropertyEditorRegistrar是Spring提供的一種類型註冊機制,其細節詳見Spring的文
    檔。
    另一個問題是,如果類型轉換失敗怎麼辦?<parser>支持兩種方法。默認情況下,類型轉換失
    敗會「保持安靜」(不拋異常),然後返回默認值。但你也可以選擇讓類型轉換失敗的異常被拋
    出來,以便應用程序處理。
    例 7.28. 設置「非安靜」模式:當類型轉換失敗時,拋出異常
    <parser converterQuiet="false">
    程序里這樣寫:
    MyEnum myEnum = null;
    try {
    myEnum = params.getObjectOfType("myparam", MyEnum.class);
    } catch (TypeMismatchException e) {
    ...
    }
    7.3.3.5. 解析GET請求的參數
    GET請求是最簡單的請求方式。它的參數以URL編碼的方式包含在URL中。當你在瀏覽器
    地址欄中敲入「http://localhost:8081/user/login.htm?name=%E5%90%8D%E5%AD
    %97&password=password」這樣一個址址的時候,瀏覽器就會向localhost:8081伺服器出如
    下HTTP請求:
    GET /user/login.htm?name=%E5%90%8D%E5%AD%97&password=password HTTP/1.1
    Host: localhost:8081
    Request Contexts功能指南
    106
    GET請求中的參數是以application/x-www-form-urlencoded方式和特定的charset編碼的。
    假如用來編碼URL參數的charset與應用的默認charset不同,那麼你必須通過特殊的參數來指定
    charset(參見第 7.2 節 「<set-locale> -設置locale區域和charset字符集編碼」):
    GET /user/login.htm?_input_charset=UTF-8&name=%E5%90%8D%E5%AD%97&password=password HTTP/1.1
    可是,上面的請求在不同的Servlet引擎中,會產生不確定的結果。這是怎麼回事呢?
    原來,儘管<set-locale>會調用request.setCharacterEncoding(charset)這個方法來設
    置input charset編碼,然而根據Servlet API的規範,這個設定只能對request content生效,
    而不對URL生效。換句話說,request.setCharacterEncoding(charset)方法只能用來解析
    POST請求的參數,而不是GET請求的參數。
    那麼,應該怎樣處理GET請求的參數呢?根據URL規範,URL中非US-ASCII的字元必須進行基
    於UTF-8的URL編碼。然而實際上,從瀏覽器到伺服器,沒有人完全遵守這些規範,於是便造成
    了一些混亂。目前應用伺服器端,我們所遇到的,有下面幾種不同的解碼方案:
    表 7.7. 伺服器對參數進行解碼的邏輯
    伺服器解碼的邏輯
    Tomcat 4 • 根據request.setCharacterEncoding(charset)所
    設置的值來解碼GET參數;
    • 如果未特別指定charset,則默認採用ISO-8859-1來
    解碼參數。
    Tomcat 5及更新版 以及搭載Tomcat 5以上版本的JBoss • 如果Tomcat配置文件conf/server.xml中設置了:
    <Connector useBodyEncodingForURI="true">那麼
    根據request.setCharacterEncoding(charset)所
    設置的值來解碼GET參數。
    • 如未設置useBodyEncodingForURI,或其值
    為false,則根據conf/server.xml中的配
    置<Connector URIEncoding="xxx">所指定的編碼,
    來解碼GET請求的參數。
    • 如未配置URIEncoding,默認採用ISO-8859-1。
    Jetty Server • Jetty總是以UTF-8來解碼GET請求的參數。
    綜上所述,所有的應用伺服器對於POST請求的參數的處理方法是沒有差別的,然而對於GET請
    求的參數處理方法各有不同。
    如果不加任何特別的設置,Tomcat最新版是以ISO-8859-1來解碼GET請求的參數,而Jetty卻
    是以UTF-8來解碼的。因此,無論你以哪一種charset來編碼GET請求的參數,都不可能在所有
    伺服器上取得相同的結果 ── 除非修改伺服器的配置,但這是一件既麻煩又容易出錯的事情。
    為了使應用程序對伺服器的配置依賴較少,且可以靈活地處理GET請求的解碼,<parser>對
    GET請求進行了手工解碼,從而解決了應用伺服器解碼的不確定性。
    <parser>完全解決了上面的問題。依據默認值,<parser>會以<set-locale>中設定的
    input charset為準,來解碼所有類型的請求,包括GET和POST請求,以及multipart/formdata(
    上傳文件)類型的請求。
    然而<parser>仍保留了一些可選方案,以備不時之需。
    保留Servlet引擎的解碼機制
    例 7.29. 使用Servlet引擎原來的解碼機制
    <parser useServletEngineParser="true" />
    Request Contexts功能指南
    107
    這個選項在用HttpUnit進行單元測試時非常有用。因為HttpUnit單元測試工具並沒有完
    全遵循Servlet API的規範 ── 目前版本的HttpUnit不能正確取得query string,從而導
    致<parser>解析GET參數錯誤。
    使用固定的charset來解碼GET請求
    例 7.30. 使用固定的charset來解碼GET請求
    <parser URIEncoding="UTF-8" useBodyEncodingForURI="false" />
    上面的配置強制所有的GET請求均使用UTF-8作為固定的charset編碼。這段邏輯和tomcat
    的完全相同,但你卻不需要去修改tomcat的conf/server.xml就可以實現上面的邏輯。 事
    實上,使用固定的charset來解碼GET請求的參數是符合Servlet API規範以及URL的規範的。
    而根據情況設置charset是一種對現實的妥協。然而你有選擇的自由 ── 無論你選擇何種風
    格,<parser>都支持你。
    7.3.3.6. 過濾參數
    出於安全的考慮,<parser>還支持對輸入參數進行過濾。請看示例:
    例 7.31. 配置過濾參數
    <parser>
    <filters>
    <parser-filters:uploaded-file-whitelist extensions="jpg, gif, png" />
    </filters>
    </parser>
    上面的配置將會禁止文件名後綴不在列表中的文件被上傳到伺服器上。如果做得更好一點,你甚
    至可以對上傳文件進行病毒掃描。
    目前,<parser>支持兩種過濾器介面:ParameterValueFilter和UploadedFileFilter。前
    者用來對普通的參數值進行過濾(例如排除可能造成攻擊的HTML代碼);後者用來對上傳文件
    的file item對象進行過濾,就像剛才的uploaded-file-whitelist的例子。
    7.4. <buffered> - 緩存response中的內容
    7.4.1. 實現原理
    Webx Turbine支持用layout/screen/control等部件共同購成一個頁面。其中,每個layout可
    包含一個screen和多個control,每個screen可包含多個control,每個control還可以再包含其
    它的control。Screen和control的內容都可以用程序代碼直接生成:
    例 7.32. 在Screen中直接輸出頁面內容
    public class MyScreenOrControl {
    @Autowired
    private HttpServletResponse response;
    public void execute() throws IOException {
    PrintWriter out = response.getWriter();
    out.println("<p>hello world</p>");
    }
    }
    Request Contexts功能指南
    108
    上面的代碼是非常直觀、易理解的。事實上,如果你寫一個簡單的servlet來生成頁面,代碼也
    是和上面的類似。
    但是,在簡單的代碼後面有一個玄機 —— 那就是這段代碼可被用於生成嵌套的頁面部件,它
    所生成的內容可被上一層嵌套的部件所利用。例如,一個screen中包含了一個control,那麼
    screen可以獲得它所調用的control的完整的渲染內容。
    這個玄機就是靠<buffered>來實現的。<buffered>改變了response的輸出流,包括output
    stream(二進位流)和writer(文本流),使寫到輸出流中的內容被暫存在內存中。當需要時,
    可以取得緩存中的所有內容。
    圖 7.2. Webx利用<buffered>機制生成嵌套式頁面的過程
    如圖所示。BufferedRequestContext主要包括了兩條用來操作buffer棧的指令:push和
    pop。
    • 每次push就會在棧頂創建一個新的buffer。
    Request Contexts功能指南
    109
    • 每次pop就會彈出棧頂buffer,並返回其內容。當最後一個buffer被彈出時,就會自動push
    一個新的buffer,從而確保任何時候棧都非空。
    • 所有寫入response.getWriter()和response.getOutputStream()輸出流的數據,將被保
    存在棧頂的buffer中。
    • Push和pop必須成對出現。如果在commit時發現棧內有兩個或兩個以上的buffer存在,說明
    有push/pop未匹配,則報錯。
    • Commit時,將僅存的棧頂buffer提交給瀏覽器。
    <buffered>還有一個重要的作用,就是可以用來支持基於cookie的session機制(參
    見:第 8 章 Request Context之Session指南)。因為cookie是response header的一部分,
    根據HTTP協議,headers出現在content的前面。一旦content開始向瀏覽器輸出,headers就
    不可能再被改變了。這會導致基於cookie的session無法保存的問題。<buffered>將所有的輸
    出內容緩存在內存中,從而避免了response過早地提交給瀏覽器,也就解決了cookie無法保存
    的問題。
    7.4.2. 使用方法
    7.4.2.1. 配置
    <buffered>的配置比較簡單,沒有任何額外的參數。只要像下面這樣寫就可以了:
    例 7.33. 配置<buffered>(/WEB-INF/webx.xml)
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <buffered />
    ...
    </services:request-contexts>
    7.4.2.2. 操作buffer棧
    例 7.34. 操作buffer棧
    @Autowired
    BufferedRequestContext buffered;
    @Autowired
    HttpServletResponse response;
    ...
    PrintWriter out = response.getWriter();
    buffered.pushBuffer(); // 創建新buffer,並壓入棧頂
    out.print("world"); // 在新buffer中寫入
    String content = buffered.popCharBuffer(); // 彈出頂層buffer
    out.print("hello, ");
    out.print(content); // 寫入較低層的buffer
    需要注意的是,response中有兩種輸出流:二進位流response.getOutputStream()和文本
    流response.getWriter()。與之對應的,BufferedRequestContext也會創建兩種類型的
    buffer。這兩種buffer類型是互斥的:
    Request Contexts功能指南
    110
    • 假如你的應用使用了response.getWriter(),那麼,你必須使
    用buffered.popCharBuffer()以取得文本buffer的內容;
    • 假如你的應用使用了response.getOutputStream(),那麼,你必須使
    用buffered.popByteBuffer()以取得二進位buffer的內容。
    • 如果用錯,則拋IllegalStateException。
    7.4.2.3. 關閉buffer機制
    Buffer機制會延遲伺服器對用戶的響應。在大部分情況下,這不會造成明顯的問題。但在某些情
    況下會產生嚴重的問題。此時,你需要把buffer機制關閉。
    例如,動態生成excel文件、PDF文件以及圖片文件。這樣的需求有如下特點:
    • 數據量大 —— 有可能達到幾兆。如果把這樣大的數據放在內存中,勢必導致伺服器性能的下
    降。
    • 沒有layout/screen/control這樣的嵌套頁面的需求,因此不需要buffer這樣的機制來幫倒
    忙。
    • 無狀態,不需要修改session,因此也不需要buffer機制來幫助延遲提交。反過來,對於這樣
    的大文件,提交越早越好 —— 甚至可以在文檔還未完全生成的時候,就開始向用戶瀏覽器輸
    出,邊生成邊下載,從而節省大量的下載時間。
    下面的程序代碼模擬了一種情況 —— 生成一個120M的PDF文件。每生成1M內容,就故意暫停
    半秒。這樣一來,120M的文件需要大約一分鐘才能生成完畢。
    例 7.35. 模擬生成PDF文檔,關閉buffer以提高性能
    public class MyDocument {
    @Autowired
    private BufferedRequestContext buffered;
    @Autowired
    private HttpServletResponse response;
    public void execute() throws Exception {
    buffered.setBuffering(false);
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition", "attachment; filename=\"mydocument.pdf\"");
    OutputStream out = response.getOutputStream();
    for (int m = 0; m < 120; m++) {
    for (int k = 0; k < 1024; k++) {
    for (int b = 0; b < 1024; b++) {
    out.write((byte) b);
    }
    }
    // 每生成1M,暫停半秒
    Thread.sleep(500);
    }
    }
    }
    把上述類代碼,放在screen目錄中。然後訪問URL:http://localhost:8081/myapp/
    my_document.do,就可以啟動下載。
    Request Contexts功能指南
    111
    假如不關閉buffer機制,從用戶點擊下載,到瀏覽器提示保存文件,中間會相隔一分鐘。這種用
    戶體驗是不可接受的。更糟糕的是,文件會佔用至少120M的伺服器內存,這也是幾乎不可接受
    的。關閉buffer機制以後,以上兩個問題就沒有了:
    • 用戶點擊下載鏈接,瀏覽器立即提示保存文件。
    • 邊下載邊生成數據,生成數據的時間是一分鐘,下載所需的時間也是一分鐘左右。
    • 生成的數據立即輸出,不會佔用過多的內存。
    7.5. <lazy-commit> - 延遲提交response
    7.5.1. 什麼是提交
    當瀏覽器向伺服器發出請求,伺服器就會返回一個response響應。每個response分成兩部分:
    headers和content。下面是一個HTTP響應的例子:
    例 7.36. HTTP請求的headers和content
    HTTP/1.0 200 OK
    Date: Sat, 08 Jan 2011 23:19:52 GMT
    Server: Apache/2.0.63 (Unix)
    ...
    <html>...
    在伺服器應用響應request的全過程中,都可以向瀏覽器輸出response的內容。然而,已經輸出
    到瀏覽器上的內容,是不可更改的;還沒有輸出的內容,還有改變的餘地。這個輸出的過程,被
    稱為提交(commit)。
    Servlet API中有一個方法,可以判定當前的response是否已經被提交。
    例 7.37. 判斷response是否已經被提交
    if (response.isCommitted()) {
    ...
    }
    在Servlet API中,有下列操作可能導致response被提交:
    • response.sendError()
    • response.sendRedirect()
    • response.flushBuffer()
    • response.setContentLength() 或者response.setHeader("Content-Length",
    length)
    • response輸出流被寫入並達到內部buffer的最大值(例如:8KB)
    7.5.2. 實現原理
    當response被提交以後,一切headers都不可再改變。這對於某些應用(例如cookie-based
    session)的實現是一個問題。
    Request Contexts功能指南
    112
    <lazy-commit>通過攔截response中的某些方法,來將可能導致提交的操作延遲到請求處理結
    束的時候,也就是request context本身被提交的時候。
    <lazy-commit>必須和<buffered>配合,才能完全實現延遲提交。如前所述,<buffered>將
    所有的輸出暫存在內存里,從而避免了因輸出流達到內部buffer的最大值(例如:8KB)而引起
    的提交。
    7.5.3. 使用方法
    7.5.3.1. 配置
    <lazy-commit>的配置比較簡單,沒有任何額外的參數。只要像下面這樣寫就可以了:
    例 7.38. 配置<lazy-commit>(/WEB-INF/webx.xml)
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <lazy-commit />
    ...
    </services:request-contexts>
    7.5.3.2. 取得當前response的狀態
    通過LazyCommitRequestContext介面,你可以訪問當前response的一些狀態:
    表 7.8. 通過LazyCommitRequestContext訪問response狀態
    LazyCommitRequestContext方法名說明
    isError() 判斷當前請求是否已出錯
    getErrorStatus() 如果sendError()方法曾被調用,則該方法返回一個error狀態值。
    getErrorMessage() 如果sendError()方法曾被調用,則該方法返回一個error信息。
    isRedirected() 判斷當前請求是否已被重定向。
    getRedirectLocation() 取得重定向的URI。
    getStatus() 取得最近設置的HTTP status
    7.6. <rewrite> -重寫請求的URL和參數
    7.6.1. 概述
    <rewrite>的功能和設計完全類似於Apache HTTPD Server所提供的mod_rewrite模塊。它可以
    根據規則,在運行時修改URL和參數。
    Request Contexts功能指南
    113
    圖 7.3. Rewrite工作原理
    當一個請求進入<rewrite>以後,它的處理過程如上圖所示。過程可分為兩個大的步驟,即:匹
    配和執行。
    • 匹配
    1. 取得URL中的path路徑。
    2. 用所取得的path,依次匹配rule1、rule2、rule3中的pattern,直到找到第一個匹配。
    3. 假如rule中包含conditions,則測試conditions。如果condtions不滿足,則當前的rule匹
    配失敗,回到第2步,繼續匹配下一個rules。
    4. 假如rule不包含conditions,或者conditions被滿足,則當前的rule匹配成功,進入「執
    行」階段。
    Request Contexts功能指南
    114
    • 執行
    1. 執行substitution替換。這可能導致path和參數的改變。
    2. 執行所有的handlers。這為編程者提供了更靈活的手段來改變request中的數據。
    3. 根據substitution中的指示,結束<rewrite>的執行、或者回到匹配階段,用新的path和參
    數繼續匹配後續的rules。
    4. <rewrite>結束時,根據substitution中的指示,改寫request或者重定向到新的URL。
    下面是一個<rewrite>配置的模板:
    例 7.39. 配置<rewrite>(/WEB-INF/webx.xml)
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <rewrite>
    <!-- rule 1 -->
    <rule pattern="...">
    <condition test="..." pattern="..." flags="..." />
    <condition test="..." pattern="..." flags="..." />
    <substitution uri="..." flags="...">
    <parameter key="..." value="..." />
    <parameter key="..." value="..." />
    <parameter key="..." value="..." />
    </substitution>
    <handlers>
    <rewrite-handlers:handler class="..." />
    </handlers>
    </rule>
    <!-- rule 2 -->
    <rule pattern="...">
    </rule>
    <!-- rule 3 -->
    <rule pattern="...">
    </rule>
    </rewrite>
    ...
    </services:request-contexts>
    7.6.2. 取得路徑
    和Apache mod_rewrite不同,用來匹配rules的路徑並不是URL的整個路徑,而是
    由servletPath + pathInfo兩部分組成,其中並不包含contextPath。
    這是因為<rewrite>是屬於WEB應用的,它只能匹配當前應用中的路徑。在基於servlet的WEB
    應用中,一個完整的URL路徑是由contextPath + servletPath + pathInfo三部分組成的。
    其中contextPath是用來區分應用的,所以對<rewrite>沒有意義。
    例如,URL是http://localhost:8081/myapp/myservlet/path/path,那麼<rewrite>用來
    匹配rules的路徑是:/myservlet/path/path。
    7.6.3. 匹配rules
    下面是一個簡單的rule。
    Request Contexts功能指南
    115
    例 7.40. 匹配規則的配置
    <rule pattern="/test1/hello\.htm">
    ...
    </rule>
    其中,rule pattern是一個正則表達式。特別需要注意的是,這個正則表達式是部分匹配的。如
    上例pattern可以匹配下面的路徑:
    • /test1/hello.htm
    • /mypath/test1/hello.htm
    • /mypath/test1/hello.htm/mypath
    如果你希望匹配整個path,請使用正則表達式的「^」和「$」標記。例如:
    例 7.41. 匹配整個path
    <rule pattern="^/test1/hello\.htm$">
    部分匹配的正則表達式為你提供了較靈活的匹配能力,例如,下面的rule可以用來匹配所有以
    jpg為後綴的URL。
    例 7.42. 後綴匹配
    <rule pattern="\.jpg$">
    此外,rules pattern還支持否定的pattern —— 即在正常的pattern前加上「!」即可。例如下
    面的rule匹配所有不以jpg為後綴的URL:
    例 7.43. 否定匹配
    <rule pattern="!\.jpg$">
    7.6.4. 匹配conditions
    每個rule都可以包含多個額外的conditions。Conditions提供了除path匹配以外的其它條件。
    下面是condition配置的基本格式:
    例 7.44. 配置conditions
    <rule pattern="/path">
    <condition test="..." pattern="..." flags="..." />
    <condition test="..." pattern="..." flags="..." />
    <condition test="..." pattern="..." flags="..." />
    ...
    </rule>
    每個condition由兩個主要的參數:測試表達式和pattern。測試表達式中可以使用下面的變
    量:
    表 7.9. Condition變數
    客戶端信息
    %{REMOTE_HOST} 客戶端主機名。相當於request.getRemoteHost()
    %{REMOTE_ADDR} 客戶端地址。相當於request.getRemoteAddr()
    %{REMOTE_USER} 用戶名。相當於request.getRemoteUser()
    %{AUTH_TYPE} 驗證用戶的方法。例如
    BASIC、FORM、CLIENT_CERT、DIGEST
    等。
    相當於request.getAuthType()
    Request Contexts功能指南
    116
    服務端信息
    %{SERVER_NAME} 伺服器主機名。相當於request.getServerName()
    %{SERVER_PORT} 伺服器埠。相當於request.getServerPort()
    %{SERVER_PROTOCOL} 伺服器協議。相當於request.getProtocol()
    請求信息
    %{REQUEST_METHOD} HTTP方法名。例如GET、POST等。相當於request.getMethod()
    %{REQUEST_URI} 所請求的URI,不包括主機名、埠和參
    數。
    相當於request.getRequestURI()
    %{QUERY_STRING} 參數和值。注意,對於POST請求取得
    QUERY_STRING,可能會影響性能。
    相當於request.getQueryString()
    %{QUERY:param} 取得參數值。無論哪一種類型的請求
    (GET/POST/上傳文件),都可以取得參數
    值。
    相當
    於request.getParameter("param")
    HTTP headers
    %{HTTP_USER_AGENT} 瀏覽器名稱。相當於request.getHeader("User-
    Agent")
    %{HTTP_REFERER} 前一個URL。相當
    於request.getHeader("Referer")
    %{HTTP_HOST} HTTP請求中的主機名,一般代表虛擬主機。相當於request.getHeader("Host")
    %{HTTP_ACCEPT} 瀏覽器可以接受的文檔類型。相當
    於request.getHeader("Accept")
    %{HTTP_COOKIE} 瀏覽器發送過來的cookie。相當
    於request.getHeader("Cookie")
    Condition pattern和rule pattern類似,也是部分匹配的正則表達式,並且支持否定的
    pattern。舉例說明:
    例 7.45. Condition patterns
    <rule pattern="/path">
    <condition test="%{SERVER_NAME}:%{SERVER_PORT}" pattern="www.(\w+).com:8080" />
    <condition test="%{QUERY:x}" pattern="!1" />
    <condition test="%{QUERY:y}" pattern="2" />
    </rule>
    上面的rule匹配符合以下條件的請求:
    匹配路徑/path。
    伺服器名為www.*.com,埠為8080。
    並且參數x!=1
    並且參數y=2
    默認情況下,必須所有的conditions條件都符合,rule才會繼續執行下去。但是condition還支
    持一個選項:OR或者ornext。如果condtion帶有這個選項,只要符合當前condition或者後續
    的conditions,rule就會執行下去。例如:
    例 7.46. 部分匹配conditions
    <rule pattern="/path">
    <condition test="%{QUERY:x}" pattern="1" flags="OR" />
    <condition test="%{QUERY:y}" pattern="2" flags="ornext" />
    <condition test="%{QUERY:z}" pattern="3" />
    </rule>
    Request Contexts功能指南
    117
    上例中,「OR」和「ornext」代表完全一樣的意思。這個rule匹配符合以下條件的請求:
    匹配路徑/path。
    參數x=1,
    或者y=2,
    或者z=3。
    7.6.5. 替換路徑
    當路徑匹配,並且conditions也匹配(如果有的話),那麼<rewrite>就會執行所匹配的rule。
    例 7.47. 替換路徑
    <rule pattern="/test1/hello\.htm">
    <substitution uri="/test1/new_hello\.htm" />
    </rule>
    上例中的rule將執行下面的替換(別忘了,rule支持部分匹配,只有匹配的部分被替換):
    • 將/test1/hello.htm替換成/test1/new_hello.htm。
    • 將/mypath/test1/hello.htm替換成/mypath/test1/new_hello.htm。
    • 將/mypath/test1/hello.htm/mypath替換成/mypath/test1/new_hello.htm/mypath。
    路徑替換時,還支持正則表達式變數。例如:
    例 7.48. 用正則表達式變數替換路徑
    <rule pattern="/(\w+)\.htm">
    <condition test="%{SERVER_NAME}" pattern="(\w+).blogs.com" />
    <substitution uri="/%1/new_$1\.htm" />
    </rule>
    需要注意的是,rule pattern中的匹配項,是用「$1」、「$2」、「$3」表示的;而condition
    pattern中的匹配項,是用「%1」、「%2」、「%3」表示的。只有最後一個被匹配的condition
    中的匹配項,才被保留用於替換。
    上面的rule將執行下面的替換:將http://myname.blogs.com/hello.htm替換成同伺服器上的
    路徑:/myname/new_hello.htm。
    7.6.6. 替換參數
    <rewrite>不僅可以替換路徑,還可以替換參數。
    例 7.49. 替換參數
    <rule pattern="/hello.(\w+)">
    <condition test="%{SERVER_NAME}" pattern="www.(\w+).com" />
    <substitution>
    <parameter key="ext" value="$1" />
    <parameter key="host" value="%1" />
    <parameter key="count">
    <value>1</value>
    <value>2</value>
    <value>3</value>
    </parameter>
    </substitution>
    </rule>
    Request Contexts功能指南
    118
    替換參數和替換路徑類似,也可以指定rule和condition pattern中的匹配項。參數支持多值,
    例如上例中的count參數。 上面的例子將執行以下替換行為:
    • 對於請求:http://www.myserver.com/hello.htm,不改變其路徑,只改變其參數:
    • 創建單值參數:ext=htm(從rule pattern中取得$1)
    • 創建單值參數:host=myserver(從condition pattern中取得%1)
    • 創建多值參數:count=[1, 2, 3]
    • 刪除其它所有參數。
    如果你想保留原來所有參數,只是修改或添加一些參數,可以指定QSA或qsappend選項。
    例 7.50. 保留原來的參數
    <substitution flags="QSA">
    ...
    </substitution>
    7.6.7. 後續操作
    當一個rule和其中的conditions被匹配時,<rewrite>就會執行這個rule。執行的結果通常是改
    變請求的路徑或參數。當一個rule執行完畢以後,接下來做什麼呢?有幾種可能的情況。
    7.6.7.1. 繼續匹配剩餘的rules
    例 7.51. 默認後續操作:繼續匹配剩餘的rules
    <rule pattern="...">
    <substitution uri="..." />
    </rule>
    <rule pattern="...">
    <substitution uri="..." />
    </rule>
    上面第一個rule執行完以後,<rewrite>會用改變過的路徑和參數去繼續匹配餘下的規則。這是
    默認情況。
    7.6.7.2. 停止匹配
    例 7.52. 後續操作:停止匹配
    <rule pattern="...">
    <substitution uri="..." flags="L" />
    </rule>
    <rule pattern="...">
    <substitution uri="..." />
    </rule>
    當在substitution中指定L或者last選項時,rule匹配會到此中止。後續的rules不會再被匹配。
    Request Contexts功能指南
    119
    7.6.7.3. 串接rules
    例 7.53. 後續操作:串接rules
    <rule pattern="^/common-prefix">
    <substitution flags="C" />
    </rule>
    <rule pattern="\.jpg">
    <substitution uri="..." />
    </rule>
    <rule pattern="\.htm">
    <substitution uri="..." />
    </rule>
    當在substitution中指定C或者chain選項時,假如當前rule匹配,則會像默認情況一樣繼續匹配
    剩餘的rules;否則,就像last選項一樣立即中止匹配。
    串接rules在下面的情況下非常有用:即對一個路徑進行匹配多個patterns。例如上面的例子
    中,第一個rule限定了路徑前綴必須是「/common-prefix」,接下來的rules在此基礎上繼續判
    斷:後綴是「jpg」還是「htm」?
    7.6.8. 重定向
    例 7.54. 重定向
    永久重定向,status code=301
    <rule pattern="^/hello1\.htm">
    <substitution uri="/new_hello.htm" flags="L,R=301" />
    </rule>
    臨時重定向,status code=302,不保留參數
    <rule pattern="^/hello2\.htm">
    <substitution uri="/new_hello.htm" flags="L,R" />
    </rule>
    臨時重定向,status code=302,保留參數
    <rule pattern="^/hello3\.htm">
    <substitution uri="/new_hello.htm" flags="L,R,QSA" />
    </rule>
    絕對URL重定向,status code=302
    <rule pattern="^/hello4\.htm">
    <substitution uri="http://www.other-site.com/new_hello.htm" flags="L,R" />
    </rule>
    當在substitution中指定R或者redirect的時候,<rewrite>會返回「重定向」的響應。 重定向
    有兩種:301永久重定向,和302臨時重定向。默認是302臨時重定向,但你可以指定301來產生
    一個永久的重定向。
    通常,R標記會和L標記一起使用,使<rewrite>立即結束。
    重定向和QSA標記一起使用時,可以將當前請求的所有參數附加到重定向請求中。不過這裡需要
    注意的是,假如當前請求是一個post請求,那麼將參數附加到新的URL中,可能會導致URL過長
    而重定向失敗的問題。
    Request Contexts功能指南
    120
    重定向可以指向另一個不同域名的網站 —— 反過來說,假如你希望rewrite到另一個網站,那麼
    你必須指定重定向的選項才行。
    7.6.9. 自定義處理器
    例 7.55. 自定義處理器
    <rule pattern="...">
    <handlers>
    <rewrite-handlers:handler class="..." />
    <rewrite-handlers:handler class="..." />
    </handlers>
    </rule>
    有時候,基於正則表達式替換的substitution不能滿足較複雜的需求,好在<rewrite>還提供了
    另一種機制:自定義處理器。
    當rule和conditions被匹配的時候,所有的handlers將被執行。Webx提供了一個handler參考
    實現:
    例 7.56. 自定處理器參考實現:規格化路徑
    <rule pattern="...">
    <handlers>
    <rewrite-handlers:handler
    class="com.alibaba.citrus.service.requestcontext.rewrite.support.UrlNormalizer"
    />
    </handlers>
    </rule>
    7.7. 本章總結
    本文詳細介紹了Request Contexts的功能。
    Request Contexts服務是Webx框架的核心功能之一。它看似簡單,但卻提供了很多有用功
    能。相對於其它框架中的解決方案,RequestContexts顯得更加優雅,因為其中大部分功能對
    應用程序是透明的 —— 應用程序不需要知道它們的存在,就可以享受它們所提供的功能。
    121
    第 8 章 Request Context之Session指南
    8.1. Session概述 ........................................................................................................... 121
    8.1.1. 什麼是Session ............................................................................................. 121
    8.1.2. Session數據存在哪? ................................................................................... 121
    8.1.3. 創建通用的session框架 ................................................................................ 123
    8.2. Session框架 ........................................................................................................... 124
    8.2.1. 最簡配置 .................................................................................................... 124
    8.2.2. Session ID .................................................................................................. 124
    8.2.3. Session的生命期 ......................................................................................... 125
    8.2.4. Session Store ............................................................................................. 127
    8.2.5. Session Model ........................................................................................... 129
    8.2.6. Session Interceptor .................................................................................... 129
    8.3. Cookie Store ........................................................................................................ 130
    8.3.1. 多值Cookie Store ...................................................................................... 131
    8.3.2. 單值Cookie Store ...................................................................................... 134
    8.4. 其它Session Store ................................................................................................. 137
    8.4.1. Simple Memory Store ................................................................................ 137
    8.5. 本章總結 ............................................................................................................... 138
    Webx實現了一套session框架。Session框架建立在request contexts機制之上。建議你先閱
    讀第 6 章 Filter、Request Contexts和Pipeline和第 7 章 Request Contexts功能指南,以便了
    解request contexts是怎麼回事。
    8.1. Session概述
    8.1.1. 什麼是Session
    HTTP協議是無狀態的,但通過session機制,就能把無狀態的變成有狀態的。Session的功能就是
    保存HTTP請求之間的狀態數據。有了session的支持,就很容易實現諸如用戶登錄、購物車等網
    站功能。在Servlet API中,有一個HttpSession的介面。你可以這樣使用它:
    例 8.1. 在Java代碼中訪問session
    在一個請求中,保存session的狀態
    // 取得session對象
    HttpSession session = request.getSession();
    // 在session中保存用戶狀態
    session.setAttribute("loginId", "myName");
    在另一個請求中,取出session的狀態:
    // 得到"myName"
    String myName = (String) session.getAttribute("loginId");
    8.1.2. Session數據存在哪?
    Session的狀態數據是怎樣保存的呢?
    Request Context之Session指南
    122
    8.1.2.1. 保存在應用伺服器的內存中
    一般的做法,是將session對象保存在內存里。同一時間,會有很多session被保存在伺服器的內
    存里。由於內存是有限的,較好的伺服器會把session對象的數據交換到文件中,以確保內存中
    的session數目保持在一個合理的範圍內。
    為了提高系統擴展性和可用性,我們會使用集群技術 —— 就是一組獨立的機器共同運行同一個
    應用。對用戶來講,集群相當於一台「大型伺服器」。而實際上,同一用戶的兩次請求可能被分
    配到兩台不同的伺服器上來處理。這樣一來,怎樣保證兩次請求中存取的session值一致呢?
    一種方法是使用session複製:當session的值被改變時,將它複製到其它機器上。這個方案又有
    兩種具體的實現,一種是廣播的方式。這種方式下,任何一台伺服器都保存著所有伺服器所接受
    到的session對象。伺服器之間隨時保持著同步,因而所有伺服器都是等同的。可想而知,當訪
    問量增大的時候,這種方式花費在廣播session上的帶寬有多大,而且隨著機器增加,網路負擔
    成指數級上升,不具備高度可擴展性。
    另一種方法是TCP-Ring的方式,也就是把集群中所有的伺服器看成一個環,A→B→C→D→A,
    首尾相接。把A的session複製到B,B的session複製到C,……,以此類推,最後一台伺服器的
    session複製到A。這樣,萬一A宕機,還有B可以頂上來,用戶的session數據不會輕易丟失。但
    這種方案也有缺點:一是配置複雜;二是每增添/減少一台機器時,ring都需要重新調整,這將
    成為性能瓶頸;三是要求前端的Load Balancer具有相當強的智能,才能將用戶請求分發到正
    確的機器上。
    8.1.2.2. 保存在單一數據源中
    也可以將session保存在單一的數據源中,這個數據源可被集群中所有的機器所共享。這樣一
    來,就不存在複製的問題了。
    然而單一數據源的性能成了問題。每個用戶請求,都需要訪問後端的數據源(很可能是資料庫)
    來存取用戶的數據。
    這種方案的第二個問題是:缺少應用服務廠商的支持 —— 很少有應用伺服器直接支持這種
    方案。更不用說數據源有很多種(MySQL、Oracle、Hsqldb等各種資料庫、專用的session
    server等)了。
    第三個問題是:數據源成了系統的瓶頸,一但這個數據源崩潰,所有的應用都不可能正常運行
    了。
    8.1.2.3. 保存在客戶端
    把session保存在客戶端。這樣一來,由於不需要在伺服器上保存數據,每台伺服器就變得獨
    立,能夠做到線性可擴展和極高的可用性。
    具體怎麼做呢?目前可用的方法,恐怕就是保存在cookie中了。但需要提醒的是,cookie具有
    有以下限制,因此不可無節制使用該方案:
    • Cookie數量和長度的限制。每個domain最多只能有20條cookie,每個cookie長度不能超過
    4KB,否則會被截掉。
    • 安全性問題。如果cookie被人攔截了,那人就可以取得所有的session信息。即使加密也與事
    無補,因為攔截者並不需要知道cookie的意義,他只要原樣轉發cookie就可以達到目的了。
    • 有些狀態不可能保存在客戶端。例如,為了防止重複提交表單,我們需要在伺服器端保存一個
    計數器。如果我們把這個計數器保存在客戶端,那麼它起不到任何作用。
    Request Context之Session指南
    123
    雖然有上述缺點,但是對於其優點(極高的擴展性和可用性)來說,就顯得微不足道。我們可以
    用下面的方法來迴避上述的缺點:
    • 通過良好的編程,控制保存在cookie中的session對象的大小。
    • 通過加密和安全傳輸技術(SSL),減少cookie被破解的可能性。
    • 只在cookie中存放不敏感數據,即使被盜也不會有重大損失。
    • 控制cookie的生命期,使之不會永遠有效。偷盜者很可能拿到一個過期的cookie。
    8.1.2.4. 將客戶端、伺服器端組合的方案
    任何一種session方案都有其優缺點。最好的方法是把它們結合起來。這樣就可以彌補各自的缺
    點。
    將大部分session數據保存在cookie中,將小部分關鍵和涉及安全的數據保存在伺服器上。由於
    我們只把少量關鍵的信息保存在服務端,因而伺服器的壓力不會非常大。
    在伺服器上,單一的數據源比複製session的方案,更簡單可靠。我們可以使用資料庫來保存這
    部分session,也可以使用更廉價、更簡單的存儲,例如Berkeley DB就是一種不錯的伺服器存儲
    方案。將session數據保存在cookie和Berkeley DB(或其它類似存儲技術)中,就可以解決我
    們的絕大部分問題。
    8.1.3. 創建通用的session框架
    多數應用伺服器並沒有留出足夠的餘地,來讓你自定義session的存儲方案。縱使某個應用服務
    器提供了對外擴展的介面,可以自定義session的方案,我們也不大可能使用它。為什麼呢?因
    為我們希望保留選擇應用伺服器軟體的自由。
    因此,最好的方案,不是在應用伺服器上增加什麼新功能,而是在WEB應用框架上做手術。一
    但我們在WEB應用框架中實現了這種靈活的session框架,那麼我們的應用可以跑在任何標準的
    JavaEE應用伺服器上。
    除此之外,一個好的session框架還應該做到對應用程序透明。具體表現在:
    • 使用標準的HttpSession介面,而不是增加新的API。這樣任何WEB應用,都可以輕易在兩種
    不同的session機制之間切換。
    • 應用程序不需要知道session中的對象是被保存到了cookie中還是別的什麼地方。
    • Session框架可以把同一個session中的不同的對象分別保存到不同的地方去,應用程序同樣不
    需要關心這些。例如,把一般信息放到cookie中,關鍵信息放到Berkeley DB中。甚至同是
    cookie,也有持久和臨時之分,有生命期長短之分。
    Webx實現了這種session框架,把它建立在Request Contexts的基礎上。
    Request Context之Session指南
    124
    8.2. Session框架
    8.2.1. 最簡配置
    例 8.2. Session框架基本配置(/WEB-INF/webx.xml)
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <buffered />
    <lazy-commit />
    ...
    <session>
    <stores>
    <session-stores:simple-memory-store id="simple" />
    </stores>
    <store-mappings>
    <match name="*" store="simple" />
    </store-mappings>
    </session>
    </services:request-contexts>
    以上的配置,創建了一個最基本的session實現:將所有數據( name=*)保存在內存里(
    simple-memory-store)。
    警告
    最簡配置只能用於開發,請不要將上述配置用在生產環境。因為simple-memorystore
    只是將數據保存在內存里。在生產環境中,內存有被耗盡的可能。這段配置
    也不支持伺服器集群。
    8.2.2. Session ID
    Session ID唯一標識了一個session對象。把session ID保存在cookie里是最方便的。這樣,凡
    是cookie值相同的所有的請求,就被看作是在同一個session中的請求。在servlet中,還可以把
    session ID編碼到URL中。Session框架既支持把session ID保存在cookie中,也支持把session ID
    編碼到URL中。
    完整的session ID配置如下:
    例 8.3. Session ID的配置
    <session>
    <id cookieEnabled="true" urlEncodeEnabled="false">
    <cookie name="JSESSIONID" domain="" maxAge="0" path="/" httpOnly="true" secure="false" />
    <url-encode name="JSESSIONID" />
    <session-idgens:uuid-generator />
    </id>
    </session>
    上面這段配置包含了關於Session ID的所有配置以及默認值。如果不指定上述參數,則系統將使
    用默認值,其效果等同於上述配置。
    表 8.1. Session ID的配置說明
    配置<session><id> —— 將Session ID保存於何處?
    cookieEnabled 是否把session ID保存在cookie中,如若不是,則只能保存的URL中。
    Request Context之Session指南
    125
    配置<session><id> —— 將Session ID保存於何處?
    默認為開啟:true。
    urlEncodeEnabled
    是否支持把session ID編碼在URL中。如果為true開啟,應用必須調
    用response.encodeURL()或response.encodeRedirectURL()來
    將JSESSIONID編碼到URL中。
    默認為關閉:false。
    配置<session><id><cookie> —— 將Session ID存放於cookie的設置
    name
    Session ID cookie的名稱。
    默認為JSESSIONID。
    domain
    Session ID cookie的domain。
    默認為空,表示根據當前請求自動設置domain。這意味著瀏覽器
    認為你的cookie屬於當前域名。如果你的應用包含多個子域名,例
    如:www.alibaba.com、china.alibaba.com,而你又希望它們能共享session
    的話,請把域名設置成「alibaba.com」。
    maxAge
    Session ID cookie的最長存活時間(秒)。
    默認為0,表示臨時cookie,隨瀏覽器的關閉而消失。
    path
    Session ID cookie的path。
    默認為/,表示根路徑。
    httpOnly
    在session ID cookie上設置HttpOnly標記。
    在IE6及更新版本中,可以緩解XSS攻擊的危險。默認為true。
    secure
    在session ID cookie上設置Secure標記。
    這樣,只有在https請求中才可訪問該cookie。默認為false。
    配置<session><id><url-encode> —— 將Session ID編碼到URL的設置
    name
    指定在URL中表示session ID的名字,默認也是JSESSIONID。
    此時,如果urlEncodeEnabled為true的話,調用:
    response.encodeURL("http://localhost:8080/test.jsp?id=1")
    將得到類似這樣的結果:
    http://localhost:8080/test.jsp;JSESSIONID=xxxyyyzzz?id=1
    配置<session><id><session-idgens:*> —— 如何生成session ID?
    uuid-generator
    以UUID作為新session ID的生成演算法。
    這是默認的session ID生成演算法。
    為了達到最大的兼容性,我們分兩種情況來處理JSESSIONID:
    1. 當一個新session到達時,假如cookie或URL中已然包含了JSESSIONID,那麼我們將直接利
    用這個值。為什麼這樣做呢?因為這個JSESSIONID可能是由同一域名下的另一個不相關應用
    生成的。如果我們不由分說地將這個cookie覆蓋掉,那麼另一個應用的session就會丟失。
    2. 多數情況下,對於一個新session,應該是不包含JSESSIONID的。這時,我們
    需要利用SessionIDGenerator來生成一個唯一的字元串,作為JSESSIONID的
    值。SessionIDGenerator的默認實現UUIDGenerator。
    8.2.3. Session的生命期
    所謂生命期,就是session從創建到失效的整個過程。其狀態變遷如下圖所示:
    Request Context之Session指南
    126
    圖 8.1. Session生命期
    總結一下,其實很簡單:
    1. 第一次打開瀏覽器時,JSESSIONID還不存在,或者存在由同一域名下的其它應用所設置的無
    效的JSESSIONID。這種情況下,session.isNew()返回true。
    2. 隨後,只要在規定的時間間隔內,以及cookie過期之前,每一次訪問系統,都會使session得
    到更新。此時session.isNew()總是返回false。Session中的數據得到保持。
    3. 如果用戶有一段時間不訪問系統了,超過指定的時間,那麼系統會清除所有的session內容,
    並將session看作是新的session。
    4. 用戶可以調用session.invalidate()方法,直接清除所有的session內容。此後所有
    試圖session.getAttribute()或session.setAttribute()等操作,都會失敗,得
    到IllegalStateException異常,直到下一個請求到來。
    在session框架中,有一個重要的特殊對象,用來保存session生命期的狀態。這個對象叫作
    session model。它被當作一個普通的對象存放在session中,但是通過HttpSession介面不能直
    接看到它。
    關於session生命期的完整配置如下:
    Request Context之Session指南
    127
    例 8.4. 關於Session生命期的配置
    <session maxInactiveInterval="0" keepInTouch="false" forceExpirationPeriod="14400"
    modelKey="SESSION_MODEL">
    ...
    </session>
    參數的意思是:
    表 8.2. Session生命期的配置參數
    參數名說明
    maxInactiveInterval
    指定session不活動而失效的期限,單位是秒。
    默認為0,也就是永不失效(除非cookie失效)。例如,設置3600秒,表示用戶
    離開瀏覽器1小時以後再回來,session將重新開始,老數據將被丟棄。
    keepInTouch
    是否每次都touch session(即更新最近訪問時間)。
    如果是false,那麼只在session值有改變時touch。當將session model保存在
    cookie中時,設為false可以減少網路流量。但如果session值長期不改變,由於
    最近訪問時間一直無法更新,將會使session超過maxInactiveInterval所設定的
    秒數而失效。
    默認為false。
    forceExpirationPeriod
    指定session強製作廢期限,單位是秒。
    無論用戶活動與否,從session創建之時算起,超過這個期限,session將被強製作
    廢。這是一個安全選項:萬一cookie被盜,過了這個期限的話,那麼無論如何,
    被盜的cookie就沒有用了。
    默認為0,表示無期限。
    modelKey
    指定用於保存session狀態的對象的名稱。
    默認為"SESSION_MODEL"。一般沒必要修改這個值。
    8.2.4. Session Store
    Session Store是session框架中最核心的部分。Session框架最強大的部分就在於此。我們可以定
    義很多個session stores,讓不同的session對象分別存放到不同的Session Store中。前面提到有
    一個特殊的對象「SESSION_MODEL」也必須保存在一個session store中。
    Request Context之Session指南
    128
    圖 8.2. Session和Stores
    類似於Servlet的配置,Session store的配置也包含兩部分內容:session store的定義,和
    session store的映射(mapping)。
    例 8.5. Session Store的配置
    <session>
    <stores>
    <session-stores:store id="store1" />
    <session-stores:store id="store2" />
    <session-stores:store id="store3" />
    </stores>
    <store-mappings>
    <match name="*" store="store1" />
    <match name="loginName" store="store2" />
    <matchRegex pattern="key.*" store="store3" />
    </store-mappings>
    </session>
    定義session stores:你可以配置任意多個session store,只要ID不重複。此
    處,store1、store2和store3分別是三個session store的名稱。
    映射session stores:match標籤用來精確匹配attribute name。一個特別的值是「*」,
    它代表默認匹配所有的names。
    本例中, 如果調用session.setAttribute("loginName", user.getId()),那麼這個
    值將被保存到store2里;如果調用session.setAttribute("other", value)將被默認
    匹配到store1中。
    映射session stores:matchRegexp標籤用正則表達式來匹配attribute names。
    本例中, key_a、key_b等值都將被保存到store3里。
    需要注意以下幾點:
    • 在整個session配置中,只能有一個store擁有默認的匹配。
    Request Context之Session指南
    129
    • 假如有多個match或matchRegex同時匹配某個attribute name,那麼遵循以下匹配順序:
    1. 精確的匹配最優先。
    2. 正則表達式的匹配遵循最大匹配的原則,假如有兩個以上的正則表達式被同時匹配,長度
    較長的匹配勝出。
    3. 默認匹配*總是在所有的匹配都失敗以後才會被激活。
    • 必須有一個session store能夠用來存放session model。
    • 你可以用<match name="*">來匹配session model;
    • 也可以用精確匹配:<match name="SESSION_MODEL" />。其中session model的名字是
    必須和前述modelKey配置的值相同,其默認值為「SESSION_MODEL」。
    8.2.5. Session Model
    Session Model是用來記錄當前session的生命期數據的,例如:session的創建時間、最近更新
    時間等。默認情況下,
    • 當需要保存session數據時,SessionModel對象將被轉換成一個JSON字元串(如下所示),
    然後這個字元串將被保存在某個session store中:
    {id:"SESSION_ID",ct:創建時間,ac:最近訪問時間,mx:最長不活動時間}
    • 需要讀取時,先從store中讀到上述格式的字元串數據,然後再把它解碼成真正
    的SessionModel對象。
    以上轉換過程是通過一個SessionModelEncoder介面來實現的。為了提供更好的移植
    性,Session框架可同時支持多個SessionModelEncoder的實現。配置如下:
    例 8.6. Session Model編碼器的配置
    <session>
    <session-model-encoders>
    <model-encoders:default-session-model-encoder />
    <model-encoders:model-encoder class="..." />
    <model-encoders:model-encoder class="..." />
    </session-model-encoders>
    </session>
    在上面的例子中,提供了三個SessionModelEncoder的實現。第一個是默認的實現,第二、第
    三個是任意實現。
    • 當從store取得SessionModel對象時,框架將依次嘗試所有的encoder,直到解碼成功為
    止。
    • 當將SessionModel對象保存到store之前,框架將使用第一個encoder來編碼對象。
    當你從不同的SessionModel編碼方案中移植的時候,上述多encoders共存的方案可以實現平
    滑的過渡。
    8.2.6. Session Interceptor
    Session Interceptor攔截器的作用是攔截特定的事件,甚至干預該事件的執行結果。目前有兩
    種攔截器介面:
    Request Context之Session指南
    130
    表 8.3. Session Interceptor攔截器介面
    介面功能
    SessionLifecycleListener
    監聽以下session生命期事件:
    • Session被創建
    • Session被訪問
    • Session被作廢
    SessionAttributeInterceptor
    攔截以下session讀寫事件:
    • onRead – 攔截session.getAttribute()方法,可以修改所讀取的數
    據。
    • onWrite – 攔截session.setAttribute()方法,可以修改所寫到store
    中的數據。
    Session框架自身已經提供了兩個有用的攔截器:
    表 8.4. Session Interceptor的實現
    名稱說明
    <lifecycle-logger> 監聽session生命期事件,並記錄日誌。
    <attribute-whitelist>
    控制session中的attributes,只允許白名單中所定義的attribute名稱和類
    型被寫入到或讀出於session store中。
    這個功能對於cookie store是很有用的。因為cookie有長度的限制,所以
    需要用白名單來限制寫入到cookie中的數據數量和類型。
    你可以同時配置多種攔截器,如下所示。
    例 8.7. 配置session interceptors
    <session>
    <request-contexts:interceptors
    xmlns="http://www.alibaba.com/schema/services/request-contexts/session/interceptors">
    <lifecycle-logger />
    <attribute-whitelist>
    <attribute name="_csrf_token" />
    <attribute name="_lang" />
    <attribute name="loginUser" type="com.alibaba...MyUser" />
    <attribute name="shoppingCart" type="com.alibaba....ShoppingCart" />
    </attribute-whitelist>
    <interceptor class="..." />
    </request-contexts:interceptors>
    </session>
    8.3. Cookie Store
    Cookie Store的作用,是將session對象保存在客戶端cookie中。Cookie Store減輕了伺服器維
    護session數據的壓力,從而提高了應用的擴展性和可用性。
    另一方面,在現實應用中,很多地方都會直接讀寫cookie。讀寫cookie是一件麻煩的事,因為
    你必須要設置很多參數:domain、path、httpOnly...等很多參數。而操作HttpSession是一件
    相對簡單的事。因此,webx主張把一切對cookie的讀寫,都轉換成對session的讀寫。
    Request Context之Session指南
    131
    8.3.1. 多值Cookie Store
    8.3.1.1. 最簡配置
    例 8.8. 最基本的cookie配置(/WEB-INF/webx.xml)
    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <buffered />
    <lazy-commit />
    ...
    <session>
    <stores>
    <session-stores:cookie-store id="temporaryClientStore">
    <session-stores:cookie name="tmp" />
    </session-stores:cookie-store>
    </stores>
    <store-mappings>
    <match name="*" store="temporaryClientStore" />
    </store-mappings>
    </session>
    </services:request-contexts>
    上面的配置創建了一個「臨時」cookie(即隨著瀏覽器關閉而清除),來作為默認的session對
    象的存儲。
    Cookie Store依賴其它兩個Request Contexts: <buffered> 和 <lazy-commit>。沒有它
    們,就不能實現基於cookie的session。為什麼呢?這要從HTTP協議談起。下面是一個標準的
    HTTP響應的文本。無論你的伺服器使用了何種平台(Apache HTTPD Server、Java Servlet/
    JSP、Microsoft IIS,……),只要你通過瀏覽器來訪問,必須返回類似下面的HTTP響應:
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Set-Cookie: JSESSIONID=AywiPrQKPEzfF9OZ; Path=/
    Content-Type: text/html;charset=GBK
    Content-Language: zh-CN
    Content-Length: 48
    Date: Mon, 06 Nov 2006 07:59:38 GMT
    <html>
    <body>
    ……
    我們注意到,HTTP響應分為Header和Content兩部分。從「HTTP/1.1 200 OK」開始,
    到「<html>」之前,都是HTTP Header,後面則為HTTP Content。而cookie是在header中指
    定的。一但應用伺服器開始向瀏覽器輸出content,那就再也沒有機會修改header了。問題就
    出在這裡。作為session的cookie可以在應用程序的任何時間被修改,甚至可能在content開始
    輸出之後被修改。但是此後修改的session將不能被保存到cookie中。
    Java Servlet API的術語稱「應用伺服器開始輸出content」為「response被提交」。你可以通
    過response.isCommitted()方法來判斷這一點。那麼,哪些操作會導致response被提交呢?
    • 向response.getWriter()或getOutputStream()所返回的流中輸出,累計達到伺服器所設
    定的一個chunk的大小,通常為8K。
    • 用戶程序或系統調用response.flushBuffer()。
    • 用戶程序或系統調用response.sendError()轉到錯誤頁面。
    • 用戶程序或系統調用response.sendRedirect()重定向。
    Request Context之Session指南
    132
    只要避免上述情形的出現,就可以確保cookie可以被隨時寫入。前兩個Request Contexts
    —— <buffered>和<lazy-commit>正好解決了上面的問題。第一個<buffered>將所有的輸出
    到response.getWriter()或getOutputStream()的內容緩存在內存里,直到最後一刻才真正
    輸出到瀏覽器;第二個<lazy-commit>攔截了response對象中引起提交的方法,將它們延遲到
    最後才執行。這樣就保證了在cookie被完整寫入之前,response絕不會被任何因素提交。
    此外,<buffered>不是專為session框架而設計的。Webx的頁面布局系統也依賴於這個
    Request Context。
    8.3.1.2. Cookie的參數
    例 8.9. Cookie的參數
    <session-stores:cookie-store id="temporaryClientStore"
    maxLength="3896" maxCount="5" checksum="false">
    <session-stores:cookie name="tmp" domain="" path="/" maxAge="0" httpOnly="true"
    secure="false" survivesInInvalidating="false" />
    </session-stores:cookie-store>
    上例中列出了所有關於cookie的參數,解釋如下:
    表 8.5. Cookie的參數
    參數名稱說明
    name
    指定cookie的名稱。
    假設名稱為「tmp」,那麼將生成tmp0、tmp1、tmp2等cookie。
    多個cookie stores的cookie名稱不能重複。
    domain 指定cookie的域名。
    path 指定cookie的路徑。
    maxAge
    指定cookie的過期時間,單位是秒。
    如果值為0,意味著cookie持續到瀏覽器被關閉
    (或稱臨時cookie)。
    有效值必須大於0,否則均被認為是臨時cookie。
    httpOnly
    在cookie上設置HttpOnly標記。
    在IE6及更新版本中,可以緩解XSS攻擊的危險。
    secure
    在cookie上設置Secure標記。
    這樣,只有在https請求中才可訪問該cookie。
    這幾個參數的默認值,均和
    Session ID cookie的設置相
    同。因此,一般不需要特別
    設置它們。
    survivesInInvalidating
    這是一個特殊的設置。如果它被設置成true,那麼當session被作廢
    (invalidate)時,這個cookie store中的對象會倖存下來,並帶入下一個新的
    session中。
    如果這個值為true,必須同時設置一個大於0的maxAge。
    這個設置有什麼用呢?比如,我們希望在cookie中記錄最近登錄的用戶名,
    以方便用戶再次登錄。可以把這個用戶名記錄在一個cookie store中,並設
    置survivesInInvalidating=true。即使用戶退出登錄,或當前session過期,
    新的session仍然可以讀到這個store中所保存的對象。
    maxLength
    指定每個cookie的最大長度。默認為3896,約3.8K。
    Cookie store會把所有對象序列化到cookie中。但是cookie的長度是不能超
    過4K的。如果cookie的長度超過這個設定,就把數據分發到新的cookie中去。因
    此每個cookie store實際可能產生好幾個cookie。
    Request Context之Session指南
    133
    參數名稱說明
    假設cookie name為tmp,那麼所生成的cookie的名稱將分別
    為:tmp0、tmp1、tmp2,以此類推。
    maxCount
    指定cookie的最大個數。默認為5。
    因此,實際cookie store可生成的cookie總長度為:maxLength * maxCount。
    如果超過這個長度,cookie store將會在日誌裡面發出警告(WARN級別),並忽
    略store中的所有對象。
    checksum
    是否創建概要cookie。默認為false。
    有時由於域名、路徑等設置的問題,會導致cookie紊亂。例如:發現同名的
    cookie、cookie缺失等錯誤。這些問題很難跟蹤。概要cookie就是為檢查這類
    問題提供一個線索。如果把這個開關打開,將會產生一個概要性的cookie。假如
    cookie name為tmp,那麼概要cookie的名字將是tmpsum。概要cookie會指出
    當前store共有幾個cookie,每個cookie的前綴等內容。當cookie的總數和內容
    與概要cookie不符時,系統將會在日誌中提出詳細的警告信息(DEBUG級別)。
    請盡量不要在生產系統中使用這個功能。
    8.3.1.3. Session Encoders
    Session里保存的是Java對象,而cookie中只能保存字元串。如何把Java對象轉換成合法的
    cookie字元串(或者將字元串恢復成對象)呢?這就是Session Encoder所要完成的任務。
    例 8.10. 配置Session Encoders
    <session-stores:cookie-store>
    ...
    <session-stores:encoders>
    <session-encoders:encoder class="..." />
    <session-encoders:encoder class="..." />
    <session-encoders:encoder class="..." />
    </session-stores:encoders>
    </session-stores:cookie-store>
    和SessionModelEncoder類似,session框架也支持多個session encoders同時存在。
    • 保存session數據時,session框架將使用第一個encoder來將對象轉換成cookie可接受的字
    符串;
    • 讀取session數據時,session框架將依次嘗試所有的encoders,直到解碼成功為止。
    這種編碼、解碼方案可讓使用不同session encoders的系統之間共享cookie數據,也有利於平
    滑遷移系統。
    Session框架提供了一種encoder的實現,編碼的基本過程為:序列化、加密(可選)、壓
    縮、Base64編碼、URL encoding編碼。
    例 8.11. 配置Session Encoders的幾種方案
    • 基本配置:用hessian演算法(默認)來序列化,不加密。
    <session-stores:cookie-store>
    <session-stores:encoders>
    <session-encoders:serialization-encoder />
    </session-stores:encoders>
    </session-stores:cookie-store>
    這是默認實現。
    Request Context之Session指南
    134
    • 用aes演算法加密。AES演算法可支持128、192、256位的密鑰,默認為keySize=128。
    <session-stores:cookie-store>
    <session-stores:encoders>
    <session-encoders:serialization-encoder>
    <session-serializers:hessian-serializer />
    <session-encrypters:aes-encrypter key="0123456789abcdef" />
    </session-encoders:serialization-encoder>
    </session-stores:encoders>
    </session-stores:cookie-store>
    也可以明確指定hession序列化。
    添加AES加密演算法,並提供密鑰。
    • 改用java原生的序列化演算法。使用hessian演算法(默認)可大幅縮短序列化的長度,但使用
    java原生的序列化演算法,具有最好的可移植性。
    <session-stores:cookie-store>
    <session-stores:encoders>
    <session-encoders:serialization-encoder>
    <session-serializers:java-serializer />
    </session-encoders:serialization-encoder>
    </session-stores:encoders>
    </session-stores:cookie-store>
    指定java序列化。
    8.3.2. 單值Cookie Store
    前面所描述的cookie store,是在一組cookie(如tmp0, tmp1, ...)中保存一組attributes的名
    稱和對象。它所創建的cookie值,只有session框架自己才能解讀。
    假如有一些非webx的代碼想要共享保存在cookie中的session數據,例如,Java Script代碼、
    其它未使用webx框架的應用,希望能讀取session數據,應該怎麼辦呢?Session框架提供了一
    種相對簡單的「單值cookie store」可用來解決這個問題。顧名思義,單值cookie store就是在
    一個cookie中僅保存一個值或對象。
    8.3.2.1. 最簡配置
    例 8.12. 單值cookie store基本配置
    <session>
    <stores>
    ...
    <stores:single-valued-cookie-store id="loginNameCookie">
    <stores:cookie name="login" />
    </stores:single-valued-cookie-store>
    </stores>
    <store-mappings>
    ...
    <match name="loginName" store="loginNameCookie" />
    </store-mappings>
    </session>
    單值cookie store的ID是loginNameCookie。
    Cookie的名稱是login。
    Request Context之Session指南
    135
    Session attribute的名稱是loginName,attribute名稱和cookie名稱不必相同。
    根據上面的配置,下面程序會生成cookie:login=myname。
    例 8.13. 訪問單值cookie的代碼
    session.setAttribute("loginName", "myname");
    需要注意的是,上述最簡配置,只能用來存取字元串值。如果需要存取其它類型的對象,則需要
    配置Session Value Encoder。詳見第 8.3.2.3 節 「Session Value Encoders」。
    8.3.2.2. Cookie的參數
    例 8.14. 單值cookie的參數配置
    <session-stores:single-valued-cookie-store id="loginNameCookie">
    <session-stores:cookie name="login" domain="" path="/" maxAge="0" httpOnly="true"
    secure="false" survivesInInvalidating="false" />
    </session-stores:single-valued-cookie-store>
    單值cookie的參數設置完全類似於普通cookie store的設置。唯一的差別是,單值cookie只生
    成一個cookie,而普通的cookie store則可能生成多個相關的cookies。
    表 8.6. Cookie的參數
    參數名稱說明
    name 指定cookie的名稱。
    domain 指定cookie的域名。
    path 指定cookie的路徑。
    maxAge
    指定cookie的過期時間,單位是秒。
    如果值為0,意味著cookie持續到瀏覽器被關閉
    (或稱臨時cookie)。
    有效值必須大於0,否則均被認為是臨時cookie。
    httpOnly
    在cookie上設置HttpOnly標記。
    在IE6及更新版本中,可以緩解XSS攻擊的危險。
    secure
    在cookie上設置Secure標記。
    這樣,只有在https請求中才可訪問該cookie。
    這幾個參數的默認值,均和
    Session ID cookie的設置相
    同。因此,一般不需要特別
    設置它們。
    survivesInInvalidating
    這是一個特殊的設置。如果它被設置成true,那麼當session被作廢
    (invalidate)時,這個cookie store中的對象會倖存下來,並帶入下一個新的
    session中。
    如果這個值為true,必須同時設置一個大於0的maxAge。
    這個設置有什麼用呢?比如,我們希望在cookie中記錄最近登錄的用戶名,
    以方便用戶再次登錄。可以把這個用戶名記錄在一個cookie store中,並設
    置survivesInInvalidating=true。即使用戶退出登錄,或當前session過期,
    新的session仍然可以讀到這個store中所保存的對象。
    8.3.2.3. Session Value Encoders
    單值cookie store可以保存任意的Java對象,只要這個Java對象能夠被轉換成字元串,以
    及從字元串中恢復。將Java對象轉換成字元串,以及從字元串中恢復,就是Session Value
    Encoder的任務。和前面所說的Session Encoder不同,Session Value Encoder只轉換session
    attribute的值,而Session Encoder需要轉換一組session attributes的key-values。
    Request Context之Session指南
    136
    例 8.15. Session Value Encoders的配置
    <session-stores:single-valued-cookie-store>
    ...
    <session-stores:encoders>
    <session-value-encoders:encoder class="..." />
    <session-value-encoders:encoder class="..." />
    <session-value-encoders:encoder class="..." />
    </session-stores:encoders>
    </session-stores:single-valued-cookie-store>
    和SessionModelEncoder以及SessionEncoder類似,session框架也支持多個session value
    encoders同時存在。
    • 保存session數據時,session框架將使用第一個encoder來將對象轉換成cookie可接受的字
    符串;
    • 讀取session數據時,session框架將依次嘗試所有的encoders,直到解碼成功為止。
    這種編碼、解碼方案可讓使用不同session value encoders的系統之間共享cookie數據,也有
    利於平滑遷移系統。
    目前有兩種基本的session value encoders實現。<simple-value-encoder>和<mappedvalues-
    encoder>。下面舉例說明。
    例 8.16. 配置Session Value Encoders的幾種方案
    • 編碼字元串值,以指定的charset對字元串進行URL encoding。
    <session-stores:encoders>
    <session-value-encoders:simple-value-encoder charset="GBK" />
    </session-stores:encoders>
    如不指定charset參數,默認charset為「UTF-8」。
    • 編碼指定類型的值,該值具有默認的PropertyEditor,可以轉換成String,或從String中
    恢復。
    <session-stores:encoders>
    <session-value-encoders:simple-value-encoder type="com.alibaba...MyEnum" />
    </session-stores:encoders>
    Spring直接支持將Enum類型的值轉成String類型,或反之。
    • 編碼指定類型的值,註冊相應的registrar來進行類型轉換。
    <session-stores:encoders>
    <session-value-encoders:simple-value-encoder type="java.util.Date">
    <session-value-encoders:property-editor-registrar
    class="com.alibaba.citrus.service.configuration.support.CustomDateRegistrar"
    p:timeZone="GMT+8" p:format="yyyy-MM-dd" />
    </session-value-encoders:simple-value-encoder>
    </session-stores:encoders>
    註冊registrar,將Date類型按格式yyyy-MM-dd轉成字元串,或從該格式的字元串中恢
    復。
    Request Context之Session指南
    137
    • 在上面例子的基礎上,可增加encrypter,對value進行加密。
    <session-stores:encoders>
    <session-value-encoders:simple-value-encoder type="java.util.Date">
    <session-value-encoders:property-editor-registrar
    class="com.alibaba.citrus.service.configuration.support.CustomDateRegistrar"
    p:timeZone="GMT+8" p:format="yyyy-MM-dd" />
    <session-encrypters:aes-encrypter key="0123456789abcdef" />
    </session-value-encoders:simple-value-encoder>
    </session-stores:encoders>
    用AES和指定密鑰進行加密。
    • <mapped-values-encoder>和<simple-value-encoder>類似,差別在於,前者只接
    受java.util.Map數據類型,並將其編碼成「key:value&key:value」的格式。下面的例子
    可接受Map<String, Date>類型的數據:
    <session-stores:encoders>
    <session-value-encoders:mapped-values-encoder valueType="com.alibaba...MyEnum" />
    </session-stores:encoders>
    注意此處所指定的類型為map中的value的類型。
    當你用下面的代碼,可設置cookie值「key1:value1&key2:value2」:
    Map<String, MyEnum> mappedValue = new HashMap<String, MyEnum>();
    mappedValue.put("key1", MyEnum.value1);
    mappedValue.put("key2", MyEnum.value2);
    session.setAttribute("cookie", mappedValue);
    將整個map作為session attribute的值,其中,map的value類型必須符合配置文件中
    指定的類型。
    • 類似的,你同樣可以對<mapped-values-encoder>指定registrar和encrypter,不再贅述。
    8.4. 其它Session Store
    8.4.1. Simple Memory Store
    SimpleMemoryStore是最簡單的session store。它將所有的session對象都保存在內存裡面。這
    種store不支持多台機器的session同步,而且也不關心內存是否被用盡。因此這種簡單的store一
    般只應使用於測試環境。
    例 8.17. 配置simple memory store
    <stores>
    <session-stores:simple-memory-store id="simple" />
    </stores>
    警告
    鑒於simple-memory-store的實現的簡單性,請不要將它應用在生產環境。
    Request Context之Session指南
    138
    8.5. 本章總結
    Session是個難題,特別是對於要求高擴展性和高可用性的網站來說。
    我們在標準的Java Servlet API的基礎之上,實現了一套全新的session框架。在此基礎上可以
    進一步實現多種session的技術,例如:基於cookie的session、基於資料庫的session、基於
    Berkeley DB的session、基於內存的session,甚至也可以實現基於TCP-ring的session等等。最
    重要的是,我們能把這些技術結合起來,使每種技術的優點能夠互補,缺點可以被避免。
    所有這一切,對應用程序是完全透明的 —— 應用程序不用知道session是如何實現的、它們的對
    象被保存到哪個session store中等問題 —— session框架可以妥善地處理好這一切。
    部分 III. Webx應用支持服務
    140
    第 9 章 表單驗證服務指南 ............................................................................................. 141
    9.1. 表單概述 ....................................................................................................... 141
    9.1.1. 什麼是表單驗證 ................................................................................... 141
    9.1.2. 表單驗證的形式 ................................................................................... 142
    9.2. 設計 .............................................................................................................. 144
    9.2.1. 驗證邏輯與表現邏輯分離 ..................................................................... 144
    9.2.2. 驗證邏輯和應用代碼分離 ..................................................................... 145
    9.2.3. 表單驗證的流程 ................................................................................... 145
    9.3. 使用表單驗證服務 .......................................................................................... 146
    9.3.1. 創建新數據 ......................................................................................... 146
    9.3.2. 修改老數據 ......................................................................................... 153
    9.3.3. 批量創建或修改數據 ............................................................................ 155
    9.4. 表單驗證服務詳解 .......................................................................................... 159
    9.4.1. 配置詳解 ............................................................................................ 159
    9.4.2. Validators .......................................................................................... 167
    9.4.3. Form Tool .......................................................................................... 177
    9.4.4. 外部驗證 ............................................................................................ 179
    9.5. 本章總結 ....................................................................................................... 180
    141
    第 9 章 表單驗證服務指南
    9.1. 表單概述 ............................................................................................................... 141
    9.1.1. 什麼是表單驗證 ........................................................................................... 141
    9.1.2. 表單驗證的形式 ........................................................................................... 142
    9.2. 設計 ...................................................................................................................... 144
    9.2.1. 驗證邏輯與表現邏輯分離 ............................................................................. 144
    9.2.2. 驗證邏輯和應用代碼分離 ............................................................................. 145
    9.2.3. 表單驗證的流程 ........................................................................................... 145
    9.3. 使用表單驗證服務 .................................................................................................. 146
    9.3.1. 創建新數據 ................................................................................................. 146
    9.3.2. 修改老數據 ................................................................................................. 153
    9.3.3. 批量創建或修改數據 .................................................................................... 155
    9.4. 表單驗證服務詳解 .................................................................................................. 159
    9.4.1. 配置詳解 .................................................................................................... 159
    9.4.2. Validators .................................................................................................. 167
    9.4.3. Form Tool .................................................................................................. 177
    9.4.4. 外部驗證 .................................................................................................... 179
    9.5. 本章總結 ............................................................................................................... 180
    9.1. 表單概述
    9.1.1. 什麼是表單驗證
    在WEB應用中,表單驗證是非常重要的一環。表單驗證,顧名思義,就是確保用戶所填寫的數
    據符合應用的要求。例如下面這個「註冊新帳戶」的表單:
    圖 9.1. 一個典型的表單頁面
    在這個表單中,各欄位需要符合以下規則:
    表 9.1. 註冊新帳戶的規則
    欄位名規則
    用戶名必須由字母、數字、下劃線構成。 · 用戶名的
    長度必須在某個範圍內,例如,4-10個字元。
    表單驗證服務指南
    142
    欄位名規則
    密碼長度必須在某個範圍內,例如,4-10個字元。
    密碼和用戶名不能相同,以保證基本的安全性。
    確認密碼(再輸一遍密碼) 必須和密碼相同,確保用戶沒有打字錯誤。
    從技術上講,表單驗證完全可以用手工書寫代碼的方式來實現。但是這樣做既無趣,又容易出
    錯,而且難以維護 —— 特別是當你需要修改驗證規則時。因此,幾乎所有的WEB框架都提供了
    表單驗證的功能,使你能方便、快速地書寫或修改表單驗證的規則。
    9.1.2. 表單驗證的形式
    驗證WEB頁面中的表單有如下幾種形式:
    9.1.2.1. 服務端批量驗證
    圖 9.2. 服務端批量驗證
    服務端批量驗證是最傳統驗證形式,它將所有表單欄位一次性提交給伺服器來驗證。伺服器對所
    有表單進行批量的驗證后,根據驗證的結果跳轉到不同的結果頁面。
    表單驗證服務指南
    143
    9.1.2.2. 客戶端驗證
    圖 9.3. 客戶端驗證
    客戶端驗證是利用Java Script對用戶輸入的數據進行逐個驗證。
    9.1.2.3. 服務端非同步驗證
    圖 9.4. 服務端非同步驗證
    伺服器非同步驗證是利用Java Script發出非同步AJAX請求,來要求伺服器驗證單個或多個欄位。如
    果網路延遲不明顯,那麼伺服器非同步驗證給用戶的體驗類似於客戶端驗證。
    9.1.2.4. 混合式驗證
    以上幾種驗證手段各有優缺點:
    表單驗證服務指南
    144
    表 9.2. 各種表單驗證的優缺點比較
    驗證形式功能性網路負荷用戶體驗簡單性可靠性
    服務端批量驗證強。
    由於驗證邏輯存
    在於伺服器上,
    可訪問伺服器的
    一切資源,功能
    最強。
    高。
    當用戶填錯任意
    一個欄位時,所
    有的欄位都必須
    在瀏覽器和服務
    器之間來回傳輸
    一次。所以它會
    給網路傳輸帶來
    較高的負荷。
    差。
    由於網路負荷較
    高,造成的響應
    遲緩。此外,驗
    證失敗時必須整
    個頁面被刷新。
    這些會給用戶帶
    來不好的體驗。
    簡單。
    它的實現比較簡
    單。
    可靠。
    相對於其它幾種
    方式,服務端批
    量驗證也是最
    可靠的方式。因
    為Java Script
    可能會失效(因
    為瀏覽器不支
    持、Java Script
    被關閉、網站受
    攻擊等原因),
    但伺服器批量驗
    證總不會失效。
    客戶端驗證弱。
    由於驗證邏輯存
    在於用戶瀏覽器
    上,不能訪問服
    務器資源,因此
    有一些功能無法
    實現,例如:檢
    查驗證碼、確認
    註冊用戶ID未被
    佔用等。
    無。
    在驗證時,不需
    要網路通信,不
    存在網路負荷。
    好。
    響應速度極快,
    用戶體驗最好。
    服務端非同步驗證強。
    由於驗證邏輯存
    在於伺服器上,
    可訪問伺服器的
    一切資源,功能
    也很強。
    低。
    每次驗證,只需
    要發送當前被驗
    證欄位的數據即
    可,網路負荷較
    小。
    較好。
    由於網路負荷
    小,用戶響應遠
    快於服務端批量
    驗證,用戶體驗
    好。
    複雜。
    因為需要JS編
    程。
    不可靠。
    由於下列原
    因,Java Script
    可能會失效,使
    得客戶端驗證被
    跳過:
    • 瀏覽器不支持
    • Java Script被
    關閉
    • 網站受攻擊
    沒有一種驗證方法是完美的。但把它們結合起來就可以克服各自的缺點,達到較完美的境地:
    • 對所有欄位做伺服器端批量驗證,即便Java Script失效,伺服器驗證可作為最後的防線。
    • 只要有可能,就對欄位做客戶端驗證,確保最迅速的響應和較好的用戶體驗。
    • 對於必須訪問伺服器資源的驗證邏輯,例如檢查驗證碼、確認註冊帳戶ID未被佔用等,採用服
    務器非同步驗證,提高用戶體驗。
    以上混合形式的驗證無疑是好的,但是它的實現也比較複雜。
    目前Webx所提供的表單驗證服務並沒有實現客戶端驗證和服務端非同步驗證。這些功能將在後續
    版本中實現。在現階段中,應用開發者必須手工編碼Java Script來實現客戶端驗證和服務端異
    步驗證。
    9.2. 設計
    9.2.1. 驗證邏輯與表現邏輯分離
    很容易想到的一種表單驗證的實現,就是將表單驗證的邏輯內嵌在頁面模板中。例如,某些
    WEB框架實現了一些用來驗證表單的JSP tags。類似下面的樣子:
    表單驗證服務指南
    145
    例 9.1. 將驗證邏輯內嵌在頁面模板中
    <input type="text" name="loginId" value="${loginId}" />
    <form:required value="${loginId}">
    <strong>Login ID is required.</strong>
    </form:required>
    <form:regexp value="${loginId}" pattern="^\w+$">
    <strong>Login ID is invalid.</strong>
    </form:regexp>
    將驗證邏輯內嵌在頁面模板中最大的問題是,驗證邏輯和頁面的表現邏輯完全混合在一起。當你
    需要修改驗證規則時,你必須找出所有的頁面,從複雜的HTML代碼中,一個不漏地找到並修改
    它們。這是一件費時費力的工作,而且很容易出錯。另一方面,嵌在頁面模板中的驗證規則是不
    能被多個頁面共享和復用的。
    Webx表單驗證服務主張驗證邏輯和頁面表現邏輯完全分離。所有的驗證規則都寫在一個單獨的
    配置文件中 —— 頁面模板是不需要關心這些驗證規則的。當你需要修改驗證規則時,只需要修
    改獨立的配置文件就可以了,並不用修改頁面模板。
    9.2.2. 驗證邏輯和應用代碼分離
    另一種容易想到的方法,是把表單驗證的邏輯寫在Java代碼中。例如,在Java代碼中直接調用
    驗證邏輯。更高級一點,也許可以通過annotation機制在Java代碼中定義驗證邏輯,像下面的
    樣子:
    例 9.2. 將驗證邏輯內嵌在Java代碼中
    public class LoginAction {
    @Required
    @Regexp("^\\w+$")
    private String loginId;

    }
    這樣做的問題是,當你需要修改驗證規則時,你必須一個不漏地找到所有定義annotations的
    那些代碼,並修改它們。另一方面,annotation機制不容易擴展,很難方便地增加新的驗證方
    案。
    Webx表單驗證服務主張驗證邏輯和應用代碼完全分離。所有的驗證規則都寫在一個單獨的配置
    文件中 —— 應用程序的代碼是不需要關心這些驗證規則的。當你需要修改驗證規則時,只需要
    修改獨立的配置文件就可以了,並不需要修改程序代碼。
    9.2.3. 表單驗證的流程
    表 9.3. 一個基本的表單驗證流程
    步驟客戶端瀏覽器WEB伺服器頁面效果
    1. 請求表單頁面 →
    ← 返回空白表單
    表單驗證服務指南
    146
    步驟客戶端瀏覽器WEB伺服器頁面效果
    用戶填寫表單,並提交

    2.
    ← 驗證表單數據,如
    果驗證有錯,則返回
    包含錯誤信息的表單頁
    面,並提示出錯信息。
    用戶修改表單,並再次
    提交(重複該步驟直至
    驗證成功) →
    3.
    ← 驗證表單數據,如
    果驗證通過,則轉至下
    一個頁面。通常是顯示
    成功信息。
    9.3. 使用表單驗證服務
    Webx表單驗證服務可用來支持以下幾種類型的表單需求:
    表 9.4. 幾種表單需求
    需求名稱說明
    創建新數據也就是讓用戶在一個空白的表單上填寫數據,並驗證之。例如,註冊一個新帳戶。
    修改老數據也就是讓用戶在已填有數據的表單上進行修改,並驗證之。例如,修改帳戶信息。
    批量創建、修改數據也就是在一個表單中,一次性創建、修改多個數據對象。例如,管理員批量審核帳
    戶。
    9.3.1. 創建新數據
    下面的例子實現了「註冊一個新帳戶」的功能。
    為了實現表單驗證的功能,需要由三個部分配合起來工作:
    表 9.5. 驗證表單所需的部件
    部件名稱說明
    驗證規則也就是form service的配置文件。
    頁面模板通過$form工具,生成表單頁面。
    Java代碼接收表單數據,並作後續處理。
    下面逐個介紹。
    9.3.1.1. 定義驗證規則
    表單驗證服務是一個基於Spring和Spring Ext的服務,可利用Schema來配置。示例如下:
    表單驗證服務指南
    147
    例 9.3. 表單驗證規則示例
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:fm-conditions="http://www.alibaba.com/schema/services/form/conditions"
    xmlns:fm-validators="http://www.alibaba.com/schema/services/form/validators"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
    http://www.alibaba.com/schema/services
    http://localhost:8080/schema/services.xsd
    http://www.alibaba.com/schema/services/form/conditions
    http://localhost:8080/schema/services-form-conditions.xsd
    http://www.alibaba.com/schema/services/form/validators
    http://localhost:8080/schema/services-form-validators.xsd
    http://www.springframework.org/schema/beans
    http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
    ">
    <services:form xmlns="http://www.alibaba.com/schema/services/form/validators">
    <services:group name="register">
    <services:field name="userId" displayName="登錄名">
    <required-validator>
    <message>必須填寫 ${displayName}</message>
    </required-validator>
    <regexp-validator pattern="^[A-Za-z_][A-Za-z_0-9]*$">
    <message>${displayName} 必須由字母、數字、下劃線構成</message>
    </regexp-validator>
    <string-length-validator minLength="4" maxLength="10">
    <message>${displayName} 最少必須由${minLength}個字組成,最多不能超過${maxLength}個字</
    message>
    </string-length-validator>
    </services:field>
    <services:field name="password" displayName="密碼">
    <required-validator>
    <message>必須填寫 ${displayName}</message>
    </required-validator>
    <string-length-validator minLength="4" maxLength="10">
    <message>${displayName} 最少必須由${minLength}個字組成,最多不能超過${maxLength}個字</
    message>
    </string-length-validator>
    <string-compare-validator notEqualTo="userId">
    <message>${displayName} 不能與 ${userId.displayName} 相同</message>
    </string-compare-validator>
    </services:field>
    <services:field name="passwordConfirm" displayName="密碼驗證">
    <required-validator>
    <message>必須填寫 ${displayName}</message>
    </required-validator>
    <string-compare-validator equalTo="password">
    <message>${displayName} 必須和 ${password.displayName} 相同</message>
    </string-compare-validator>
    </services:field>
    </services:group>
    </services:form>
    </beans:beans>
    <form>代表表單驗證服務的配置。從這裡開始定義表單驗證的規則。
    表單驗證服務指南
    148
    可以定義多個groups,每個group有一個唯一的名稱,例如:「register」。每個group
    代表了一組需要驗證的欄位(field)。
    每個field有一個在組中唯一的名稱,例如:「userId」、「password」等。
    每個field又包含了多個驗證規則(validator)。
    每個驗證規則都包含了一段文字描述(message),如果用戶填寫的數據沒有通過當前的
    規則的驗證,那麼用戶將會看到這段文字描述,以解釋出錯的原因。
    圖 9.5. 表單驗證配置文件的結構
    9.3.1.2. 創建表單頁面
    創建表單頁面需要使用一個pull tool工具,配置如下:
    例 9.4. 表單驗證pull tool的配置
    <services:pull xmlns="http://www.alibaba.com/schema/services/pull/factories">
    <form-tool />
    ...
    </services:pull>
    上面的配置定義了一個$form工具。現在你可以在模板中直接使用它。
    表單驗證服務指南
    149
    例 9.5. 表單驗證的頁面模板示例
    #macro (registerMessage $field)
    #if (!$field.valid) $field.message #end
    #end
    <form action="" method="post">
    <input type="hidden" name="action" value="UserAccountAction"/>
    #set ($group = $form.register.defaultInstance)
    <p>用戶註冊</p>
    <dl>
    <dt>用戶名</dt>
    <dd>
    <div>
    <input type="text" name="$group.userId.key" value="$!group.userId.value"/>
    </div>
    <div class="errorMessage">
    #registerMessage ($group.userId)
    </div>
    </dd>
    <dt>密碼</dt>
    <dd>
    <div>
    <input type="password" name="$group.password.key" value="$!group.password.value"/>
    </div>
    <div class="errorMessage">
    #registerMessage ($group.password)
    </div>
    </dd>
    <dt>再輸一遍密碼</dt>
    <dd>
    <div>
    <input type="password" name="$group.passwordConfirm.key" value="$!
    group.passwordConfirm.value"/>
    </div>
    <div class="errorMessage">
    #registerMessage ($group.passwordConfirm)
    </div>
    </dd>
    </dl>
    <p>
    <input type="submit" name="event_submit_do_register" value="立即註冊!"/>
    </p>
    </form>
    HTML form的action值為空,意思是把表單提交給當前頁面。
    這樣,當用戶填寫表單有錯時,應用會停留在當前表單頁面,將表單數據連同錯誤提示一
    起顯示給用戶,要求用戶修改。如果表單驗證通過,應用必須通過重定向操作來轉向下一
    個頁面。
    創建一個register group的實例。
    利用新創建的group對象來生成表單欄位,包括生成欄位的名稱$group.field.key,以及
    欄位的值為$!group.field.value。
    表單驗證服務指南
    150
    定義velocity宏:僅當field驗證通過時(即$group.field.valid=true),才顯示錯誤信
    息。
    對於空白表單和通過驗證的欄位而言,$group.field.valid為true。
    如果驗證失敗的話,顯示驗證出錯消息。這裡通過前面所定義的velocity宏來簡化代碼。
    根據這參數,表單將會被交給UserAccountAction來處理。Action的職責是調用表單驗證
    過程。假如驗證通過,就保存數據,並重定向到下一個頁面。
    根據這個參數,表單被提交以後,系統會調用當前action(即UserAccountAction)
    的doRegister()方法。每個action類中,可以包含多個處理數據的動作,例
    如doCreate、doUpdate、doDelete等。
    上面的Velocity頁面模板演示了怎樣利用表單驗證服務創建一個帳戶註冊的HTML表單。關鍵技
    術解釋如下:
    創建group實例
    $form.register.defaultInstance將會對register group創建一個默認的實例。絕大多
    數情況下,只需要創建唯一的default instance就足夠了。但後面我們會講到創建多實例的
    例子。
    所創建的group instance(如register)必須先在規則配置文件中被定義。
    圖 9.6. 創建一個group實例
    生成表單欄位
    一個表單欄位包含名稱和值兩個部分。
    欄位的名稱為$group.field.key。表單驗證服務會自動生成一個欄位名。這個欄位名被
    設計成僅供系統內部解讀的,而不是讓外界的系統或人來解讀的。它看起來是這個樣子
    的:「_fm.r._0.p」。外界的系統不應該依賴於這個名稱。
    欄位的值為$!group.field.value。它的初始值(即用戶填寫數據之前)是null。但你也
    可以在配置文件中為它指定一個默認的初始值,例如:
    表單驗證服務指南
    151
    例 9.6. 在表單驗證規則中添加默認值
    <services:field name="myfield" defaultValue="mydefault" ...>
    因為值可能是null,因此在velocity中,需要以「$!」來標記它 —— Velocity認為null是
    一個錯誤,除非你以$!來標記它,告訴velocity忽略它。
    需要注意的是,默認值只會影響field的初始值。一旦用戶填寫並提交了表單,那
    么$group.field.value的值將保持用戶所填寫的值不變 —— 不論驗證失敗或成功。
    頁面展現
    一般來說,你需要定義CSS風格以便讓表單的field和錯誤信息能以適當的格式來顯示給用
    戶。展現效果可能是像這個樣子:
    圖 9.7. 在頁面中顯示錶單驗證錯誤信息
    表單系統不應該干預頁面的具體展現方法,以下內容均和表單系統無關。例如:
    • Field展現的方式:textbox、checkbox、hidden field?
    • 錯誤信息的顏色、顯示位置。
    9.3.1.3. 創建Java代碼(action)
    用戶提交表單后,由伺服器端的Java代碼讀取並驗證用戶的數據。
    在Webx中,這個功能通常由action來完成。前文已經提到,在HTML表單中,設
    置action欄位,以及event_submit_do_register提交按鈕,就可以讓Webx框架調
    用UserAccountAction.doRegister()方法。
    下面是UserAccountAction類的實現代碼:
    表單驗證服務指南
    152
    例 9.7. 創建用於處理提交數據的action代碼
    public class UserAccountAction {
    @Autowired
    private FormService formService;
    public void doRegister(Navigator nav) throws Exception {
    Form form = formService.getForm();
    if (form.isValid()) {
    Group group = form.getGroup("register");
    MyUser user = new MyUser();
    group.setProperties(user);
    save(user);
    // 跳轉到註冊成功頁面
    nav.redirectTo("registerSuccess");
    }
    }
    }
    注入form服務。
    取得form對象,form對象中包含若干groups。
    僅當表單驗證成功時,才執行下去。
    取得group對象。Group對象的名稱必須和配置文件以及模板中的group名稱相同。
    將group中的數據灌入bean中。
    處理完數據以後,利用Webx navigation介面跳轉到「註冊成功」頁面。
    例子中的MyUser對象是一個簡單的Java Bean:
    例 9.8. 被灌入group數據的Java Bean
    public static class MyUser {
    private String userId;
    private String password;
    public String getUserId() {
    return userId;
    }
    public void setUserId(String userId) {
    this.userId = userId;
    }
    public String getPassword() {
    return password;
    }
    public void setPassword(String password) {
    this.password = password;
    }
    }
    Group.setProperties()方法將fields的值映射到同名的Java Bean properties中。然而這個
    對應關係是可以改變的,後文會再次講到該問題。
    表單驗證服務指南
    153
    是不是有點複雜?事實上,上面的代碼可以通過Webx的參數注入機制加以簡化。下面的代碼可
    以完成完全相同的功能,但是代碼卻短得多。然而,理解前面的較複雜代碼,將有助於你理解下
    面的簡化代碼。
    例 9.9. 創建用於處理提交數據的action代碼(Annotations簡化版)
    public class UserAccountAction {
    public void doRegister(@FormGroup("register") MyUser user, Navigator nav) throws Exception {
    save(user);
    nav.redirectTo("registerSuccess");
    }
    }
    在這個簡化版代碼中,@FormGroup註解完成了前面複雜代碼中的大部分功能,包括:
    • 驗證表單,如果失敗則不執行action,否則執行doRegister方法。
    • 取得form和register group對象,並將group中的數據注入到MyUser對象中。
    9.3.2. 修改老數據
    在前面的例子中,我們利用表單創建了一個新數據 —— 註冊新帳戶。它是從一個空白表單開始
    的,也就是說,在用戶填寫表單之前,表單是沒有內容的,或只包含默認值的。另一種常見情況
    是修改老數據。例如「修改帳戶資料」。和創建新數據的例子不同,在用戶填寫表單之前,表單
    里已經包含了從資料庫中取得的老數據。
    在創建新數據的模板和代碼中,稍微添加一點東西,就可以實現修改老數據的功能。
    9.3.2.1. 用screen來讀取數據
    修改老數據的第一步,是要取得老的數據。例如,取得要修改的帳戶信息。在Webx中,這個任
    務是由screen來完成的:
    例 9.10. 用screen取得表單驗證的初始數據
    public class UserAccount {
    @Autowired
    private UserManager userManager;
    public void execute(Context context) throws Exception {
    User user = userManager.getUser(getCurrentUser().getId());
    context.put("user", user);
    }
    }
    UserManager是一個業務介面。通過它,可以從資料庫中取得當前登錄帳戶的信息。
    隨後,screen代碼把所取得的user對象放到context中,這樣,就可以在模板中用$user來
    引用它。
    表單驗證服務指南
    154
    9.3.2.2. 表單頁面
    例 9.11. 用來修改數據的頁面模板
    #set ($group = $form.userAccount.defaultInstance)
    $group.mapTo($user)
    ...
    <input type="hidden" name="$group.userId.key" value="$!group.userId.value"/>
    ...
    <input type="text" name="$group.lastName.key" value="$!group.lastName.value"/>
    ...
    #userAccountMessage ($group.lastName)
    ...
    <input type="submit" name="event_submit_do_update" value="修改"/>
    在前面「創建新數據」的頁面上,加上和修改一點內容:
    mapTo的功能是填充表單。
    這行代碼的意思是:用screen中所取得的user對象的值來填充表單,作為表單的初始值。
    和Group.setProperties()方法相反,mapTo將Java Bean properties的值映射到同名的
    fields中。
    保存主鍵。
    和創建新數據不同,在修改老數據時,一般需要在表單中包含主鍵。這個主鍵(user id)
    在資料庫中唯一識別這一數據對象(user)。
    應該避免用戶改變主鍵。最簡便的方法,就是用hidden欄位來保存主鍵。
    這個submit按鈕將引導webx執行UserAccountAction.doUpdate方法。
    需要注意的是,調用mapTo在下列情況下是無效的:
    • 當$user對象不存在(值為null)時,mapTo不做任何事。這樣,你就可以讓「創建新帳
    戶」和「修改帳戶信息」共用同一個模板。在新表單中,由於$user不存在,所以mapTo失
    效;而在更新表單中,就可以從$user中取得初始的數據。
    • 當用戶提交表單以後,mapTo不做任何事。因為mapTo只會影響表單的初始數據。一旦用戶修
    改並提交數據以後,mapTo就不會改變用戶所修改的數據。
    9.3.2.3. 用action來處理數據
    修改老數據的action代碼和創建新數據的action代碼幾乎相同,而且它們可以共享同一
    個UserAccountAction類:
    表單驗證服務指南
    155
    例 9.12. 用來保存提交數據的action
    public class UserAccountAction {
    public void doRegister(...) throws Exception {
    ...
    }
    public void doUpdate(@FormGroup("userAccount") MyUser user,
    Navigator nav) throws Exception {
    save(user);
    nav.redirectTo("updateSuccess");
    }
    }
    通過annotation取得的MyUser對象中,包含了通過hidden欄位傳過來的user id,以及其
    它所有欄位的值。
    9.3.3. 批量創建或修改數據
    有時,我們需要在一個表單頁面中批量創建或修改一批數據。例如,後台管理界面中,管理員可
    以一次審核10個帳戶的信息。每個帳戶的信息格式都是相同的:姓名、性別、年齡、地址等。
    表單驗證服務完全支持這樣的表單。
    9.3.3.1. 用screen來讀取批量數據
    假如你希望做的是批量修改數據,很顯然,你需要在screen代碼中取得所有需要修改的數據。
    例 9.13. 批量讀取數據的screen
    public class BatchUserAccount {
    @Autowired
    private UserManager userManager;
    public void execute(Context context) throws Exception {
    List<User> users = userManager.getUsers(getIds());
    context.put("users", users);
    }
    }
    和修改單個數據的screen代碼不同的是,你需要一次性讀取多個數據對象,並置入到
    context中。
    表單驗證服務指南
    156
    9.3.3.2. 表單頁面
    例 9.14. 批量創建、修改數據的表單頁面模板
    <form action="" method="post">
    <input type="hidden" name="action" value="UserAccountAction"/>
    #foreach($user in $users)
    #set ($group = $form.userAccount.getInstance($user.id))
    $group.mapTo($user)
    ...
    <input type="hidden" name="$group.userId.key" value="$!group.userId.value"/>
    ...
    <input type="text" name="$group.lastName.key" value="$!group.lastName.value"/>
    ...
    #userAccountMessage ($group.lastName)
    ...
    #end
    ...
    <input type="submit" name="event_submit_do_batch_edit" value="批量修改"/>
    </form>
    為了批量創建、修改數據,需要在表單頁面中利用foreach循環來遍曆數據對象。其
    中,$users是由screen放入context中的對象列表。
    對每個數據對象創建一個group實例。
    指定action事件。這個submit按鈕將引導webx執
    行UserAccountAction.doBatchEdit方法。
    在前面的例子中,我們一直使用$form.xyz.defaultInstance來創建默認的group實例。而這
    里,我們改變了用法:$form.userAccount.getInstance($user.id)。每次調用該方法,就
    對一個group生成了一個實例(instance)。
    圖 9.8. 創建多個group實例
    每個instance必須以不同的id來區分。最簡單的方法,就是採用數據對象的唯一id來作為
    group instance的id。在這個例子中,我們採用$user的唯一id($user.id)來區分group
    instances。
    表單驗證服務指南
    157
    前文講過,default instance的field key是這個樣子的:「_fm.r._0.p」。類似的,通
    過getInstance("myid")方法所取得的group中的field key是這樣的:「_fm.u.myid.n」。很
    明顯,form service就是依賴於field key中所包含的group instance id來區分同一group的不
    同instances的。
    因為field key將作為HTML的一部分,所以group instance的id必須為滿足下面的條件:只包含
    英文字母、數字、下劃線、短橫線的字元串。
    頁面的其它部分和創建、修改單個數據的代碼完全相同。只不過它們被循環生成了多次。 最後
    的結果是類似下面的樣子:
    圖 9.9. 批量修改數據的頁面示例
    9.3.3.3. 用action來處理數據
    和前面的例子類似,我們先用傳統的方法來寫action以便闡明原理,再用annotation來簡化
    action代碼。
    表單驗證服務指南
    158
    例 9.15. 用來批量處理數據的action
    public class UserAccountAction {
    @Autowired
    private FormService formService;
    public void doBatchEdit(Navigator nav) throws Exception {
    Form form = formService.getForm();
    if (form.isValid()) {
    Collection<Group> groups = form.getGroups("userAccount");
    for (Group group : groups) {
    MyUser user = new MyUser();
    group.setProperties(user);
    save(user);
    }
    nav.redirectTo("success");
    }
    }
    }
    通過這個方法,可以取得所有名稱為「userAccount」的group instances,包
    括:user1、user2、……。
    取得group實例,除了例子中的form.getGroups(groupName)這種形式以外,還有以下幾種變
    化:
    • 取得所有的group instances,無論其名稱是什麼:Collection<Group> groups =
    form.getGroups();
    • 取得指定group名稱和instance key的group instances:Group user1Group =
    form.getGroup("userAccount", "user1");
    下面是一個簡化版的action,實現完全相同的功能。
    例 9.16. 用來批量處理數據的action(Annotation簡化版)
    public class UserAccountAction {
    public void doBatchEdit(@FormGroup("userAccount") MyUser[] users,
    Navigator nav) throws Exception {
    for (MyUser user : users) {
    save(user);
    }
    nav.redirectTo("success");
    }
    }
    表單驗證服務指南
    159
    9.4. 表單驗證服務詳解
    9.4.1. 配置詳解
    9.4.1.1. 基本配置
    例 9.17. 表單驗證服務的基本配置
    <services:form xmlns="http://www.alibaba.com/schema/services/form/validators">
    <services:group name="group1">
    <services:field name="field1">
    <validator />
    <validator />
    ...
    </services:field>
    <services:field name="field2" />
    ...
    </services:group>
    <services:group name="group2">
    ...
    </services:group>
    ...
    </services:form>
    開始配置表單驗證服務。
    每個表單驗證服務可包含多個groups。
    每個group可包含多個fields。
    每個field可包含多個validators。
    9.4.1.2. Post Only參數
    例 9.18. 配置Post Only參數
    <services:form postOnlyByDefault="true">
    <services:group name="group1" postOnly="true" />
    </services:form>
    如果不指定,postOnlyByDefault的默認值為true。
    如果不指定,那麼postOnly的值取決於postOnlyByDefault。這意味著如果什麼也不設
    置,所有postOnly的實際值均為true。
    如果一個group被設置成postOnly=true,那麼,這個group將不接受通過GET方法提交的數
    據,只允許通過POST方式提交數據。這樣可以略略增加系統的安全性,增加CSRF攻擊的難度。
    表單驗證服務指南
    160
    9.4.1.3. Trimming參數
    例 9.19. 配置Trimming參數
    <services:form>
    <services:group name="group1" trimmingByDefault="true">
    <services:field name="field1" trimming="true" />
    </services:group>
    </services:form>
    如果不指定,trimmingByDefault的默認值為true。
    如果不指定,trimming的值取決於trimmingByDefault。這意味著如果什麼也不設置,所
    有trimming的實際值均為true。
    用戶所提交的字元串數據中,兩端的空白往往是無意義的。這些空白可能會影響驗證規則的準確
    性。
    如果設置了trimming=true參數,那麼表單系統可以自動剪除欄位值兩端的空白字元,例如把「
    my name 」(兩端有空白)轉變成「my name」(兩端無空白)。
    9.4.1.4. Display Name參數
    例 9.20. 配置Display Name參數
    <services:field name="field1" displayName="我的欄位">
    <required-validator>
    <message>必須填寫${displayName}</message>
    </required-validator>
    </services:field>
    如果未指定displayName,那麼其默認為field名稱。也就是的「field1」。
    在validator message中,可以引用${displayName}。這樣做的好處是,validator
    message可以被複制給其它的field,而不是需要更動其信息內容。
    Display Name是對當前field的一個描述信息。
    9.4.1.5. 類型轉換
    例 9.21. 類型轉換的配置
    <services:form converterQuiet="true">
    <services:property-editor-registrar
    class="com.alibaba.citrus.service.configuration.support.CustomDateRegistrar"
    p:format="yyyy-MM-dd" />
    </services:form>
    如果converterQuiet=true,那麼類型轉換失敗時,將取得默認值。否則,拋出異
    常。converterQuiet的默認值為true。
    類型轉換採用Spring Property Editor機制。你可以通過註冊新的registrar來增加新的類型
    轉換方法。這段配置增加了一種將日期轉成字元串的方式(用yyyy-MM-dd格式)。
    下面的操作將用到類型轉換:
    表 9.6. 何時用到類型轉換?
    操作說明
    Group.setProperties(bean) 將Group中的所有fields值注入bean properties。
    表單驗證服務指南
    161
    操作說明
    Group.mapTo(bean) 用bean properties中的值初始化group fields。
    9.4.1.6. 國際化
    表單驗證失敗時,將在頁面上顯示錯誤信息。有兩種方法可以定義錯誤信息:
    • 將錯誤信息直接定義在配置文件中。前文的例子所用的都是這種方案。
    • 將錯誤信息定義在Spring Message Source中,從而支持國際化。
    為了將使用國際化(多語言)的錯誤信息,首先需要定義Spring Message Source。
    例 9.22. 在Spring Message Source中定義錯誤信息
    <bean id="messageSource"
    xmlns="http://www.springframework.org/schema/beans"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
    p:defaultEncoding="GB18030">
    <property name="basenames">
    <list>
    <value>form_msgs</value>
    </list>
    </property>
    </bean>
    這段配置告訴spring去讀取form_msgs開頭的resource bundle文件,例如:
    • form_msgs.properties
    • form_msgs_zh_CN.properties
    • form.msgs_zh_TW.properties
    請注意,Spring是從ResourceLoader中讀取resource bundle文件的。因此,你可能需要配置
    Resource Loading以幫助spring找到這些消息文件。關於資源裝載,請參見第 5 章 Resource
    Loading服務指南。
    使用message source以後,你可以省略validator中的message標籤,但是每
    個validator必須指定id。表單系統將會從message source中查找指定的
    key:「form.<GroupName>.<FieldName>.<ValidatorID>」。
    例 9.23. 配置validator ID
    <services:form>
    <services:group name="register">
    <services:field name="userId">
    <required-validator id="required" />
    </services:field>
    </services:group>
    </services:form>
    指定了validator ID為required,根據格
    式「form.<GroupName>.<FieldName>.<ValidatorID>」,當前validator message的
    key為:「form.register.userId.required」。
    假設message source定義文件及內容如下:
    表單驗證服務指南
    162
    表 9.7. Message Source的內容
    文件名內容
    form_msgs_zh_CN.properties form.register.userId.required = 必須填寫用戶名
    form_msgs.properties form.register.userId.required = User ID is required
    對於以上的message source內容,在中文環境中(locale=zh_CN),將顯示錯誤信息「必
    須填寫用戶名」;而在英文環境中(locale=en_US),將顯示默認的錯誤信息「User ID is
    required」。
    系統的當前locale是由SetLocaleRequestContext來決定的。關
    於SetLocaleRequestContext的設定和使用,請參見第 7 章 Request Contexts功能指南。
    此外,你還可以可以改變message source中key的前綴。
    例 9.24. 改變message source key的前綴
    <services:form messageCodePrefix="myform">
    ...
    </services:form>
    上面的配置將指導表單系統在message source中查找指定的
    key:「myform.GroupName.FieldName.ValidatorID」。
    9.4.1.7. 切分表單服務
    在實際的應用中,有時一個表單規則的配置文件會很長。將一個長文件切分成幾個較短的文件,
    更有利於管理。表單驗證服務支持導入多個form表單服務,從而實現分割較長配置文件的功
    能。
    例 9.25. 切分表單服務
    主配置文件:form.xml:
    <?xml version="1.0" encoding="UTF-8" ?>
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:beans="http://www.springframework.org/schema/beans">
    <beans:import resource="inc/form_part1.xml" />
    <beans:import resource="inc/form_part2.xml" />
    <services:form xmlns="http://www.alibaba.com/schema/services/form/validators" primary="true">
    <services:import form="part1" />
    <services:import form="part2" />
    ...
    </services:form>
    </beans:beans>
    導入包含著子表單服務的spring配置。
    定義主表單服務時,必須指定primary="true"。否則spring將無法區分主從表單服務,從
    而導致注入FormService時失敗。
    導入指定ID的子表單服務。
    表單驗證服務指南
    163
    子表單服務的配置文件:inc/form_part1.xml和inc/form_part2.xml:
    <!-- inc/form_part1.xml -->
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:beans="http://www.springframework.org/schema/beans">
    <services:form id="part1">
    ...
    </services:form>
    </beans:beans>
    <!-- inc/form_part2.xml -->
    <beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:beans="http://www.springframework.org/schema/beans">
    <services:form id="part2">
    ...
    </services:form>
    </beans:beans>
    子表單服務必須指定ID。
    導入子表單服務,意味著將子表單服務中的所有groups導入到主表單的空間。主表單中的
    groups將會覆蓋被導入子表單中的groups。也就是說,假如主表單中存在一個group,它的名
    字和被導入的子表單中的group同名,那麼子表單中的group將被忽略。
    9.4.1.8. Group的繼承和導入
    在實際應用中,你會發現有一些groups的定義很相似。繼承和導入的目的是讓這些相似的
    groups之間可以共享共同的參數、欄位和驗證規則,避免重複定義。
    下面是group繼承的用法:
    例 9.26. 繼承一個group
    <services:form>
    <services:group name="baseGroup">
    <services:field name="field1" >
    <validator1 />
    <validator2 />
    </services:field>
    <services:field name="field2" />
    <services:field name="field3" />
    </services:group>
    <services:group name="subGroup" extends="baseGroup">
    <services:field name="field1">
    <validator3 />
    </services:field>
    <services:field name="field4" />
    </services:group>
    </services:form>
    這段配置中,subGroup繼承了baseGroup。其效果是:
    • baseGroup的參數(postOnly、trimmingByDefault)被subGroup繼承,除非subGroup明
    確指定了該參數。
    表單驗證服務指南
    164
    • baseGroup中fields被subGroup繼承。具體來說:
    • baseGroup中不同名的fields被直接添加到subGroup中。
    • baseGroup中同名的fields被subGroup中的繼承。具體來說:
    • baseGroup field的參數
    (name、displayName、defaultValue、trimming、propertyName)被subGroup
    field繼承,除非subGroup field明確指定了該參數。
    • baseGroup field中的validators被全部添加到subGroup field中。
    因此,上面配置所定義的subGroup和下面配置中的完全等效:
    例 9.27. Group繼承的效果
    <services:form>
    <services:group name="subGroup">
    <services:field name="field1">
    <validator1 /><!-- 來自baseGroup -->
    <validator2 /><!-- 來自baseGroup -->
    <validator3 />
    </services:field>
    <services:field name="field2" /><!-- 來自baseGroup -->
    <services:field name="field3" /><!-- 來自baseGroup -->
    <services:field name="field4" />
    </services:group>
    </services:form>
    另一種和繼承類似的功能是導入:
    例 9.28. 導入groups和fields
    <services:form>
    <services:group name="group1">
    <services:field name="field1" />
    <services:field name="field2" />
    <services:field name="field3" />
    </services:group>
    <services:group name="group2">
    <services:import group="group1" />
    <services:field name="field4" />
    </services:group>
    <services:group name="group3">
    <services:import group="group1" field="field1" />
    <services:field name="field5" />
    </services:group>
    </services:form>
    導入group1中的全部fields。導入以後,group2擁有的fields包
    括:field1、field2、field3和field4。其中,field1、field2、field3均來自
    於group1。
    導入group1中的一個field。導入以後,group3擁有的fields包括:field1和field5。其
    中,field1來自於group1。
    導入有兩種形式,導入全部fields和導入一個field。
    表單驗證服務指南
    165
    導入和繼承都可以使group共享一些內容,但是,
    • 繼承只能有一個base group,而導入可以有多次import;
    • 繼承會合併同名的fields,而導入時則禁止同名fields的。在上例中,假如group2已經有
    了field1,那麼再次導入group1的field1將會報錯。
    9.4.1.9. 設置默認值
    例 9.29. 設置表單欄位的默認值
    <services:field name="field1" defaultValue="defaultValue" />
    當表單被創建時,所有的欄位值默認都是空的 —— 除非你指定了defaultValue。需要注意的
    是,默認值隻影響初始表單。對於用戶已經提交數據的表單不起作用。
    假如一個field需要多個值,例如多選的checkbox,那麼它可以設置一個具有多值的默認值。方
    法是:用逗號分隔多值。像下面的樣子:
    例 9.30. 設置多個值作為表單欄位的默認值
    <services:field name="field1" defaultValue="defaultValue1, defaultValue2, defaultValue3" />
    9.4.1.10. Fields和properties
    Fields和properties是兩個重要辭彙。
    Fields
    Fields是指HTML表單中的form fields,例如一個文本框textbox、複選框
    checkbox、hidden欄位等。
    Fields也是表單驗證的基本單元。
    Properties
    Properties是指Java bean中的數據成員,例如:setName(String)方法定義了一個
    property:name。
    而在表單驗證服務中,
    • Group.setProperties(bean)方法將fields中的值注入到bean的properties中。
    • Group.mapTo(bean)方法將bean properties的值設置成fields的初始值。
    一般情況下,field的名稱就是property的名稱。然而有一些情況下,property名稱和field名稱
    會有出入。這時可以這樣設置:
    例 9.31. 設置不同的property和field名稱
    <services:field name="homeAddress" propertyName="home.address" />
    其中,「homeAddress」為field名稱。如果不指定propertyName的話,表單系統認為property
    名稱也是「homeAddress」。然而在這裡,指定了property名稱為「home.address」 ——
    spring支持這種多級的property。在上面的配置中,
    • 當做Group.setProperties()時,會執行bean.getHome().setAddress(value);
    表單驗證服務指南
    166
    • 而做Group.mapTo()時,會執行bean.getHome().getAddress()。
    9.4.1.11. Validator messages
    每一個validator都可以附帶一段message文字,這段文字會在validator驗證失敗時顯示給用戶
    看。配置validator message可以有下面兩種寫法:
    例 9.32. Validator message的兩種寫法
    <string-length-validator minLength="4" maxLength="10">
    <message>登錄名最少必須由4個字組成,最多不能超過10個字</message>
    </string-length-validator>
    或者,你可以這樣寫:
    <string-length-validator minLength="4" maxLength="10">
    <message>${displayName} 最少必須由${minLength}個字組成,最多不能超過${maxLength}個字</message>
    </string-length-validator>
    第二種形式使用了替換變數,例如:${displayName}等。這種方法有較多好處:
    • 易複製 —— 假如有多個fields中都包含string-length-validator,由於每個fields的名稱
    (displayName)、validator的參數(minLength、maxLength)都不同,第一種形式是不
    可複製的,而第二種形式是通用的、可複製的。
    • 易維護 —— 當validator的參數被修改時,例如minLength被修改,對於第一種形式,你必須
    同時修改message字元串 —— 這很可能被忘記;而第二種形式就不需要修改message字元
    串。
    事實上,validator message是一個JEXL表達式,其格式詳見:http://
    commons.apache.org/jexl/reference/syntax.html。 下面列出了在message中可用的變數
    和工具。
    表 9.8. Validator message中可用的變數和工具
    分類可用的變數和工具
    當前Validator中的所有
    properties
    不同的validator有不同的properties。
    例如,對於<string-length-validator minLength="4" maxLength="10">,
    可取得的變數包括${minLength}、${maxLength}
    ${displayName} Field顯示名
    ${defaultValue} 默認值(String)
    ${defaultValues} 默認值數組(String[])
    ${value} 當前field的值(Object)
    ${values} 當前field的一組值(Object[])
    當前Field對象中的所有
    properties
    特定類型的值,例如:${booleanValue}、${intValue}等。
    當前Group中的其它Field對象例如:${userId},${password}等。
    如果想取得其值,必須這樣寫:${userId.value}
    也可取得其它Field properties,例如:${userId.displayName}
    當前的Group對象${group}
    當前的Form對象${form}
    System properties 所有從System.getProperties()中取得的值,
    表單驗證服務指南
    167
    分類可用的變數和工具
    例如:${user.dir}、${java.home}等
    小工具${utils},其中包含了很多靜態方法。
    例如:${utils.repeat("a", 10)}將會生成10個「a」。
    詳見com.alibaba.citrus.util.Utils類的API文檔。
    9.4.2. Validators
    每個field都可以包含多個validators。Validators是用來驗證當前field的正確性的。表單驗證系
    統提供了很多常用的validators。然而,如果不夠用,你還可以隨時擴展出新的validators。
    9.4.2.1. 驗證必選項:<required-validator>
    例 9.33. 配置<required-validator>
    <services:field name="field1" displayName="我的欄位">
    <required-validator>
    <message>必須填寫${displayName}</message>
    </required-validator>
    </services:field>
    這是最常用的一個驗證器。它的功能是確保用戶填寫了欄位,即確保欄位值非空。
    需要注意的是,必選項驗證器會受到trimming參數的影響。假如trimming=true(默認值),
    而用戶輸入了一組空白字元,那麼仍然被認為「欄位值為空」;反之,如果trimming=false,
    當用戶輸入空白時,會被認為「欄位值非空」。
    除了必選項驗證器以外,其它絕大多數的驗證器並不會判斷欄位值是否為空。例如:
    例 9.34. 絕大多數的驗證器並不會判斷欄位值是否為空
    <services:field name="userId" displayName="登錄名">
    <regexp-validator pattern="^[A-Za-z_][A-Za-z_0-9]*$">
    <message>${displayName} 必須由字母、數字、下劃線構成</message>
    </regexp-validator>
    </services:field>
    在這個例子中,即便用戶什麼也沒填,<regexp-validator>也會通過驗證。換言
    之,<regexp-validator>只負責當欄位值非空時,檢查其是否符合特寫的格式。
    因此通常需要將必選項驗證器和其它驗證器配合起來驗證。如下例:
    例 9.35. 將必選項驗證器和其它驗證器配合起來驗證
    <services:field name="userId" displayName="登錄名">
    <required-validator>
    <message>必須填寫 ${displayName}</message>
    </required-validator>
    <regexp-validator pattern="^[A-Za-z_][A-Za-z_0-9]*$">
    <message>${displayName} 必須由字母、數字、下劃線構成</message>
    </regexp-validator>
    </services:field>
    這樣配置以後,就可以確保:
    • 欄位值非空;
    表單驗證服務指南
    168
    • 欄位值符合特定格式。
    9.4.2.2. 驗證字元串長度:<string-length-validator>
    例 9.36. 配置<string-length-validator>
    <services:field name="field1" displayName="我的欄位">
    <string-length-validator minLength="4" maxLength="10">
    <message>${displayName} 最少必須由${minLength}個字組成,最多不能超過${maxLength}個字</message>
    </string-length-validator>
    </services:field>
    該驗證器確保用戶輸入的欄位值的字元數在確定的範圍內。
    可用的參數:
    表 9.9. <string-length-validator>的可用參數:
    參數名說明
    minLength 代表最少字元數,如不設置minLength代表最代表不設下限。
    maxLength 代表最多字元數,如果不設置maxLength則代表不設上限,允許任意多個字元。
    9.4.2.3. 驗證字元串位元組長度:<string-byte-length-validator>
    例 9.37. <string-byte-length-validator>的配置
    <services:field name="field1" displayName="我的欄位">
    <string-byte-length-validator minLength="4" maxLength="10" charset="UTF-8">
    <message>${displayName} 最少必須由${minLength}個位元組組成,最多不能超過${maxLength}個位元組</message>
    </string-byte-length-validator>
    </services:field>
    該驗證器也是用來驗證字元值的長度的。但是和前面所講的<string-length-validator>不同
    的是,<string-byte-length-validator>會將字元串先轉換成位元組串,然後判斷這個位元組串
    的長度。
    為什麼需要判定位元組的長度呢?因為在資料庫中,常用位元組長度而不是字元長度來表示一個欄位
    的長度的。例如:varchar(20)代表該資料庫欄位可接受的位元組長度為20位元組,超過部分將被
    截斷。20位元組可填入:
    • 20個英文字母、數字,
    • 或者6個UTF-8編碼的漢字,
    • 或者10個GBK編碼的漢字。
    可見20位元組所能容納的字元數是不確定的,取決於字元的類型(中文、英文、數字等),以及
    字符集編碼的類型(ISO-8859-1、UTF-8、GBK等)。
    可用的參數:
    表 9.10. <string-byte-length-validator>的可用參數:
    參數名說明
    minLength 代表最少位元組數,如不設置minLength代表最代表不設下限。
    maxLength 代表最多位元組數,如果不設置maxLength則代表不設上限,允許任意多個字元。
    charset 用來將字元串轉換成位元組,如不設置,則取當前線程的上下文編碼。
    表單驗證服務指南
    169
    9.4.2.4. 比較字元串:<string-compare-validator>
    例 9.38. <string-compare-validator>的配置
    <services:field name="field1" displayName="我的欄位">
    <string-compare-validator notEqualTo="field2">
    <message>${displayName} 不能與 ${field2.displayName} 相同</message>
    </string-compare-validator>
    </services:field>
    該驗證器將當前欄位的值和另一個欄位比較。
    可用的參數:
    表 9.11. <string-compare-validator>的可用參數:
    參數名說明
    equalTo 確保當前欄位值和指定的另一欄位的值相同。
    notEqualTo 確保當前欄位值和指定的另一欄位的值不相同。
    ignoreCase 如果為true,則比較時忽略大小寫。默認為false,即比較大小寫。
    9.4.2.5. 用正則表達式驗證:<regexp-validator>
    例 9.39. <regexp-validator>的配置
    <services:field name="field1" displayName="我的欄位">
    <regexp-validator pattern="^[A-Za-z_][A-Za-z_0-9]*$">
    <message>${displayName} 必須由字母、數字、下劃線構成</message>
    </regexp-validator>
    </services:field>
    該驗證器用正則表達式來驗證字元串的格式。
    可用的參數:
    表 9.12. <regexp-validator>的可用參數:
    參數名說明
    pattern 用來匹配字元串的正則表達式。需要注意的是:
    • 表達式為部分匹配,例如:表達式「abc」可以匹配用戶輸入「xabcy」。如果期
    望匹配完整字元串,必須使用「^」和「$」標識符。例如表達式「^abc$」只能匹
    配「abc」而不能匹配「xabcy」。
    • 表達式支持否定匹配,例如:表達式「!abc」可以匹配所有不包含「abc」的字元串。
    9.4.2.6. 驗證電子郵件地址:<mail-address-validator>
    例 9.40. <mail-address-validator>的配置
    <services:field name="field1" displayName="我的欄位">
    <mail-address-validator>
    <message>${displayName} 必須是合法電子郵件地址</message>
    </mail-address-validator>
    </services:field>
    該驗證器用來確保用戶輸入了合法的電子郵件地址。
    表單驗證服務指南
    170
    事實上,該驗證器使用了一個比較寬鬆的正則表達式來驗證電子郵件地址:「^\S+@[^\.]\S*
    $」。假如你覺得這個正則表達式不足以驗證你所需要的郵件地址,你可以利用<regexpvalidator>
    和自定義的正則表達式來直接驗證。
    9.4.2.7. 驗證數字:<number-validator>
    例 9.41. <number-validator>的配置
    <services:field name="field1" displayName="我的欄位1">
    <number-validator>
    <message>${displayName} 必須是數字</message>
    </number-validator>
    </services:field>
    <services:field name="field2" displayName="我的欄位2">
    <number-validator numberType="int" lessThan="100">
    <message>${displayName} 必須是小於${lessThan}的整數</message>
    </number-validator>
    </services:field>
    該驗證器用來確保用戶輸入了合法的數字。數字的合法性包括:格式的合法和範圍的合法。
    可用的參數:
    表 9.13. <number-validator>的可用參數:
    參數名說明
    numberType 數字的類型。可用的類型為:int、long、float、double、bigDecimal。如不設置,
    默認值為int。
    equalTo 可選數字範圍:要求數字等於指定值。
    notEqualTo 可選數字範圍:要求數字不等於指定值。
    lessThan 可選數字範圍:要求數字小於指定值。
    lessThanOrEqualTo 可選數字範圍:要求數字小於或等於指定值。
    greaterThan 可選數字範圍:要求數字大於指定值。
    greaterThanOrEqualTo 可選數字範圍:要求數字大於或等於指定值。
    9.4.2.8. 比較數字:<number-compare-validator>
    例 9.42. <number-compare-validator>的配置
    <services:field name="field1" displayName="我的欄位">
    <number-compare-validator greaterThanOrEqualTo="field2" lessThan="field3">
    <message>${displayName} 必須是位於 ${field2.displayName} 和 ${field3.displayName}之間的數字</
    message>
    </number-compare-validator>
    </services:field>
    該驗證器將當前欄位的值和另一個欄位比較。
    可用的參數:
    表 9.14. <number-compare-validator>的可用參數:
    參數名說明
    numberType 數字的類型。可用的類型為:int、long、float、double、bigDecimal。如不設置,
    默認值為int。
    equalTo 可選數字範圍:要求數字等於指定欄位的值。
    表單驗證服務指南
    171
    參數名說明
    notEqualTo 可選數字範圍:要求數字不等於指定欄位的值。
    lessThan 可選數字範圍:要求數字小於指定欄位的值。
    lessThanOrEqualTo 可選數字範圍:要求數字小於或等於指定欄位的值。
    greaterThan 可選數字範圍:要求數字大於指定欄位的值。
    greaterThanOrEqualTo 可選數字範圍:要求數字大於或等於指定欄位的值。
    9.4.2.9. 驗證日期:<date-validator>
    例 9.43. <date-validator>的配置
    <services:field name="field1" displayName="我的欄位">
    <date-validator format="yyyy-MM-dd">
    <message>${displayName} 必須是日期,格式為 ${format}</message>
    </date-validator>
    </services:field>
    該驗證器用來確保用戶輸入正確的日期格式,也可以限定日期的範圍。
    可用的參數:
    表 9.15. <date-validator>的可用參數:
    參數名說明
    format 日期的格式,如不指定,默認為yyyy-MM-dd。
    minDate 可選的日期範圍:最早的日期。該日期格式也是用format參數來表示的。
    maxDate 可選的日期範圍:最晚的日期。該日期格式也是用format參數來表示的。
    9.4.2.10. 驗證上傳文件:<uploaded-file-validator>
    例 9.44. <uploaded-file-validator>的配置
    <services:field name="picture" displayName="產品圖片">
    <uploaded-file-validator extension="jpg, gif, png">
    <message>${displayName}不是合法的圖片文件</message>
    </uploaded-file-validator>
    <uploaded-file-validator maxSize="100K">
    <message>${displayName}不能超過${maxSize}位元組</message>
    </uploaded-file-validator>
    </services:field>
    該驗證器用來驗證用戶上傳文件的大小、類型等信息。
    可用的參數:
    表 9.16. <date-validator>的可用參數:
    參數名說明
    minSize 最小文件尺寸。可使用K/M等單位,例如:10K、1M等。
    maxSize 最大文件尺寸。可使用K/M等單位,例如:10K、1M等。
    extension 允許的文件名後綴,多個後綴以逗號分隔。例如:gif、jpg、png。
    注意,文件名是由瀏覽器傳遞給伺服器的,因此驗證器並不能保證保證文件確實是文件名
    後綴聲明的格式。
    例如,xxx.jpg有可能是一個exe可執行文件。
    contentType 允許的文件類型,多個類型以逗號分隔。
    表單驗證服務指南
    172
    參數名說明
    例如:image/gif, image/jpeg, image/pjpeg, image/jpg, image/png。
    注意,contentType是由瀏覽器傳遞給伺服器的,因此驗證器並不能保證文件確實是瀏覽
    器所聲明的格式。
    其次,有些瀏覽器不會傳送contentType。因此推薦使用extension文件名後綴驗證,來
    取代contentType驗證。
    注意:(請補充閱讀第 7 章 Request Contexts功能指南)
    • 上傳文件驗證只能檢查瀏覽器所聲稱的文件名後綴和類型,並不能保證文件名後綴和類型屬
    實。如果你希望進一步檢查文件的內容,可以結合request-contexts/parser/filter的功
    能。
    • 假如設置了request-contexts/parser/uploaded-file-whitelist,那麼不符合要求
    的文件會在進入表單驗證之前被刪除。因此上傳文件驗證器的extension參數必須存在
    於uploaded-file-whitelist.extensions列表當中。
    • Upload服務可限制請求的總尺寸,大於該尺寸的請求會被全部忽略,以保證伺服器的安全性
    —— 這意味著對於這類超大請求,你根本讀不到這個請求中的所有參數,當然也不可能執行
    到表單驗證的階段。
    • Upload服務可以限制每個上傳文件的尺寸,大於指定尺寸的文件會被刪除。但只要請求的
    總尺寸還是在許可範圍內,那麼除了被刪文件以外,其它的參數和文件還是可以被取得的。
    因此,上傳文件驗證器的maxSize必須小於upload服務中設置的單個文件的最大尺寸才有意
    義。
    9.4.2.11. 驗證CSRF token
    CSRF是跨站請求偽造(Cross-site request forgery)的意思,它是一種常見的WEB網站攻擊方
    法。攻擊者通過各種方法偽造一個請求,模仿用戶提交表單的行為,從而達到修改用戶的數據,
    或者執行特定任務的目的。為了假冒用戶的身份,CSRF攻擊常常和XSS攻擊配合起來做,但也
    可以通過其它手段,例如誘使用戶點擊一個包含攻擊的鏈接。
    通過CSRF token,可以確保該請求確實是用戶本人填寫表單並提交的,而不是第三者偽造的,
    從而避免CSRF攻擊。CSRF token驗證器是用來確保表單中包含了CSRF token。
    CSRF token的驗證是每個表單都需要的安全功能,所以通常可利用group的繼承功能來定義
    CSRF token驗證器。
    例 9.45. <csrf-validator>的配置
    <services:group name="csrfTokenCheckGroup">
    <services:field name="csrfToken">
    <csrf-validator>
    <message>您提交的表單已過期</message>
    </csrf-validator>
    </services:field>
    </services:group>
    <services:group name="group1" extends="csrfTokenCheckGroup">
    ...
    </services:group>
    除了表單驗證以外,實現CSRF驗證還需要其它幾個步驟:
    • 定義pull tool。
    表單驗證服務指南
    173
    例 9.46. 定義CSRF pull tool
    <services:pull xmlns="http://www.alibaba.com/schema/services/pull/factories">
    ...
    <csrfToken />
    ...
    </services:pull>
    定義了pull tool以後,可以在模板中以$csrfToken來引用它。
    • 在每一個表單中創建一個保存著CSRF token的hidden欄位。
    例 9.47. 在模板中插入包含CSRF token的hidden欄位
    <form action="" method="post">
    $csrfToken.hiddenField
    <input type="hidden" name="action" value="LoginAction"/>
    ...
    </form>
    調用$csrfToken.hiddenField以後將創建一個包含CSRF long live token的hidden字
    段,等同於調用$csrfToken.longLiveHiddenField。
    有兩種CSRF token,你也可以用下面兩種方法來創建它們:
    • 創建unique token:$csrfToken.uniqueHiddenField。這種類型的token不僅能防
    止CSRF攻擊,還能防止重複提交表單。
    • 創建long live token:$csrfToken.longLiveHiddenField。這種類型的token只能
    防止CSRF攻擊,不能防止重複提交表單。
    • 在pipeline中驗證token。
    例 9.48.
    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    ...
    <checkCsrfToken />
    ...
    </services:pipeline>
    此處可以指定一個tokenKey參數。如果不指定,將使用默認的token
    key:_csrf_token。
    • 在表單驗證中指定postOnly=true(默認值),有助於提高CSRF攻擊的難度。
    例 9.49. 配置Post Only參數
    <services:form postOnlyByDefault="true">
    </services:form>
    9.4.2.12. Custom Error – 由action來驗證數據
    有一些情況下,由validator來驗證數據並不方便。例如,當我們註冊帳戶時,即便用戶名的格
    式完全正確(由字母和數字構成,並在指定的字數範圍之內),也有可能註冊不成功的。原因是
    表單驗證服務指南
    174
    當前用戶名已經被其它用戶註冊使用了。而判斷用戶名是否可用,最簡單的辦法是在action中
    通過訪問資料庫來確定。
    Custom Error「驗證器」就是用來滿足這個需求。
    例 9.50. <custom-error>的配置
    <services:group name="register">
    <services:field name="userId" displayName="登錄名">
    <required-validator>
    <message>必須填寫 ${displayName}</message>
    </required-validator>
    <regexp-validator pattern="^[A-Za-z_][A-Za-z_0-9]*$">
    <message>${displayName} 必須由字母、數字、下劃線構成</message>
    </regexp-validator>
    <string-length-validator minLength="4" maxLength="10">
    <message>${displayName} 最少必須由${minLength}個字組成,最多不能超過${maxLength}個字</message>
    </string-length-validator>
    <custom-error id="duplicatedUserId">
    <message>登錄名「${userId}」已經被人注掉了,請嘗試另一個名字</message>
    </custom-error>
    </services:field>
    ...
    </services:group>
    Custom Error「驗證器」不做任何驗證 —— 它把驗證的責任交給action來做。但是除此以
    外,它和其它驗證器完全相同 —— 你可以設置message,甚至可以用message source
    來實現國際化的錯誤提示。你不需要把錯誤提示寫在代碼中,或者啟用另一種錯誤提示方
    案。
    對於custom error,需要在action中有相應的支持,否則不會自動生效:
    例 9.51. 用來生成custom error的action代碼
    public void doRegister(@FormField(name = "userId", group = "register") CustomErrors err,
    ...) throws Exception {
    try {
    ...
    } catch (DuplicatedUserException e) {
    Map<String, Object> params = createHashMap();
    params.put("userId", user.getUserId());
    err.setMessage("duplicatedUserId", params);
    }
    }
    注入CustomErrors介面。和注入Field的方法相同,通過@FormField註解指
    明CustomErrors所在的 group名稱以及field名稱。
    調用CustomeErrors.setMessage()方法。其中,「duplicatedUserId」就是配置文件
    中<custom-error>的id。
    第二個參數params是可選的。它是一個Map,其中的所有值,都可以在<customerror>
    的message中訪問到。例如,這裡指定的userId參數值,就可以被message表達
    式${userId}所訪問。
    9.4.2.13. 條件驗證
    條件驗證就是讓某些validator僅在條件滿足時才驗證。條件驗證有兩種,單分支和多分支驗
    證。
    表單驗證服務指南
    175
    例 9.52. 單分支條件驗證
    <services:field name="other" displayName="其它建議">
    <if test="commentCode.value == 'other'">
    <required-validator>
    <message>必須填寫 ${displayName}</message>
    </required-validator>
    </if>
    </services:field>
    例 9.53. 多分支條件驗證
    <services:field name="field1" displayName="我的欄位">
    <choose>
    <when test="expr1">
    <validator />
    </when>
    <when test="expr2">
    <validator />
    <validator />
    </when>
    <otherwise>
    <validator />
    </otherwise>
    </choose>
    </services:field>
    在上面的配置示例中,<if>和<when>都支持的test參數,其內容為JEXL表達式。其格式詳
    見:http://commons.apache.org/jexl/reference/syntax.html。JEXL表達式中可用的變數
    同Validator messages中可用的變數,請參見:第 9.4.1.11 節 「Validator messages」。 除
    此之外,條件分支還支持任意自定義的條件,方法是:
    例 9.54. 在條件驗證中自定義條件
    <if xmlns:fm-conditions="http://www.alibaba.com/schema/services/form/conditions">
    <fm-conditions:condition class="xxx" />
    </if>
    ...
    <when xmlns:fm-conditions="http://www.alibaba.com/schema/services/form/conditions">
    <fm-conditions:condition class="xxx" />
    </when>
    實現類只需要實現Condition介面就可以了。
    9.4.2.14. 多值驗證
    HTML表單欄位均支持多值。比如:
    例 9.55. 具有多值的HTML表單欄位
    <p>你喜歡吃哪些食物?</p>
    <input type="checkbox" name="poll" value="italian" /> 義大利菜
    <input type="checkbox" name="poll" value="french" /> 法國菜
    <input type="checkbox" name="poll" value="chinese" /> 中國菜
    當用戶選擇了多個複選框並提交以後,在表單系統中體現為數組:
    field.getName(); // "poll"
    field.getValues(); // "italian", "french", "chinese"
    表單驗證服務指南
    176
    不僅僅是複選框,任何其它類型的輸入框(textbox、hidden field、file upload等)都支持多
    值。然而前面所說的所有validator只對field中的第一個值進行驗證。假如我希望對多個值同時
    進行驗證,該怎麼辦呢?表單驗證服務提供了一組用於多值驗證的validators。
    9.4.2.14.1. 驗證值的數量
    例 9.56. <multi-values-count-validator>的配置
    <services:field name="poll" displayName="調查">
    <multi-values-count-validator minCount="1" maxCount="3">
    <message>至少選擇${minCount}項,最多選擇${maxCount}項</message>
    </multi-values-count-validator>
    </services:field>
    對用戶提交的值的數量進行驗證,迫使用戶選擇1-3項他喜歡的食物。
    9.4.2.14.2. 要求所有值均通過驗證
    例 9.57. <all-of-values>的配置
    <all-of-values>
    <message>${allMessages}</message>
    <validator />
    <validator />
    </all-of-values>
    只有當前欄位的所有值都符合要求,<all-of-values>驗證才會通過。其message支
    持${allMessages},它是一個List列表,可以用來顯示所有未通過驗證的validators的消息。
    9.4.2.14.3. 要求任意一個值通過驗證
    例 9.58. <any-of-values>的配置
    <any-of-values>
    <message>至少有一個${displayName}要符合要求</message>
    <validator />
    <validator />
    </any-of-values>
    只要當前欄位有一個值通過驗證,<any-of-values>驗證就會通過。其message支
    持${valueIndex}代表被驗證通過的值的序號;支持${allMessages},它是一個List列表,
    可以用來顯示所有未通過驗證的validators的消息。
    9.4.2.14.4. 要求任意一個值都不通過驗證
    例 9.59. <none-of-values>的配置
    <none-of-values>
    <message>所有${displayName}都不能符合要求</message>
    <validator />
    <validator />
    </none-of-values>
    只要當前欄位有一個值通過驗證,<none-of-values>驗證就會失敗。
    9.4.2.15. 組合驗證
    組合驗證就是將validators組合起來,類似於Java中的and(&&)、or(||)、not(!)等操
    作符的功能。
    表單驗證服務指南
    177
    9.4.2.15.1. 要求所有validators通過驗證
    例 9.60. <all-of>的配置
    <all-of>
    <validator />
    <validator />
    </all-of>
    只要有一個validator通不過驗證,就失敗。<all-of>不需要設置message,它的message就是
    第一個沒有通過驗證的validator的message。
    9.4.2.15.2. 要求任意一個validators通過驗證
    例 9.61. <any-of>的配置
    <any-of>
    <message>${allMessages}</message>
    <validator />
    <validator />
    </any-of>
    只要有一個validator通過驗證,<any-of>驗證就會通過。其message支持${allMessages},
    它是一個List列表,可以用來可以用來顯示所有未通過驗證的validators的消息。
    9.4.2.15.3. 要求任何一個validators都通不過驗證
    例 9.62. <none-of>的配置
    <none-of>
    <message>${displayName}不符合要求</message>
    <validator />
    <validator />
    </none-of>
    只要有一個validator通過驗證,<none-of>驗證就會失敗。
    9.4.3. Form Tool
    Form Tool是一個pull tool工具,配置如下:
    例 9.63. Form Tool的配置
    <services:pull xmlns="http://www.alibaba.com/schema/services/pull/factories">
    <form-tool />
    ...
    </services:pull>
    上面的配置定義了一個$form工具。可以在模板中直接使用它。下頁簡單介紹在模板
    中,$form工具的用法。
    9.4.3.1. Form API
    表 9.17. 有關Form的API
    API用法說明
    #if ($form.valid) ... #end 判斷當前form是否驗證為合法,或者未經過驗
    證。
    #set ($group = $form.group1.defaultInstance) 取得group1的默認實例,如果不存在,則創建
    之。
    表單驗證服務指南
    178
    API用法說明
    #set ($group = $form.group1.getInstance("id")) 取得group1的指定id的實例,如果不存在,則
    創建之。
    #set ($group = $form.group1.getInstance("id", false)) 取得group1的指定id的實例,如果不存在,則
    返回null。
    #foreach ($group in $form.groups) ... #end 遍歷當前form中所有group實例。
    #foreach ($group in $form.getGroups("group1")) … #end 遍歷當前form中所有名為group1的實例。
    9.4.3.2. Group API
    表 9.18. 有關Group的API
    API用法說明
    #if ($group.valid) … #end 判斷當前group是否驗證為合法,或者未經過
    驗證(即初始表單)
    #if ($group.validated) ... #end 判斷當前group是否經過驗證(初始表單為未
    經過驗證的表單)
    $group.field1 取得field1
    #foreach ($field in $group.fields) ... #end 遍歷當前group中所有的fields
    $group.mapTo($bean) 將bean中的properties設置成group的初始
    值。 該操作只對初始表單有效。如果bean為
    null則忽略該操作。
    9.4.3.3. Field API
    創建一個HTML表單欄位
    例 9.64. 創建一個HTML表單欄位
    $field.displayName
    <input type="text" name="$field.key" value="$!field.value" />
    其中,displayName來自於配置文件。將displayName顯示在頁面中的好處是,確保頁面與
    出錯信息的措辭一致。
    判斷驗證合法性,並顯示錯誤消息
    例 9.65. 判斷驗證合法性,並顯示錯誤消息
    #if (!$field.valid)
    <div class="error">$field.message</div>
    #end
    取得多值
    例 9.66. 取得多值
    #foreach ($value in $field.values) ... #end
    創建checkbox和radiobox的默認值
    例 9.67. 創建checkbox和radiobox的默認值
    <input type="hidden" name="$field.absentKey" value="$value" />
    或者簡化為:
    $field.getAbsentHiddenField($value)
    表單驗證服務指南
    179
    Checkbox和radiobox有一個特性,當用戶沒有選中它們時,它們是沒有值的(就像不存
    在一樣)。這點對於表單驗證會帶來不便。
    表單服務支持一種特殊的absentKey。通過它,可以為checkbox/radiobox設置默認值。
    這樣,當用戶沒有選中任何checkbox或radiobox時,這個值就成為field的值。
    創建附件
    Field可以帶一個附件。附件是一個對象,被序列化保存在hidden欄位中。在下一次請求的
    時候,附件可以被恢復成對象。通過附件,可以讓應用程序在表單中攜帶一些附加信息。
    下頁的代碼會生成一個hidden欄位,將$obj序列化保存在其中:
    例 9.68. 創建附件
    $field.setAttachment($obj)
    $field.attachmentHiddenField
    當你要取得它時,只要這樣:
    #set ($obj = $field.attachment)
    判斷是否有附件:
    #if ($field.hasAttachment()) … #end
    清除附件:
    $field.cleanAttachment()
    9.4.4. 外部驗證
    表單驗證服務是被設計成在一個應用的內部使用的。它所生成的field key(像這個樣
    子:「_fm.r._0.p」)是不穩定的,可能隨著配置的變化而變化。所以外界系統不可依賴於這
    些內部的keys。
    如果真的需要讓外部來提交並驗證表單,可以做一個screen來轉發這個請求。Screen的代碼像
    這個樣子:
    例 9.69. 轉發外部表單請求
    public class RemoteRegister {
    public void execute(ParameterParser params, Form form, Navigator nav) throws Exception {
    Group group = form.getGroup("register");
    group.init();
    group.getField("userId").setValue(params.getString("userId"));
    group.getField("password").setValue(params.getString("password"));
    group.getField("passwordConfirm").setValue(params.getString("password"));
    group.validate();
    nav.forwardTo("register")
    .withParameter("action", "registerAction")
    .withParameter("eventSubmitDoRegister", "yes");
    }
    }
    創建register group的實例。
    表單驗證服務指南
    180
    將request parameters中的參數設置到form group中。需要注意的是,request參數名和
    field名稱不必相同。
    驗證表單。
    內部重定向到register頁面,並指明action參數。
    只需要訪問下面的URL就可以實現從系統外部註冊帳戶的功能:
    http://localhost:8081/myapp/remote_register.do?userId=xxx&password=yyy
    9.5. 本章總結
    表單服務是一個比較複雜但也相當強大的服務。雖然目前它還不支持客戶端驗證和服務端非同步驗
    證功能,但下一步會加上這些功能。
    表單服務最重要的設計思想是:將驗證規則與頁面以及業務邏輯完全分離,使驗證規則的擴展和
    維護變得非常容易。
    部分 IV. Webx應用實作
    182
    第 10 章 創建第一個Webx應用 ...................................................................................... 183
    10.1. 準備工作 ..................................................................................................... 183
    10.1.1. 安裝JDK ........................................................................................... 183
    10.1.2. 安裝和配置maven ............................................................................ 183
    10.1.3. 安裝集成開發環境 ............................................................................. 183
    10.2. 創建應用 ..................................................................................................... 183
    10.3. 運行應用 ..................................................................................................... 184
    10.4. 提問和解答 .................................................................................................. 186
    10.4.1. 在生產環境的應用上,也會出現前述的「開發者首頁」嗎? .................. 186
    10.4.2. 「開發模式」是什麼意思? ................................................................ 187
    10.4.3. 所生成的應用中包含了什麼? ............................................................. 187
    第 11 章 Webx日誌系統的配置 ...................................................................................... 189
    11.1. 名詞解釋 ..................................................................................................... 189
    11.1.1. 日誌系統(Logging System) ........................................................... 189
    11.1.2. 日誌框架(Logging Framework) .................................................... 190
    11.2. 在Maven中組裝日誌系統 ............................................................................. 190
    11.2.1. 在Maven中配置logback作為日誌系統 .............................................. 192
    11.2.2. 在Maven中配置log4j作為日誌系統 .................................................... 195
    11.3. 在WEB應用中配置日誌系統 ........................................................................... 198
    11.3.1. 設置WEB應用 .................................................................................... 198
    11.3.2. 定製/WEB-INF/logback.xml(或/WEB-INF/log4j.xml) .................. 200
    11.3.3. 同時初始化多個日誌系統 ................................................................... 203
    11.4. 常見錯誤及解決 ........................................................................................... 205
    11.4.1. 查錯技巧 ........................................................................................... 205
    11.4.2. 異常信息:No log system exists ........................................................ 205
    11.4.3. 異常信息:NoSuchMethodError:
    org.slf4j.MDC.getCopyOfContextMap() ................................................... 206
    11.4.4. STDERR輸出:Class path contains multiple SLF4J bindings ............... 206
    11.4.5. 看不到日誌輸出 ................................................................................. 206
    11.5. 本章總結 ..................................................................................................... 208
    183
    第 10 章 創建第一個Webx應用
    10.1. 準備工作 ............................................................................................................. 183
    10.1.1. 安裝JDK ................................................................................................... 183
    10.1.2. 安裝和配置maven .................................................................................... 183
    10.1.3. 安裝集成開發環境 ..................................................................................... 183
    10.2. 創建應用 ............................................................................................................. 183
    10.3. 運行應用 ............................................................................................................. 184
    10.4. 提問和解答 .......................................................................................................... 186
    10.4.1. 在生產環境的應用上,也會出現前述的「開發者首頁」嗎? .......................... 186
    10.4.2. 「開發模式」是什麼意思? ........................................................................ 187
    10.4.3. 所生成的應用中包含了什麼? ..................................................................... 187
    本章將幫助你快速創建一個可運行的Webx應用。你可以把它作為你的Webx新項目的開端。
    10.1. 準備工作
    請耐心,準備工作會花掉你少許時間。但是磨刀不誤砍柴工,做好準備工作將為你將來的開發節
    省大量時間。
    10.1.1. 安裝JDK
    Webx需要JDK 5.0以上的版本。請從這裡下載並安裝它:http://www.oracle.com/
    technetwork/java/javase/。
    10.1.2. 安裝和配置maven
    Webx需要maven 2或更高版本。請從這裡下載並安裝它:http://maven.apache.org/。
    注意
    你不需要對maven進行特殊的配置,因為運行Webx應用所需要的所有包都存放在
    全世界共享的中心Maven倉庫(Central Maven Repository)中。Maven將從那
    里自動獲取所有的jar包、源代碼和javadoc。
    你可以從這裡查詢到所有和Webx有關的發布包:http://search.maven.org/
    #search%7Cga%7C1%7Ccom.alibaba.citrus。
    10.1.3. 安裝集成開發環境
    很難想像不用集成開發環境(IDE)來幫助開發Java應用會變成怎樣。
    如果你使用Eclipse(從這裡下載:http://www.eclipse.org/),建議安裝如下插件:
    • Maven eclipse插件:http://eclipse.org/m2e/
    • Git eclipse插件:http://eclipse.org/egit/
    10.2. 創建應用
    請打開命令行工具(Windows cmd或Unix/Linux bash),輸入如下命令:
    創建第一個Webx應用
    184
    例 10.1. 從archetype創建Webx應用
    mvn archetype:generate \
    -DgroupId=com.alibaba.webx \
    -DartifactId=tutorial1 \
    -Dversion=1.0-SNAPSHOT \
    -Dpackage=com.alibaba.webx.tutorial1 \
    -DarchetypeArtifactId=archetype-webx-quickstart \
    -DarchetypeGroupId=com.alibaba.citrus.sample \
    -DarchetypeVersion=1.0 \
    -DinteractiveMode=false
    由於Windows下不支持命令換行,請改用非換行版:
    mvn archetype:generate -DgroupId=com.alibaba.webx -DartifactId=tutorial1 -Dversion=1.0-SNAPSHOT
    -Dpackage=com.alibaba.webx.tutorial1 -DarchetypeArtifactId=archetype-webx-quickstart -
    DarchetypeGroupId=com.alibaba.citrus.sample -DarchetypeVersion=1.0 -DinteractiveMode=false
    命令執行完后,你會看見一個新目錄:tutorial1。它就是我們剛剛創建的新項目。項目的各項
    參數如下所示:
    項目組(groupId):com.alibaba.webx。
    項目名稱(artifactId):tutorial1。
    項目版本(version):1.0-SNAPSHOT。
    項目中Java類的包名(package):com.alibaba.webx.tutorial1。
    注意
    你完全可以根據你的需要來調整上述命令中的參數,改用其它
    的groupId、artifactId、version以及package。
    10.3. 運行應用
    進入剛創建的tutorial1目錄,在此目錄下執行maven命令:
    例 10.2. 啟動Jetty伺服器
    mvn jetty:run
    這條命令會啟動Jetty Server,默認的埠是8081。請在瀏覽器地址欄輸入地址,或直接點擊這
    個鏈接:http://localhost:8081/。你應該可以看到類似下面的結果:
    創建第一個Webx應用
    185
    圖 10.1. Webx開發者首頁
    這是一個「開發者首頁」。它不是真正的應用程序首頁,而是一個專為開發者準備的首頁。這個
    頁面顯示了一些諸如Webx版本、Java版本、OS類型、IP地址、內存等信息。
    請點擊頁面頂部的菜單中的Application Home,
    這樣就可以進入真正的應用程序首頁:
    創建第一個Webx應用
    186
    圖 10.2. Webx應用首頁
    10.4. 提問和解答
    10.4.1. 在生產環境的應用上,也會出現前述的「開發者首頁」嗎?
    不會的。
    事實上,之所以剛才的運行會產生「開發者首頁」,是因為在jetty啟動時,定義了一個啟動參
    數。請打開pom.xml看一下:
    創建第一個Webx應用
    187
    例 10.3. 定義「開發者模式」的啟動參數
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    ...
    <build>
    <plugins>
    ...
    <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>maven-jetty-plugin</artifactId>
    <configuration>
    <contextPath>/</contextPath>
    <connectors>
    <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
    <port>8081</port>
    <maxIdleTime>60000</maxIdleTime>
    </connector>
    </connectors>
    <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
    <filename>target/access.log</filename>
    <retainDays>90</retainDays>
    <append>false</append>
    <extended>false</extended>
    <logTimeZone>GMT+8:00</logTimeZone>
    </requestLog>
    <systemProperties>
    <systemProperty>
    <name>productionMode</name>
    <value>false</value>
    </systemProperty>
    </systemProperties>
    </configuration>
    </plugin>
    ...
    </plugins>
    </build>
    </project>
    設定JVM啟動參數:productionMode=false。
    如果不加特別設置,系統的默認狀態是「生產模式」,即productionMode默認為true。因此,
    生產環境的伺服器總是運行在「生產模式」而不是「開發模式」下的。而「開發者首頁」只會出
    現在「開發模式」下面。
    你可以用下面的命令來覆蓋pom.xml的設置:
    例 10.4. 在命令行上指定JVM參數
    mvn jetty:run -DproductionMode=true
    然後在瀏覽器地址欄輸入地址,或直接點擊這個鏈接:http://localhost:8081/,你將直接被帶
    入應用首頁,而不是「開發者首頁」。
    10.4.2. 「開發模式」是什麼意思?
    開發模式是為了方便應用開發,Webx所提供的額外功能。具體請見:第 3.2.4 節 「開發模式工
    具」。
    10.4.3. 所生成的應用中包含了什麼?
    這個應用程序包含了幾個Webx應用的常見元素:
    創建第一個Webx應用
    188
    • 一個歡迎頁面(index screen)
    • 一個頁面布局(layout)
    • 一個表單驗證(form)
    • 一個action,用來處理用戶提交的數據
    • Logback日誌被列印在屏幕上
    189
    第 11 章 Webx日誌系統的配置
    11.1. 名詞解釋 ............................................................................................................. 189
    11.1.1. 日誌系統(Logging System) ................................................................... 189
    11.1.2. 日誌框架(Logging Framework) ............................................................ 190
    11.2. 在Maven中組裝日誌系統 ..................................................................................... 190
    11.2.1. 在Maven中配置logback作為日誌系統 ...................................................... 192
    11.2.2. 在Maven中配置log4j作為日誌系統 ............................................................ 195
    11.3. 在WEB應用中配置日誌系統 ................................................................................... 198
    11.3.1. 設置WEB應用 ............................................................................................ 198
    11.3.2. 定製/WEB-INF/logback.xml(或/WEB-INF/log4j.xml) .......................... 200
    11.3.3. 同時初始化多個日誌系統 ........................................................................... 203
    11.4. 常見錯誤及解決 ................................................................................................... 205
    11.4.1. 查錯技巧 ................................................................................................... 205
    11.4.2. 異常信息:No log system exists ................................................................ 205
    11.4.3. 異常信息:NoSuchMethodError:
    org.slf4j.MDC.getCopyOfContextMap() ........................................................... 206
    11.4.4. STDERR輸出:Class path contains multiple SLF4J bindings ....................... 206
    11.4.5. 看不到日誌輸出 ......................................................................................... 206
    11.5. 本章總結 ............................................................................................................. 208
    日誌系統是一個應用中必備的部分,提供了查看錯誤信息、了解系統狀態的最直接手段。
    本章介紹了基於Webx框架的應用如何配置、使用日誌系統的方法。
    11.1. 名詞解釋
    11.1.1. 日誌系統(Logging System)
    表 11.1. 日誌系統
    名稱說明
    Log4j http://logging.apache.org/log4j/
    較早出現的比較成功的日誌系統是Log4j。
    Log4j開創的日誌系統模型(Logger/Appender/Level)行之有效,並一直延用至
    今。
    JUL(java.util.logging.*) http://download.oracle.com/javase/6/docs/technotes/guides/logging/
    overview.html
    JDK1.4是第一個自帶日誌系統的JDK,簡稱(JUL)。
    JUL並沒有明顯的優勢來戰勝Log4j,反而造成了標準的混亂 —— 採用不同日誌系
    統的應用程序無法和諧共存。
    Logback http://logback.qos.ch/
    是較新的日誌系統。
    它是Log4j的作者吸取多年的經驗教訓以後重新做出的一套系統。它的使用更方
    便,功能更強,而且性能也更高。
    Logback不能單獨使用,必須配合日誌框架SLF4J來使用。
    Webx日誌系統的配置
    190
    11.1.2. 日誌框架(Logging Framework)
    JUL誕生以後,為了克服多種日誌系統並存所帶來的混亂,就出現了「日誌框架」。日誌框架
    本身不提供記錄日誌的功能,它只提供了日誌調用的介面。日誌框架依賴於實際的日誌系統如
    Log4j或JUL來產生真實的日誌。
    使用日誌框架的好處是:應用的部署者可以決定使用哪一種日誌系統(Log4j還是JUL),或者
    在多種日誌系統之間切換,而不需要更改應用的代碼。
    表 11.2. 日誌框架
    名稱說明
    JCL(Jakarta Commons
    Logging)
    http://commons.apache.org/logging/
    這是目前最流行的一個日誌框架,由Apache Jakarta社區提供。
    Spring框架、許多老應用都依賴於JCL。
    SLF4J http://www.slf4j.org/
    這是一個最新的日誌框架,由Log4j的作者推出。
    SLF4J提供了新的API,特別用來配合Logback的新功能。但SLF4J同樣兼容Log4j。
    由於Log4j原作者的感召力,SLF4J和Logback很快就流行起來。Webx的新版本也決定使用
    SLF4J作為其日誌框架;並推薦Logback作為日誌系統,但同時支持Log4J。
    11.2. 在Maven中組裝日誌系統
    要在應用中使用日誌系統,必須把正確的jar包組裝起來。本章假設你的應用是用maven構建
    的。
    Webx日誌系統的配置
    191
    圖 11.1. 日誌系統的組成
    如圖所示,
    • 由於JCL-over-SLF4J和原來的JCL具有完全相同的API,因此兩者是不能共存的。
    • Logback和slf4j-log4j12也不能並存,否則SLF4J會迷惑併產生不確定的結果。
    組裝完整的日誌系統將涉及如下部件:
    表 11.3. 日誌系統的組成
    類別組件名稱說明
    日誌框架SLF4J Webx框架以及所有新應用,直接依賴於SLF4J。
    JCL Spring框架、許多以前的老應用,都使用JCL來輸出日誌。
    好在SLF4J提供了一個「橋接」包:JCL-over-SLF4J,它重寫了JCL
    的API,並將所有日誌輸出轉向SLF4J。這樣就避免了兩套日誌框架並
    存的問題。
    Logback Webx推薦使用logback來取代log4j。
    Logback可直接被SLF4J識別並使用。
    日誌系統
    Log4j 由於客觀原因,有些系統暫時不能升級到Logback。
    好在SLF4J仍然支持Log4j。Log4j需要一個適配器slf4j-log4j12才能被
    SLF4J識別並使用。
    Webx日誌系統的配置
    192
    11.2.1. 在Maven中配置logback作為日誌系統
    圖 11.2. 以logback作為日誌系統
    Webx日誌系統的配置
    193
    例 11.1. 配置pom.xml以使用logback
    <dependencies>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    </dependency>
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    </dependency>
    </dependencies>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>0.9.29</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
    <scope>provided</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    把所依賴jar包的版本定義在<dependencyManagement>中,而不是<dependencies>中。
    因為前者可影響間接依賴,後者只能影響直接依賴。
    如果你的項目指定了parent pom,那麼建議把<dependencyManagement>放在parent
    pom中,以便多個子項目共享配置。
    將logback日誌系統的依賴設定為<scope>runtime</scope>,因為應用程序永遠不需要
    直接調用日誌系統,而是通過SLF4J或JCL這樣的日誌框架來調用它們。
    由於和jcl-over-slf4j存在衝突,因此JCL(commons-logging)是必須被排除的。由於
    maven目前缺少這樣一個功能:它不能全局地排除一個jar包依賴,所以建議將commonslogging
    設置成<scope>provided</scope>,這樣在最終的依賴關係中,將不會包含
    commons-logging包。
    將commons-logging設置成<scope>provided</scope>可以用來排除commons-logging,
    然而這樣做有一個缺點 —— 你無法從單元測試中將commons-logging排除。假如這個影響了
    你的單元測試的話,請使用另一種方案:
    Webx日誌系統的配置
    194
    例 11.2. 另一種排除commons-logging的方法
    <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>99.0-does-not-exist</version>
    </dependency>
    「<version>99.0-does-not-exist</version>」是一個特殊的版本,這個版本的jar包
    里空無一物。這樣就可以「欺騙」maven使用這個空的jar包來取代commons-logging,
    達到排除它的目的。
    最後,你需要在項目文件夾下面,執行一下命令:「mvn dependency:tree」,確保沒有jar包
    直接或間接依賴了slf4j-log4j12。如果有的話,你可以用下面的配置來排除掉:
    例 11.3. 排除間接依賴的slf4j-log4j12
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>yourGroupId</groupId>
    <artifactId>yourArtifactId</artifactId>
    <version>yourVersion</version>
    <exclusions>
    <exclusion>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    </dependencies>
    </dependencyManagement>
    事實上,如果有其它的jar包依賴slf4j-log4j12,這本身就是有錯誤的。因為應用不應該直接依賴
    於這些包中的API —— 它們只應該依賴於日誌框架API。任何應用都應該把下列和日誌系統相關
    的依賴(如:slf4j-log4j12、logback-classic)設置成<scope>runtime</scope>的。
    Webx日誌系統的配置
    195
    11.2.2. 在Maven中配置log4j作為日誌系統
    圖 11.3. 以log4j作為日誌系統
    Webx日誌系統的配置
    196
    例 11.4. 配置pom.xml以使用log4j
    <dependencies>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    </dependency>
    </dependencies>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.1</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
    <scope>runtime</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    配置log4j的方法和關注要點和logback相似,請參見例 11.1 「配置pom.xml以
    使用logback」。除此以外,你需要在項目文件夾下面,執行一下命令:「mvn
    dependency:tree」,確保沒有jar包間接依賴了logback-classic。如果有的話,你可以用下面
    的配置來排除掉:
    Webx日誌系統的配置
    197
    例 11.5. 排除間接依賴的logback-classic
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>yourGroupId</groupId>
    <artifactId>yourArtifactId</artifactId>
    <version>yourVersion</version>
    <exclusions>
    <exclusion>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    </dependencies>
    </dependencyManagement>
    事實上,如果有其它的jar包依賴logback-classic,這本身就是有錯誤的。因為應用不應該直接
    依賴於這些包中的API —— 它們只應該依賴於日誌框架API。任何應用都應該把下列和日誌系統
    相關的依賴(如:slf4j-log4j12、logback-classic)設置成<scope>runtime</scope>的。
    Webx日誌系統的配置
    198
    11.3. 在WEB應用中配置日誌系統
    11.3.1. 設置WEB應用
    例 11.6. 設置/WEB-INF/web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    <context-param>
    <param-name>loggingRoot</param-name>
    <param-value>/tmp/logs</param-value>
    </context-param>
    <context-param>
    <param-name>loggingLevel</param-name>
    <param-value>INFO</param-value>
    </context-param>
    <context-param>
    <param-name>loggingCharset</param-name>
    <param-value>UTF-8</param-value>
    </context-param>
    <context-param>
    <param-name>log...</param-name>
    <param-value>...</param-value>
    </context-param>
    <listener>
    <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class>
    </listener>
    <filter>
    <filter-name>mdc</filter-name>
    <filter-class>com.alibaba.citrus.webx.servlet.SetLoggingContextFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>mdc</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>
    指定日誌系統的參數。
    在WEB應用啟動的時候,這個listener會被激活,並初始化日誌系統。
    將當前請求的信息放到日誌系統的MDC中(Mapped Diagnostic Context)。
    11.3.1.1. 日誌系統的參數
    可以在/WEB-INF/web.xml配置文件中集中定義日誌系統的參數。
    表 11.4. 可配置的日誌參數
    參數名稱說明
    loggingRoot 指定保存日誌文件的根目錄。如不指定,默認為:「${user.home}/logs」。
    loggingLevel 指定日誌級別,低於指定級別的日誌將不被輸出。如不指定,默認為「INFO」。
    Webx日誌系統的配置
    199
    參數名稱說明
    loggingCharset 指定用來生成日誌文件的字符集編碼。如不指定,默認為當前操作系統的默認字
    符集編碼。
    log* 名稱以「log」開頭的任意<context-param>參數,都將被用作日誌系統的參
    數。
    日誌系統的參數可被替換到log4j或logback的配置中去,例如,在logback的配置文件中,你
    可以指定${loggingRoot}來取得存放日誌文件的根目錄。
    將日誌參數配置在/WEB-INF/web.xml中,有如下優點:
    • 使一套配置參數可同時應用於任意日誌系統,包括logback和log4j。
    • 便於管理。通常,我們可以利用maven的filtering機制,或者autoconfig插件來生成/WEBINF/
    web.xml文件,以便定製上述參數。
    11.3.1.2. 自動識別並初始化日誌系統
    LogConfiguratorListener負責在系統啟動的時候初始化日誌系
    統。LogConfiguratorListener會根據下面所列的條件,來自動識別出當前的日誌系統,並正
    確地配置它:
    • 假如你在maven的pom.xml中指定log4j為日誌系統,那麼該listener就會試圖用/WEB-INF/
    log4j.xml來初始化日誌系統。
    • 假如你在maven的pom.xml中指定logback為日誌系統,那麼該listener就會試圖用/WEBINF/
    logback.xml來初始化日誌系統。
    • 假如你在maven的pom.xml中未指定任何日誌系統(不存在logback-classic或slf4jlog4j12),
    那麼listener會報錯並失敗,整個WEB應用會退出,伺服器報告應用啟動失敗。
    • 假如/WEB-INF/logback.xml(或/WEB-INF/log4j.xml)不存在,那麼listener會用默認的
    配置文件來初始化日誌。默認的配置會:
    • 把WARN級別以上的日誌列印在STDERR中,
    • 把WARN級別以下的日誌列印在STDOUT中。
    11.3.1.3. 初始化MDC
    SetLoggingContextFilter將當前請求的信息放到日誌系統的MDC中(Mapped Diagnostic
    Context)。這樣,日誌系統就可以列印出諸如下面所示的日誌信息:
    例 11.7. 利用MDC輸出的日誌
    30377 [2010-06-02 15:24:29] - GET /wrongpage.htm [ip=127.0.0.1 , ref=http://localhost:8081/index
    , ua=Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ,
    sid=scnd32ei11a7 ] ……
    這段日誌信息中包含了如下信息:
    用戶請求的類型:GET。
    請求的URI:/wrongpage.htm。
    用戶IP:127.0.0.1。
    Webx日誌系統的配置
    200
    上一個頁面的鏈接referrer:http://localhost:8081/index
    用戶的瀏覽器:Mac版的mozilla瀏覽器。
    Session ID:scnd32ei11a7。
    用戶客戶端的詳細信息,對於發現和追蹤錯誤非常有幫助。
    SetLoggingContextFilter是一個可選的filter —— 即使沒有它,Webx
    的<setLoggingContext /> valve也會做同樣的事情。但是把這些信息放在filter中,有利於及
    早記錄用戶的信息。
    11.3.2. 定製/WEB-INF/logback.xml(或/WEB-INF/log4j.xml)
    11.3.2.1. 可用的參數
    在日誌配置文件中,你可以使用以下參數:
    表 11.5. 日誌配置文件中可用的參數
    在/WEB-INF/web.xml中定義的所有日誌參數
    ${loggingRoot} 代表保存日誌文件的根目錄。
    ${loggingCharset} 代表用來生成日誌文件的字符集編碼。
    ${loggingLevel} 代表日誌級別,低於指定級別的日誌將不被輸出。
    ${log*} 自定義參數,其中「*」代表任意名稱。
    由系統自動取得的參數
    ${loggingHost} 代表當前的伺服器名稱
    ${loggingAddress} 代表當前的伺服器IP地址
    11.3.2.2. 可用的MDC參數
    在appender pattern中,你可以使用以下MDC參數:
    表 11.6. 日誌配置文件中可用的MDC參數
    參數名說明
    %X{productionMode} 系統運行的模式。如果系統以開發模式運行,將會顯示Development
    Mode;否則將會顯示Production Mode。在生產環境中啟動開發模式
    會引起嚴重的性能和安全問題。將系統運行的模式列印在日誌中,可以
    作為一種提醒。
    %X{method} 請求類型,如:GET或POST
    %X{requestURL} 完整的URL,如:http://localhost/test
    %X{requestURLWithQueryString} 完整的URL,以及query string,如:http://localhost/test?id=1
    %X{requestURI} 不包括host信息的URI,如:/test
    %X{requestURIWithQueryString} 不包括host信息的URI,以及query string,如:/test?id=1
    %X{queryString} URL參數,如:id=1
    %X{remoteAddr} 客戶端地址
    %X{remoteHost} 客戶端域名
    %X{userAgent} 客戶端瀏覽器信息
    %X{referrer} 上一個頁面的URL
    %X{cookies} 所有cookies的名稱,如:[cookie1, cookie2]
    Webx日誌系統的配置
    201
    參數名說明
    %X{cookie.*} 特定cookie的值,如:%X{cookie.JSESSIONID},將顯示當前session
    的ID
    %X{*} 其它由應用程序或框架置入MDC的參數
    11.3.2.3. Logback配置示例
    例 11.8. Logback配置示例(/WEB-INF/logback.xml)
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="false">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <target>System.out</target>
    <encoding>${loggingCharset}</encoding>
    <layout class="ch.qos.logback.classic.PatternLayout">
    <pattern><![CDATA[
    %n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=
    %X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} -
    %m%n
    ]]></pattern>
    </layout>
    <filter class="com.alibaba.citrus.logconfig.logback.LevelRangeFilter">
    <levelMax>INFO</levelMax>
    </filter>
    </appender>
    <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
    <target>System.err</target>
    <encoding>${loggingCharset}</encoding>
    <layout class="ch.qos.logback.classic.PatternLayout">
    <pattern><![CDATA[
    %n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=
    %X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} -
    %m%n
    ]]></pattern>
    </layout>
    <filter class="com.alibaba.citrus.logconfig.logback.LevelRangeFilter">
    <levelMin>WARN</levelMin>
    </filter>
    </appender>
    <appender name="PROJECT" class="ch.qos.logback.core.FileAppender">
    <file>${loggingRoot}/${localHost}/petstore.log</file>
    <encoding>${loggingCharset}</encoding>
    <append>false</append>
    <layout class="ch.qos.logback.classic.PatternLayout">
    <pattern><![CDATA[
    %n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method} %X{requestURIWithQueryString} [ip=
    %X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=%X{cookie.JSESSIONID}]%n %-5level %logger{35} -
    %m%n
    ]]></pattern>
    </layout>
    </appender>
    <root>
    <level value="${loggingLevel}" />
    <appender-ref ref="STDERR" />
    <appender-ref ref="STDOUT" />
    <appender-ref ref="PROJECT" />
    </root>
    </configuration>
    更詳細配置方法請參考logback官方文檔:http://logback.qos.ch/manual/
    configuration.html。
    Webx日誌系統的配置
    202
    請特別留意示例中參數的寫法,如「${loggingRoot}」;以及appender pattern中MDC參數
    的的寫法,如:「%X{method}」、「%X{requestURIWithQueryString}」等。
    11.3.2.4. Log4j配置示例
    例 11.9. Log4j配置示例(/WEB-INF/log4j.xml)
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.out" />
    <param name="encoding" value="${loggingCharset}" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern"
    value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method}
    %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=
    %X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n"
    />
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="levelMax" value="INFO" />
    </filter>
    </appender>
    <appender name="STDERR" class="org.apache.log4j.ConsoleAppender">
    <param name="target" value="System.err" />
    <param name="encoding" value="${loggingCharset}" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern"
    value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method}
    %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=
    %X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n"
    />
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="levelMin" value="WARN" />
    </filter>
    </appender>
    <appender name="PROJECT" class="org.apache.log4j.FileAppender">
    <param name="file" value="${loggingRoot}/${localHost}/myapp.log" />
    <param name="encoding" value="${loggingCharset}" />
    <param name="append" value="true" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern"
    value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method}
    %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=
    %X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n"
    />
    </layout>
    </appender>
    <root>
    <level value="${loggingLevel}" />
    <appender-ref ref="STDOUT" />
    <appender-ref ref="STDERR" />
    <appender-ref ref="PROJECT" />
    </root>
    </log4j:configuration>
    更詳細配置方法請參考log4j官方文檔:http://logging.apache.org/log4j/1.2/
    manual.html。
    Webx日誌系統的配置
    203
    請特別留意示例中參數的寫法,如「${loggingRoot}」;以及appender pattern中MDC參數
    的的寫法,如:「%X{method}」、「%X{requestURIWithQueryString}」等。
    11.3.3. 同時初始化多個日誌系統
    在某些遺留系統中,有些代碼直接用到了Log4j API(例如Log4j Appender)。假如,我們仍
    然希望SLF4J以logback作為日誌系統,但是保持這些老代碼繼續不變地使用log4j來記錄日誌。
    這樣我們就需要同時初始化logback和log4j。
    例 11.10. 同時初始化Logback和Log4j
    首先,你需要確保在pom.xml中,同時包含log4j和logback-classic這兩個依賴,但是請一定不
    要包含slf4j-log4j12這個包,因為它會和logback-classic起衝突。
    下面的配置在例 11.1 「配置pom.xml以使用logback」基礎上,添加了log4j的依賴:
    Webx日誌系統的配置
    204
    <dependencies>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    </dependency>
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    </dependency>
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    </dependency>
    </dependencies>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>0.9.29</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
    <scope>runtime</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    然後,你需要在/WEB-INF/web.xml中增加logSystem參數。
    下面的配置在例 11.6 「設置/WEB-INF/web.xml」基礎上,添加了所需的參數:
    <web-app>
    <context-param>
    <param-name>logSystem</param-name>
    <param-value>log4j, logback</param-value>
    </context-param>
    ...
    </web-app>
    Webx日誌系統的配置
    205
    以上這段/WEB-INF/web.xml的配置,告訴LogConfiguratorListener同時初始化兩個日
    志系統:log4j和logback。它們的配置文件分別是:/WEB-INF/log4j.xml和/WEB-INF/
    logback.xml。假如文件不存在也沒關係,LogConfiguratorListener會用系統默認的配置文
    件來初始化它們。
    11.4. 常見錯誤及解決
    11.4.1. 查錯技巧
    11.4.1.1. 檢查提示信息
    分析錯誤前,先檢查一下日誌系統輸出的提示信息,往往可以節省很多時間。
    當LogConfiguratorListener啟動時,將會在STDERR中列印信息,像下面這個樣子:
    例 11.11. 日誌初始化時的提示信息(STDERR)
    2010-06-02 16:57:28.021:INFO:/:Initializing log4j system
    INFO: configuring "log4j" using file:/Users/…/WEB-INF/log4j.xml
    - with property localAddress = 10.16.58.5
    - with property localHost = baobao-macbook-pro.local
    - with property loggingCharset = UTF-8
    - with property loggingLevel = warn
    - with property loggingRoot = /tmp/logs
    通過這些信息,你可以檢查如下內容:
    是否選擇了正確的日誌系統,如:log4j或logback,抑或兩樣都有。
    是否選擇了正確的日誌配置文件,如:/WEB-INF/log4j.xml。
    日誌文件的參數,如根目錄、字符集編碼、日誌級別等信息。
    11.4.1.2. 查看class真實歸屬的jar包位置
    有時,因為各種原因導致應用找到了錯誤的jar包,從而產生神秘的錯誤。例如,你以為你使用
    了SLF4J的最新版,然而在伺服器上存在一個SLF4J的老版本,並且其class loader優先順序比新版
    本更高。在這種情況下,應用會引用高優先順序class loader中的老版本的class。這可能導致錯
    誤。
    發現這類錯誤的有效的方法,是在應用程序的任意點設置斷點(利用eclipse遠程調試功能),
    當系統停留在斷點處時,執行如下的java代碼,查看其值:
    例 11.12. 查看class真實歸屬的jar包位置
    getClass().getClassLoader().getResource(getClass().getName().replace('.', '/') + ".class")
    另外,Webx開發模式所提供的詳細出錯頁面中,也會列出stacktrace中每一個class的真實jar
    包位置。
    11.4.2. 異常信息:No log system exists
    報這個錯的原因可能是:
    Webx日誌系統的配置
    206
    • 不存在slf4j-log4j12、logback-classic等任何一個日誌系統的實現。
    • Slf4j的版本和日誌系統的版本不匹配,例如,slf4j為1.4.3版,而slf4j-log4j12為1.6.1版。
    解決方法:
    • 用mvn dependency:tree命令查看所有的依賴包,排除以上錯誤。
    • 查看伺服器環境(如jboss),查看是不是存在不正確版本的jar包,被優先於應用jar包而載入
    了。參見第 11.4.1.2 節 「查看class真實歸屬的jar包位置」。
    11.4.3. 異常信息:NoSuchMethodError:
    org.slf4j.MDC.getCopyOfContextMap()
    報這個錯的原因是:
    • SLF4J的版本過老。MDC.getCopyOfContextMap()方法是從SLF4J 1.5.1時加入的,假如你
    的SLF4J是之前的版本,就會報錯。
    解決方法:
    • 用mvn dependency:tree查看所有的依賴包,排除以上錯誤。
    • 查看伺服器環境(如jboss),查看是不是存在不正確版本的jar包,被優先於應用jar包而載入
    了。
    11.4.4. STDERR輸出:Class path contains multiple SLF4J bindings
    SLF4J在STDERR報如下錯誤:
    例 11.13. Class path contains multiple SLF4J bindings
    SLF4J: Class path contains multiple SLF4J bindings.
    SLF4J: Found binding in [jar:file:/…/WEB-INF/lib/logback-classic-0.9.18.jar!/org/slf4j/impl/
    StaticLoggerBinder.class]
    SLF4J: Found binding in [jar:file:/…/WEB-INF/lib/slf4j-log4j12-1.5.11.jar!/org/slf4j/impl/
    StaticLoggerBinder.class]
    SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
    報這個錯的原因是:
    • classpath中存在多個日誌系統,使SLF4J無所適從。
    解決方法:
    • SLF4J已經列出了classpath中所有的日誌系統的位置。根據這些信息,你可以調整應用的依
    賴,或者整理伺服器的環境,使之只剩下一個日誌系統。
    11.4.5. 看不到日誌輸出
    原因可能是日誌的配置文件可能有錯。
    解決方法:
    Webx日誌系統的配置
    207
    • 首先,查看LogConfiguratorListener輸出到STDERR中的信息(參見第 11.4.1.1 節 「檢查
    提示信息」),確定系統:
    • 選擇了正確的日誌系統;
    • 選擇了正確的配置文件;
    • 設置了正確的參數(loggingRoot、loggingLevel等)。
    注意
    在JBOSS環境中,STDOUT和STDERR會被重定向到Log4j中,然後被輸出
    到一個文件中,通常是log/server.log。你必須從這個日誌文件中查
    看LogConfiguratorListener的輸出。
    • 假如以上信息均正確,查看日誌配置文件/WEB-INF/log4j.xml或/WEB-INF/logback.xml,
    是否引用了正確的參數,例如:${loggingRoot}、${loggingLevel}等。
    • 檢查文件系統許可權,確保應用有許可權創建和修改日誌文件。
    • 假設你使用log4j作為日誌系統,以jboss作為應用伺服器。在JBOSS環境中,當log4j被初始
    化后,STDOUT和STDERR可能會被重新配置到不同的appender中。原先用來記錄STDOUT和
    STDERR的日誌文件log/server.log將不會再被使用。建議你設置/WEB-INF/log4j.xml,增
    加如下內容:
    例 11.14. 在log4j中配置jboss伺服器日誌
    <appender name="JBOSS_APPENDER" class="org.apache.log4j.FileAppender">
    <param name="file" value="${loggingRootJboss}/server.log" />
    <param name="encoding" value="${loggingCharset}" />
    <param name="append" value="true" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern"
    value="%n%-4r [%d{yyyy-MM-dd HH:mm:ss}] %X{productionMode} - %X{method}
    %X{requestURIWithQueryString} [ip=%X{remoteAddr}, ref=%X{referrer}, ua=%X{userAgent}, sid=
    %X{cookie.JSESSIONID}]%n %-5level %logger{35} - %m%n"
    />
    </layout>
    </appender>
    <logger name="STDOUT">
    <appender-ref ref="JBOSS_APPENDER" />
    </logger>
    <logger name="STDERR">
    <appender-ref ref="JBOSS_APPENDER" />
    </logger>
    這裡用到了一個新的變數:${loggingRootJboss},你需要把它定義在/WEB-INF/
    web.xml中。
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app>
    ...
    <context-param>
    <param-name>loggingRootJboss</param-name>
    <param-value>${jboss}/log</param-value>
    </context-param>
    ...
    </web-app>
    Webx日誌系統的配置
    208
    如果你使用logback作為日誌系統,則不需要作如上配置。
    11.5. 本章總結
    LogConfiguratorListener目前只提供了logback和log4j的支持,儘管支持一種新的日誌系統
    是非常容易的,但現在看來,這兩種日誌系統已經足夠我們使用了。
    LogConfiguratorListener以SLF4J為基礎。SLF4J還提供了更多的功能:
    • 除了log4j和logback以外,SLF4J還支持幾種其它的日誌系統;
    • 除了jcl-over-slf4j以外,SLF4J還提供了幾種對其它legacy日誌系統的橋接功能。
    詳情請見SLF4J的文檔:http://www.slf4j.org/docs.html。
    部分 V. 輔助工具
    210
    第 12 章 AutoConfig工具使用指南 ................................................................................ 211
    12.1. 需求分析 ..................................................................................................... 211
    12.1.1. 解決方案 ........................................................................................... 211
    12.2. AutoConfig的設計 ...................................................................................... 214
    12.2.1. 角色與職責 ....................................................................................... 214
    12.2.2. 分享二進位目標文件 .......................................................................... 215
    12.2.3. 布署二進位目標文件 .......................................................................... 215
    12.2.4. AutoConfig特性列表 ........................................................................ 216
    12.3. AutoConfig的使用 —— 開發者指南 ............................................................. 217
    12.3.1. 建立AutoConfig目錄結構 ................................................................. 217
    12.3.2. 建立auto-config.xml描述文件 ........................................................... 218
    12.3.3. 建立模板文件 .................................................................................... 221
    12.4. AutoConfig的使用 —— 布署者指南 ............................................................. 223
    12.4.1. 在命令行中使用AutoConfig .............................................................. 223
    12.4.2. 在maven中使用AutoConfig ............................................................. 224
    12.4.3. 運行並觀察AutoConfig的結果 ........................................................... 226
    12.4.4. 共享properties文件 ........................................................................... 227
    12.4.5. AutoConfig常用命令 ........................................................................ 229
    12.5. 本章總結 ..................................................................................................... 231
    第 13 章 AutoExpand工具使用指南 .............................................................................. 232
    13.1. AutoExpand工具簡介 ................................................................................. 232
    13.1.1. Java、JavaEE打包的格式 ................................................................. 232
    13.1.2. 應用布署的方式 ................................................................................. 233
    13.1.3. AutoExpand的用武之地 ................................................................... 233
    13.2. AutoExpand的使用 ..................................................................................... 234
    13.2.1. 取得AutoExpand ............................................................................. 234
    13.2.2. 執行AutoExpand ............................................................................. 234
    13.2.3. AutoExpand和AutoConfig的合作 .................................................... 235
    13.3. AutoExpand的參數 ..................................................................................... 236
    13.4. 本章總結 ..................................................................................................... 237
    211
    第 12 章 AutoConfig工具使用指南
    12.1. 需求分析 ............................................................................................................. 211
    12.1.1. 解決方案 ................................................................................................... 211
    12.2. AutoConfig的設計 .............................................................................................. 214
    12.2.1. 角色與職責 ............................................................................................... 214
    12.2.2. 分享二進位目標文件 .................................................................................. 215
    12.2.3. 布署二進位目標文件 .................................................................................. 215
    12.2.4. AutoConfig特性列表 ................................................................................ 216
    12.3. AutoConfig的使用 —— 開發者指南 ..................................................................... 217
    12.3.1. 建立AutoConfig目錄結構 ......................................................................... 217
    12.3.2. 建立auto-config.xml描述文件 ................................................................... 218
    12.3.3. 建立模板文件 ............................................................................................ 221
    12.4. AutoConfig的使用 —— 布署者指南 ..................................................................... 223
    12.4.1. 在命令行中使用AutoConfig ...................................................................... 223
    12.4.2. 在maven中使用AutoConfig ..................................................................... 224
    12.4.3. 運行並觀察AutoConfig的結果 ................................................................... 226
    12.4.4. 共享properties文件 ................................................................................... 227
    12.4.5. AutoConfig常用命令 ................................................................................ 229
    12.5. 本章總結 ............................................................................................................. 231
    12.1. 需求分析
    在一個應用中,我們總是會遇到一些參數,例如:
    • 資料庫伺服器IP地址、埠、用戶名;
    • 用來保存上傳資料的目錄。
    • 一些參數,諸如是否打開cache、加密所用的密鑰名稱等等。
    這些參數有一個共性,那就是:它們和應用的邏輯無關,只和當前環境、當前系統用戶相關。以
    下場景很常見:
    • 在開發、測試、發布階段,使用不同的資料庫伺服器;
    • 在開發階段,使用Windows的A開發者將用戶上傳的文件存放在d:\my_upload目錄中,而使
    用Linux的B開發者將同樣的文件存放在/home/myname/my_upload目錄中。
    • 在開發階段設置cache=off,在生產環境中設置cache=on。
    很明顯,這些參數不適合被「硬編碼」在配置文件或代碼中。因為每一個從源碼庫中取得它們的
    人,都有可能需要修改它們,使之與自己的環境相匹配。
    12.1.1. 解決方案
    12.1.1.1. 運行時替換的placeholders
    很多框架支持在運行時刻替換配置文件中的placeholder佔位符。例如, Webx/Spring就有這
    個功能。
    AutoConfig工具使用指南
    212
    例 12.1. 在Webx中定義placeholders
    <services:property-placeholder />
    <services:webx-configuration>
    <services:productionMode>${productionMode:true}</services:productionMode>
    </services:webx-configuration>
    在上面這個例子中,你可以在啟動應用時,加上JVM參數:「-DproductionMode=false|
    true」來告訴系統用哪一種模式來工作。如果不指定,則取默認值「true」。
    運行時替換placeholder是一種非常實用的技術,它有如下優缺點:
    表 12.1. 運行時替換placeholders的優缺點
    優點缺點
    • 配置文件是靜態的、不變的。即使採用不同的參數
    值,你也不需要更改配置文件本身。
    • 你可以隨時改變參數的值,只需要啟動時指定不同的
    JVM參數、或指定不同的properties文件即可。
    • 這種配置對於應用程序各組件是透明的 —— 應用程序
    不需要做特別的編程,即可使用placeholders。
    • 並非所有框架都支持這種技術。
    • 支持該技術的框架各有不同的用法。例如:
    Spring和Log4j都支持placeholder替換,
    然則它們的做法是完全不同的。Spring通
    過PropertyPlaceholderConfigurer類來配置,而
    Log4j則需要在DomConfigurator中把參數傳進去。
    12.1.1.2. 中心配置伺服器(Config Server)
    這也是一種運行時技術。它可以在運行時刻,將應用所需的參數推送到應用中。
    它有如下優缺點:
    表 12.2. 中心配置伺服器的優缺點
    優點缺點
    • 它可以集中管理所有應用的配置,避免可能的錯誤;
    • 它可以在運行時改變參數的值,並推送到所有應用
    中。參數的更改可立即生效。
    • 需要一套獨立的伺服器系統。性能、可用性
    (availability)都是必須考慮的問題。
    • 對應用不是透明的,有一定的侵入性。應用程序必須
    主動來配合該技術。因此,該技術不可能適用於所
    有情況,特別對於第三方提供的代碼,很難使用該技
    術。
    • 為了連接到中心配置伺服器,你仍然需要配置適當的
    IP、埠等參數。你需要用其它技術來處理這些參數
    (例如placeholders)。
    12.1.1.3. Maven Filtering機制
    Maven提供了一種過濾機制,可以在資源文件被複制到目標目錄的同時,替換其中的
    placeholders。
    例 12.2. 配置Maven Filtering機制
    假設你的項目目錄結構如下:
    web-project
    │ pom.xml

    └─src
    └─main
    ├─java
    ├─resources
    └─webapp
    └─WEB-INF
    web.xml
    AutoConfig工具使用指南
    213
    在pom.xml中這樣寫:
    <build>
    <filters>
    <filter>${user.home}/antx.properties</filter>
    </filters>
    <resources>
    <resource>
    <directory>src/main/resources</directory>
    <includes>
    <include>**.xml</include>
    </includes>
    <filtering>true</filtering>
    </resource>
    <resource>
    <directory>src/main/resources</directory>
    <excludes>
    <exclude>**.xml</exclude>
    </excludes>
    </resource>
    </resources>
    <plugins>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <configuration>
    <webResources>
    <resource>
    <directory>src/main/webapp</directory>
    <includes>
    <include>WEB-INF/**.xml</include>
    </includes>
    <filtering>true</filtering>
    </resource>
    <resource>
    <directory>src/main/webapp</directory>
    <excludes>
    <include>WEB-INF/**.xml</include>
    </excludes>
    </resource>
    </webResources>
    </configuration>
    </plugin>
    </plugins>
    </build>
    這段pom定義告訴maven:
    用指定的properties文件(${user.home}/antx.properties)中的值,替換文件中的
    placeholders。
    過濾src/main/resources/目錄中的所有xml文件,替換其中的placeholders。
    過濾src/webapp/WEB-INF/目錄中的所有xml文件,替換其中的placeholders。
    如果上述xml文件中,包含「${xxx.yyy.zzz}」這樣的placeholders,將被替換成properties
    文件中的相應值。
    和運行時替換placeholders方案相比,Maven Filtering是一個build時進行的過程。它的優缺
    點是:
    表 12.3. Maven Filtering機制的優缺點
    優點缺點
    • Maven filtering機制和應用所採用的技術、框架完全
    無關,對應用完全透明,通用性好。
    • Maven filtering機制在build時刻永久性改變被過濾的
    配置文件的內容,build結束以後無法更改。這將導致
    AutoConfig工具使用指南
    214
    優點缺點
    一個問題:如果要改變配置文件的參數,必須獲取源
    碼並重新build。
    • 缺少驗證機制。當某個placeholder拼寫錯誤;當
    properties中的值寫錯;當某配置文件中新增了一個
    placeholder,而你的properties文件中沒有對應的值
    時,maven不會提醒你。而這些錯誤往往被拖延到應
    用程序運行時才會被報告出來。
    12.1.1.4. AutoConfig機制
    AutoConfig是一種類似於Maven Filtering的build時刻的工具。這意味著該機制與應用所採用
    的技術、框架完全無關,對應用完全透明,具有良好的通用性。同時,AutoConfig與運行時的
    配置技術並不衝突。它可以和運行時替換的placeholders以及中心配置伺服器完美並存,互為
    補充。
    AutoConfig書寫placeholder的方法和Maven Filtering機制完全相同。換言之,Maven
    Filtering的配置文件模板(前例中的/WEB-INF/**.xml)可以不加修改地用在AutoConfig中。
    然而,autoconfig成功克服了Maven Filtering的主要問題。
    表 12.4. Maven Filtering和AutoConfig的比較
    問題Maven Filtering AutoConfig
    如何修改配置文件的參數? Maven Filtering必須獲得源碼並重
    新build;
    而AutoConfig不需要提取源碼,也
    不需要重新build,即可改變目標文
    件中所有配置文件中placeholders的
    值。
    如何確保placeholder替換的正確
    性?
    Maven Filtering不能驗證
    placeholder值的缺失和錯誤;
    但AutoConfig可以對placeholder
    及其值進行檢查。
    接下來,我們將將詳細介紹AutoConfig的使用方法。
    12.2. AutoConfig的設計
    很多人會把AutoConfig看作Maven Filtering機制的簡單替代品。事實上,這兩者的設計初衷有
    很大的區別。
    12.2.1. 角色與職責
    為了把事情說清楚,我們必須要定義兩種角色:開發者(Developer)和布署者
    (Deployer)。
    表 12.5. 角色和職責
    角色名稱職責
    開發者• 定義應用所需要的properties,及其限定條件;
    • 提供包含placeholders的配置文件模板。
    布署者• 根據所定義的properties,提供符合限定條件的屬性值。
    • 調用AutoConfig來生成目標配置文件。
    例如,一個寵物店(petstore)的WEB應用中需要指定一個用來上傳文件的目錄。於是,
    AutoConfig工具使用指南
    215
    表 12.6. Petstore應用中的角色和職責
    開發者布署者
    開發者定義了一個property:petstore.upload_dir,
    限定條件為:「合法的文件系統的目錄名」。
    布署者取得petstore的二進位發布包,通過AutoConfig
    了解到,應用需要一個名為petstore.upload_dir目錄
    名。
    布署者便指定一個目錄給petstore,該目錄名的具體值
    可能因不同的系統而異。
    AutoConfig會檢驗該值是否符合限定條件(是否為合法
    目錄名),如果檢驗通過,就生成配置文件,並將其中
    的${petstore.upload_dir}替換成該目錄名。
    需要注意的是,一個「物理人」所對應的「角色」不是一成不變的。例如:某「開發者」需要試
    運行應用,此時,他就變成「布署者」。
    12.2.2. 分享二進位目標文件
    假設現在有兩個team要互相合作,team A的開發者創建了project A,而team B的開發者創
    建了project B。假定project B依賴於project A。如果我們利用maven這樣的build工具,那麼
    最顯而易見的合作方案是這樣的:
    • Team A發布一個project A的版本到maven repository中。
    • Team B從maven repository中取得project A的二進位目標文件。
    這種方案有很多好處,
    • 每個team都可以獨立控制自己發布版本的節奏;
    • Team之間的關係較鬆散,唯一的關係紐帶就是maven repository。
    • Team之間不需要共享源碼。
    然而,假如project A中有一些配置文件中的placeholders需要被替換,如果使用Maven
    Filtering機制,就會出現問題。因為Maven Filtering只能在project A被build時替換其中的
    placeholders,一旦project A被發布到repository中,team B的人將無法修改任何project A
    中的配置參數。除非team B的人取得project A的源碼,並重新build。這將帶來很大的負擔。
    AutoConfig解決了這個問題。因為當team B的人從maven repository中取得project A的二進
    制包時,仍然有機會修改其配置文件里的placeholders。Team B的人甚至不需要了解project
    A里配置文件的任何細節,AutoConfig會自動發現所有的properties定義,並提示編輯。
    12.2.3. 布署二進位目標文件
    布署應用的人(即布署者、deployer)也從中受益。因為deployer不再需要親手去build源代
    碼,而是從maven repository中取得二進位目標文件即可。
    AutoConfig工具使用指南
    216
    圖 12.1. 多團隊多角色的合作
    從這個意義上講,AutoConfig不應當被看成是一個build時的簡單配置工具,而是一個「軟體安
    裝工具」。如同我們安裝一個Windows軟體 —— 我們當然不需要從源碼開始build它們,而是
    執行其安裝程序,設定一些參數諸如安裝目錄、文檔目錄、可選項等。安裝程序就會自動把軟體
    設置好,確保軟體可正確運行於當前的Windows環境中。
    12.2.4. AutoConfig特性列表
    為了滿足前面所說的目的,我們將AutoConfig設計成下面的樣子:
    表 12.7. AutoConfig Features
    名稱描述
    兩種用法• 既可獨立使用(支持Windows和Unix-like平台)。
    • 也可以作為maven插件來使用。
    對目標文件而不是源文件進行
    配置
    • 可對同一個目標文件反覆配置。
    • 配置時不依賴於項目源文件。
    • 支持嵌套包文件,例如:ear包含war,war又包含jar。
    • 高性能,特別對於嵌套的包文件。
    驗證和編輯properties • 自動發現保存於war包、jar包、ear包中的properties定義。
    • 驗證properties的正確性。
    • 互動式編輯properties。
    • 當配置文件中出現未定義的placeholders時,提示報錯。
    AutoConfig工具使用指南
    217
    12.3. AutoConfig的使用 —— 開發者指南
    12.3.1. 建立AutoConfig目錄結構
    和Maven Filtering不同的是,AutoConfig是針對目標文件的配置工具。因此AutoConfig關心
    的目錄結構是目標文件的目錄結構。不同的build工具,創建同一目標目錄結構所需要的源文件
    的目錄結構會各有不同。本文僅以maven標準目錄結構為例,來說明源文件的目錄結構編排。
    12.3.1.1. WAR包的目錄結構
    這裡所說的war包,可以是一個以zip方式打包的文件,也可以是一個展開的目錄。下面以
    maven標準目錄為例,說明項目源文件和目標文件的目錄結構的對比:
    例 12.3. WAR包的源文件和目標文件目錄結構
    war-project(源目錄結構) -> war-project.war(目標目錄結構)
    │ pom.xml

    └─src
    └─main
    ├─java
    ├─resources -> /WEB-INF/classes
    │ file1.xml file1.xml
    │ file2.xml file2.xml

    └─webapp -> /
    ├─META-INF -> /META-INF
    │ └─autoconf -> /META-INF/autoconf
    │ auto-config.xml auto-config.xml

    └─WEB-INF -> /WEB-INF
    web.xml web.xml
    file3.xml file3.xml
    /META-INF/autoconf目錄用來存放AutoConfig的描述文件,以及可選的模板文件。
    auto-config.xml是用來指導AutoConfig行為的關鍵描述文件。
    創建war包的AutoConfig機制,關鍵在於創建war目標文件中的/META-INF/autoconf/autoconfig.
    xml描述文件。該描述文件對應的maven項目源文件為:/src/main/webapp/METAINF/
    autoconf/auto-config.xml。
    12.3.1.2. JAR包的目錄結構
    這裡所說的jar包,可以是一個以zip方式打包的文件,也可以是一個展開的目錄。下面以maven
    標準目錄為例,說明項目源文件和目標文件的目錄結構的對比:
    AutoConfig工具使用指南
    218
    例 12.4. JAR包的源文件和目標文件目錄結構
    jar-project(源目錄結構) -> jar-project.jar(目標目錄結構)
    │ pom.xml

    └─src
    └─main
    ├─java
    └─resources -> /
    │ file1.xml file1.xml
    │ file2.xml file2.xml

    └─META-INF -> /META-INF
    └─autoconf -> /META-INF/autoconf
    auto-config.xml auto-config.xml
    /META-INF/autoconf目錄用來存放AutoConfig的描述文件,以及可選的模板文件。
    auto-config.xml是用來指導AutoConfig行為的關鍵描述文件。
    創建jar包的AutoConfig機制,關鍵在於創建jar目標文件中的/META-INF/autoconf/autoconfig.
    xml描述文件。該描述文件對應的maven項目源文件為:/src/main/resources/
    META-INF/autoconf/auto-config.xml。
    12.3.1.3. 普通目錄
    AutoConfig也支持對普通文件目錄進行配置。
    例 12.5. 對普通的目錄執行AutoConfig
    directory
    │ file1.xml
    │ file2.xml

    └─conf
    auto-config.xml
    默認情況下,AutoConfig在/conf目錄中尋找AutoConfig的描述文件,以及可選的模板
    文件。
    auto-config.xml是用來指導AutoConfig行為的關鍵描述文件。
    12.3.2. 建立auto-config.xml描述文件
    AutoConfig系統的核心就是auto-config.xml描述文件。該描述文件中包含兩部分內容:
    1. 定義properties:properties的名稱、描述、默認值、約束條件等信息;
    2. 指定包含placeholders的模板文件。
    下面是auto-config.xml文件的樣子:(以petstore應用為例)
    AutoConfig工具使用指南
    219
    例 12.6. AutoConfig描述文件示例
    <?xml version="1.0" encoding="UTF-8"?>
    <config>
    <group>
    <property name="petstore.work"
    description="應用程序的工作目錄" />
    <property name="petstore.loggingRoot"
    defaultValue="${petstore.work}/logs"
    description="日誌文件目錄" />
    <property name="petstore.upload"
    defaultValue="${petstore.work}/upload"
    description="上傳文件的目錄" />
    <property name="petstore.loggingLevel"
    defaultValue="warn"
    description="日誌文件級別">
    <validator name="choice"
    choice="trace, debug, info, warn, error" />
    </property>
    </group>
    <script>
    <generate template="WEB-INF/web.xml" />
    <generate template="WEB-INF/common/resources.xml" />
    </script>
    </config>
    定義properties
    定義property的驗證規則(可選)
    生成配置文件的指令。
    12.3.2.1. 定義properties
    定義一個property的完整格式如下:
    例 12.7. 定義一個property
    <property
    name="..."
    [defaultValue="..."]
    [description="..."]
    [required="true|false"]
    >
    <validator name="..." />
    <validator name="..." />
    ...
    </property>
    可用的property參數包括:
    表 12.8. 定義property時可用的參數
    參數名說明
    name Property名稱。
    AutoConfig工具使用指南
    220
    參數名說明
    defaultValue(可選) 默認值。默認值中可包含對其它property的引用,如${petstore.work}/
    logs。
    description(可選) 對欄位的描述,這個描述會顯示給deployer,這對他理解該property非常重
    要。
    required(可選) 是否「必填」,默認為true。如果deployer未提供必填項的值,就會報錯。
    12.3.2.2. 定義property的驗證規則
    目前,有以下幾種驗證器:
    表 12.9. 可用的property驗證規則
    驗證規則說明
    <validator name="boolean" /> Property值必須為true或false。
    <validator name="choice"
    choice="trace, debug, info, warn, error" />
    Property值必須為choice所定義的值之一。
    <validator name="email" /> Property值必須為合法的email格式。
    <validator name="fileExist"
    [file="WEB-INF/web.xml"] />
    Property值必須為某個存在的文件或目錄。
    如果指定了file,那就意味著property值所
    指的目錄下,必須存在file所指的文件或子目
    錄。
    <validator name="hostExist" /> Property值必須為合法的IP地址,或者可以解
    析得到的域名。
    <validator name="keyword" /> Property值必須為字母、數字、下劃線的組
    合。
    <validator name="number" /> Property值必須為數字的組合。
    <validator name="regexp"
    regexp="..."
    [mode="exact|prefix|contain"] />
    Property值必須符合regexp所指的正則表達
    式。
    其中,mode為匹配的方法:
    • 完全匹配exact
    • 前綴匹配prefix
    • 包含contain
    如未指定mode,默認mode為contain。
    <validator name="url"
    [checkHostExist="false"]
    [protocols="http, https"]
    [endsWithSlash="true"] />
    Property值必須是合法URL。
    假如指定了checkHostExist=true,那麼還
    會檢查域名或IP的正確性;
    假如指定了protocols,那麼URL的協議必須
    為其中之一;
    假如指定了endsWithSlash=true,那麼URL
    必須以/結尾。
    12.3.2.3. 生成配置文件的指令
    描述文件中,每個<generate>標籤指定了一個包含placeholders的配置文件模板,具體格式
    為:
    AutoConfig工具使用指南
    221
    例 12.8. 生成配置文件的指令
    <generate
    template="..."
    [destfile="..."]
    [charset="..."]
    [outputCharset="..."]
    >
    下面是參數的說明:
    表 12.10. 生成配置文件的指令參數
    參數名說明
    template 需要配置的模板名。
    模板名為相對路徑,相對於當前jar/war/ear包的根目錄。
    destfile(可選) 目標文件。
    如不指定,表示目標文件和模板文件相同。
    charset(可選) 模板的字符集編碼。
    XML文件不需要指定charset,因為AutoConfig可以自動取得XML文件的字符集編碼;
    對其它文件必須指定charset。
    outputCharset(可選) 目標文件的輸出字符集編碼。
    如不指定,表示和模板charset相同。
    12.3.3. 建立模板文件
    12.3.3.1. 模板文件的位置
    定義完auto-config.xml描述文件以後,就可以創建模板了。模板放在哪裡呢?舉例說明。
    例 12.9. 模板文件的位置
    假設在一個典型的WEB應用中,你的auto-config.xml中包含指定了如下模板:
    <?xml version="1.0" encoding="UTF-8"?>
    <config>
    <group>
    ...
    </group>
    <script>
    <generate template="WEB-INF/classes/file1.xml" />
    <generate template="WEB-INF/classes/file2.xml" />
    <generate template="WEB-INF/file3.xml" />
    </script>
    </config>
    那麼,你可以把file1.xml、file2.xml、file3.xml放在下面的位置:
    AutoConfig工具使用指南
    222
    war-project(源目錄結構) -> war-project.war(目標目錄結構)
    │ pom.xml

    └─src
    └─main
    ├─java
    ├─resources -> /WEB-INF/classes
    │ file1.xml file1.xml - 建議放在這裡
    │ file2.xml file2.xml - 建議放在這裡

    └─webapp
    ├─META-INF
    │ └─autoconf
    │ │ auto-config.xml
    │ │
    │ └─WEB-INF -> /WEB-INF
    │ │ file3.xml file3.xml - 也可以放在這裡
    │ │
    │ └─classes -> /WEB-INF/classes
    │ file1.xml file1.xml - 也可以放在這裡
    │ file2.xml file2.xml - 也可以放在這裡

    └─WEB-INF -> /WEB-INF
    file3.xml file3.xml - 建議放在這裡
    AutoConfig的尋找模板的邏輯是:
    • 如果在auto-config.xml所在的目錄下發現模板文件,就使用它;
    • 否則在包的根目錄中查找模板文件;如果兩處均未找到,則報錯。
    12.3.3.2. 模板的寫法
    書寫模板是很簡單的事,你只要:
    • 把需要配置的點替換成placeholder:「${property.name}」。當然,你得確保
    property.name被定義在auto-config.xml中。
    • 假如模板中包含不希望被替換的運行時的placeholder「${...}」,需要更改
    成「${D}{...}」 。
    例 12.10. 模板示例
    ...
    <context-param>
    <param-name>loggingRoot</param-name>
    <param-value>${petstore.loggingRoot}</param-value>
    </context-param>
    <context-param>
    <param-name>loggingLevel</param-name>
    <param-value>${petstore.loggingLevel}</param-value>
    </context-param>
    ...
    ${D}{runtime.placeholder}
    此外,AutoConfig模板其實是由Velocity模板引擎來渲染的。因此,所有的placeholder必須
    能夠通過velocity的語法。
    例 12.11. 使用不符合velocity語法的placeholders
    例如,下面的placeholder被velocity看作非法:
    AutoConfig工具使用指南
    223
    ${my.property.2}
    解決的辦法是,改寫成如下樣式:
    ${my_property_2}
    12.4. AutoConfig的使用 —— 布署者指南
    布署者有兩種方法可以使用AutoConfig:
    • 在命令行上直接運行。
    • 在maven中調用AutoConfig plugin。
    12.4.1. 在命令行中使用AutoConfig
    12.4.1.1. 取得可執行文件
    AutoConfig提供了Windows以及Unix-like(Linux、Mac OS等)等平台上均可使用的native
    可執行程序。可執行程序文件被發布在Maven repository中。
    如果你已經配置好了maven,那麼可以讓maven來幫你下載目標文件。
    例 12.12. 讓maven幫忙下載AutoConfig可執行文件
    請創建一個臨時文件:pom.xml。
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <parent>
    <groupId>com.alibaba.citrus.tool</groupId>
    <artifactId>antx-parent</artifactId>
    <version>1.0.10</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>temp</artifactId>
    </project>
    文件中的parent pom的版本號(1.0.10)決定了你要取得的AutoConfig的版本號。
    然後在命令行上執行如下命令:
    mvn dependency:copy
    這樣就取得了兩個文件:
    • autoconfig-1.0.10.tgz
    • autoexpand-1.0.10.tgz - AutoExpand是另一個小工具。它是用來展開war、jar、ear包
    的。關於AutoExpand的詳情,請見第 13 章 AutoExpand工具使用指南。
    你也可以直接去maven repository中手工下載以上兩個包:
    • http://code.taobao.org/mvn/repository/com/alibaba/citrus/tool/antxautoconfig/
    1.0.10/antx-autoconfig-1.0.10.tgz
    AutoConfig工具使用指南
    224
    • http://code.taobao.org/mvn/repository/com/alibaba/citrus/tool/antxautoexpand/
    1.0.10/antx-autoexpand-1.0.10.tgz
    取得壓縮包以後,可以用下面的命令來展開並安裝工具。
    表 12.11. 展開並安裝工具
    Unix-like系統Windows系統
    tar zxvf autoconfig-1.0.10.tgz
    tar zxvf autoexpand-1.0.10.tgz
    cp autoconfig /usr/local/bin
    cp autoexpand /usr/local/bin
    tar zxvf autoconfig-1.0.10.tgz
    tar zxvf autoexpand-1.0.10.tgz
    copy autoconfig.exe c:\windows\system32
    copy autoexpand.exe c:\windows\system32
    12.4.1.2. 執行AutoConfig命令
    取得可執行文件以後,就可以試用一下:在命令行上輸入autoconfig。不帶參數
    的autoconfig命令會顯示出如下幫助信息。
    例 12.13. AutoConfig的幫助信息
    $ autoconfig
    Detected system charset encoding: UTF-8
    If your can't read the following text, specify correct one like this:
    autoconfig -c mycharset
    使用方法:autoconfig [可選參數] [目錄名|包文件名]
    可選參數:
    -c,--charset 輸入/輸出編碼字符集
    -d,--include-descriptors
    包含哪些配置描述文件,例如:conf/auto-config.xml,可使用*、**、?通配符,如有多項,用
    逗號分隔
    -D,--exclude-descriptors 排除哪些配置描述文件,可使用*、**、?通配符,如有多項,用逗號分隔
    -g,--gui 圖形用戶界面(交互模式)
    -h,--help 顯示幫助信息
    -i,--interactive 交互模式:auto|on|off,默認為auto,無參數表示on
    -I,--non-interactive 非交互模式,相當於--interactive=off
    -n,--shared-props-name 共享的屬性文件的名稱
    -o,--output 輸出文件名或目錄名
    -P,--exclude-packages 排除哪些打包文件,可使用*、**、?通配符,如有多項,用逗號分隔
    -p,--include-packages
    包含哪些打包文件,例如:target/*.war,可使用*、**、?通配符,如有多項,用逗號分隔
    -s,--shared-props 共享的屬性文件URL列表,以逗號分隔
    -T,--type 文件類型,例如:war, jar, ear等
    -t,--text 文本用戶界面(交互模式)
    -u,--userprop 用戶屬性文件
    -v,--verbose 顯示更多信息
    總耗費時間:546毫秒
    最簡單的AutoConfig命令如下:
    例 12.14. 最簡單的AutoConfig命令
    autoconfig petstore.war
    無論petstore.war是一個zip包還是目錄,AutoConfig都會正確地生成其中的配置文件。
    12.4.2. 在maven中使用AutoConfig
    AutoConfig也可以通過maven plugin來執行。
    AutoConfig工具使用指南
    225
    這種方式使用方式,方便了開發者試運行並測試應用程序。開發者可以在build項目的同時,把
    AutoConfig也配置好。然而對於非開發的應用測試人員、發布應用的系統管理員來說,最好的
    方法是使用獨立可執行的AutoConfig來配置應用的二進位目標文件。
    為了使用maven插件,你需要修改項目的pom.xml來設定它。請注意,一般來說,不要在
    parent pom.xml中設定AutoConfig,因為這個設置會作用在每個子項目上,導致不必要的
    AutoConfig執行。只在生成最終目標文件的子項目pom.xml中設定AutoConfig就可以了。例
    如,對於一個web項目,你可以在生成war包的子項目上設置AutoConfig plugin。
    例 12.15. 在pom.xml中設定AutoConfig plugin
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    ...
    <properties>
    ...
    <!-- 定義autoconfig的版本,建議將此行寫在parent pom.xml中。 -->
    <autoconfig-plugin-version>1.0.10</autoconfig-plugin-version>
    </properties>
    ...
    <build>
    <plugins>
    <plugin>
    <groupId>com.alibaba.citrus.tool</groupId>
    <artifactId>maven-autoconfig-plugin</artifactId>
    <version>${autoconfig-plugin-version}</version>
    <configuration>
    <!-- 要進行AutoConfig的目標文件,默認為${project.artifact.file}。
    <dest>${project.artifact.file}</dest>
    -->
    <!-- 配置后,是否展開目標文件,默認為false,不展開。
    <exploding>true</exploding>
    -->
    <!-- 展開到指定目錄,默認為${project.build.directory}/${project.build.finalName}。
    <explodedDirectory>
    ${project.build.directory}/${project.build.finalName}
    </explodedDirectory>
    -->
    </configuration>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>autoconfig</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    </project>
    這樣,每次執行mvn package或者mvn install時,都會激活AutoConfig,對package目標
    文件進行配置。
    想要避免AutoConfig,只需要一個額外的命令行參數:
    例 12.16. 避免執行AutoConfig
    mvn install –Dautoconfig.skip
    AutoConfig工具使用指南
    226
    12.4.3. 運行並觀察AutoConfig的結果
    第一次執行AutoConfig,無論通過何種方式(獨立命令行或maven插件),AutoConfig都會
    提示你修改user properties文件,以提供所需要的properties值。AutoConfig提供了一套基於
    文本的互動式界面來編輯這些properties。
    例 12.17. 互動式編輯properties
    ╭───────────────────────┈┈┈┈

    │ 您的配置文件需要被更新:

    │ file:/.../antx.properties

    │ 這個文件包括了您個人的特殊設置,
    │ 包括伺服器埠、您的郵件地址等內容。

    └───────┈┈┈┈┈┈┈┈┈┈┈
    如果不更新此文件,可能會導致配置文件的內容不完整。
    您需要現在更新此文件嗎? [Yes][No] y
    當你通過互動式界面填寫了所有properties的值,並通過了AutoConfig的驗證以
    后,AutoConfig就開始生成配置文件:
    即將保存到文件"file:/.../antx.properties"中, 確定? [Yes][No] y
    ╭───────────────────────┈┈┈┈
    │ 保存文件 file:/.../antx.properties...
    │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
    │petstore.loggingLevel = warn
    │petstore.loggingRoot = ${petstore.work}/logs
    │petstore.upload = ${petstore.work}/upload
    │petstore.work = /tmp
    └───────┈┈┈┈┈┈┈┈┈┈┈
    已保存至文件: file:/.../antx.properties
    Loading file:/.../antx.properties
    <jar:file:/.../Work/my/apps/petstore-webx3/target/petstore.war!/>
    Generating WEB-INF/web.xml [UTF-8] => WEB-INF/web.xml [UTF-8]
    <jar:file:/.../Work/my/apps/petstore-webx3/target/petstore.war!/>
    Generating WEB-INF/common/resources.xml [UTF-8] => WEB-INF/common/resources.xml [UTF-8]
    <jar:file:/.../Work/my/apps/petstore-webx3/target/petstore.war!/>
    Generating log file: META-INF/autoconf/auto-config.xml.log
    Expanding: /.../Work/my/apps/petstore-webx3/target/petstore.war
    To: /.../Work/my/apps/petstore-webx3/target/petstore
    done.
    假如發現模板中某個placeholder,並未在auto-config.xml中定義,就會出現以下錯誤:
    ERROR - Undefined placeholders found in template:
    - Template: META-INF/autoconf/WEB-INF/web.xml
    - Descriptor: META-INF/autoconf/auto-config.xml
    - Base URL: file:/.../Work/my/apps/petstore-webx3/target/petstore/
    ---------------------------
    -> petstore.loggingRoot
    ---------------------------
    出現錯誤以後,Maven會報錯,並停止build過程。假如你不希望maven停止,可以用下面的
    命令來執行maven:
    AutoConfig工具使用指南
    227
    例 12.18. 避免maven因為placeholder未定義而停止
    mvn ... –Dautoconfig.strict=false
    AutoConfig會生成一個日誌文件,就在auto-config.xml所在的目錄下,名字為:autoconfig.
    xml.log。
    例 12.19. AutoConfig所生成的日誌文件
    Last Configured at: Fri Jun 18 13:54:22 CST 2010
    Base URL: file:/.../Work/my/apps/petstore-webx3/target/petstore/
    Descriptor: META-INF/autoconf/auto-config.xml
    Generating META-INF/autoconf/WEB-INF/web.xml [UTF-8] => WEB-INF/web.xml [UTF-8]
    Generating META-INF/autoconf/WEB-INF/common/resources.xml [UTF-8] => WEB-INF/common/resources.xml [UTF-8]
    最後,讓我們查看一下AutoConfig所生成的文件,其中所有的placeholders應當被替換成你所
    提供的值了。
    例 12.20. AutoConfig生成的結果
    ...
    <context-param>
    <param-name>loggingRoot</param-name>
    <param-value>/tmp/logs</param-value>
    </context-param>
    <context-param>
    <param-name>loggingLevel</param-name>
    <param-value>warn</param-value>
    </context-param>
    ...
    ${runtime.placeholder}
    12.4.4. 共享properties文件
    當需要配置的內容越來越多時,即使使用AutoConfig這樣的機制,也會變得不勝其煩。
    假如你的項目包含了好幾個模塊,而你只負責其中的一個模塊。一般來說,你對其它模塊的配
    置是什麼並不清楚,事實上你也懶得去關心。但是你為了運行這個項目,你不得不去配置這些模
    塊。假如模塊A就是一個你不想關心的模塊,但為了運行它,你需要告訴模塊A一些參數:數據
    庫連接的參數、域名、埠、文件目錄、搜索引擎……可你並不清楚這些參數應該取什麼值。
    好在AutoConfig提供了一個共享properties文件的方法。
    12.4.4.1. 共享的properties文件
    你可以創建一系列文件:module-a-db.properites,module-asearchengine.
    properties等等。每個文件中都包含了某個運行環境中的關於module A模塊
    的配置參數。
    現在,你可以不關心module A了!你只要使用下面的命令:
    例 12.21. 指定共享的properties文件
    autoconfig -s module-a-db.properties,module-a-searchengine.properties ……
    -s參數代表「共享的properties文件」。
    AutoConfig工具使用指南
    228
    同時,你的antx.properties也被簡化了,因為這裡只會保存你定義的配置項,而不會包含共
    享的配置項。
    12.4.4.2. 共享整個目錄
    假如共享的文件很多的話,AutoConfig還有一個貼心的功能,你可以把這些文件按目錄來組
    織:
    例 12.22. 按目錄組織要被共享的properties文件
    shared-properties/
    ├─test/ // 測試環境的共享配置
    │ module-a-db.properties
    │ module-a-searchengine.properties
    │ module-b.properties
    └─prod/ // 生產環境的共享配置
    module-a-db.properties
    module-a-searchengine.properties
    module-b.properties
    然後,你可以直接在AutoConfig中引用目錄:
    例 12.23. 共享指定目錄中的所有properties文件
    autoconfig -s shared-propertes/test/ ……
    AutoConfig就會為你裝載這個目錄下的所有共享配置文件。(注意,目錄必須以斜杠「/」結
    尾)
    12.4.4.3. 將共享目錄放在http、https或ssh伺服器上
    AutoConfig還支持從http、https或ssh伺服器上取得共享配置文件,只需要將前面例子中的文
    件名改成http或ssh的URI就可以了:
    例 12.24. 共享遠程伺服器上的properties文件或目錄
    autoconfig -s http://share.alibaba.com/shared-propertes/test/ ……
    autoconfig -s http://myname@share.alibaba.com/shared-propertes/test/ ……
    autoconfig -s https://share.alibaba.com/shared-propertes/test/ ……
    autoconfig -s https://myname@share.alibaba.com/shared-propertes/test/ ……
    autoconfig -s ssh://myname@share.alibaba.com/shared-propertes/test/ ……
    共享遠程http伺服器上的properties文件。
    共享遠程http伺服器上的properties文件,指定登錄用戶名。
    共享遠程https伺服器上的properties文件。
    共享遠程https伺服器上的properties文件,指定登錄用戶名。
    共享遠程ssh伺服器上的properties文件,必須指定用戶名。
    由於Subversion、Git伺服器是支持HTTP/HTTPS協議的,因此將properties文件存放在
    Subversion或Git伺服器上,也是一個極好的辦法。由於採用了Subversion或Git,properties文
    件的版本管理問題也被一舉解決了。
    需要注意的是,訪問http和ssh有可能需要驗證用戶和密碼。當需要驗證時,AutoConfig會提示
    你輸入用戶名和密碼。輸入以後,密碼將被保存在$HOME/passwd.autoconfig文件中,以後就
    不需要重複提問了。
    AutoConfig工具使用指南
    229
    12.4.4.4. 在多種配置項中切換
    當你使用前文所述的autoconfig –s命令來生成antx.properties文件時,你會發
    現antx.properties中增加了幾行特別的內容:
    例 12.25. 包含共享文件、目錄信息的antx.properties文件
    antx.properties.default = http://share.alibaba.com/shared-propertes/test/
    如果你在-s參數中指定了多項共享properties文件或目錄,那麼antx.properties中將會這
    樣:
    antx.properties.default.1 = http://share.alibaba.com/shared-propertes/test/
    antx.properties.default.2 = file:/shared-properties/test/my-1.properites
    antx.properties.default.3 = file:/shared-properties/test/my-2.properites
    事實上,AutoConfig還支持多組共享配置,請試用下面的命令:
    例 12.26. 使用多組共享配置
    autoconfig -s http://share.alibaba.com/shared-propertes/test/ -n test ……
    為當前共享配置定義一個名字,以後可以用這個名字來簡化命令。
    這時,antx.properties就會是這個樣子:
    antx.properties = test
    antx.properties.test = http://share.alibaba.com/shared-propertes/test/
    再執行:
    autoconfig -s http://share.alibaba.com/shared-propertes/prod/ -n prod ……
    antx.properties就會變成這個樣子:
    antx.properties = prod
    antx.properties.test = http://share.alibaba.com/shared-propertes/test/
    antx.properties.prod = http://share.alibaba.com/shared-propertes/prod/
    以後再執行,就不需要再指定-s參數了,只需用-n參數選擇一組共享properties文件即可。例
    如:
    autoconfig -n prod …… // 使用prod生產環境的參數
    autoconfig -n test …… // 使用test測試環境的參數
    autoconfig …… // 不指定,則使用最近一次所選擇的共享文件
    12.4.5. AutoConfig常用命令
    下面羅列了AutoConfig的常用的命令及參數:
    12.4.5.1. 指定互動式界面的charset
    一般不需要特別指定charset,除非AutoConfig自動識別系統編碼出錯,導致顯示亂碼。
    運行AutoConfig獨立可執行程

    autoconfig ... -c GBK
    AutoConfig工具使用指南
    230
    運行AutoConfig maven插件mvn ... -Dautoconfig.charset=GBK
    12.4.5.2. 指定交互模式
    默認情況下,交互模式為自動(auto)。僅當user properties中的值不滿足auto-config.xml中
    的定義時,才會互動式地引導用戶提供properties值。
    但你可以強制打開交互模式:
    運行AutoConfig獨立可執行程

    autoconfig ... –i
    autoconfig ... –i on
    運行AutoConfig maven插件mvn ... -Dautoconfig.interactive
    mvn ... -Dautoconfig.interactive=true
    或強制關閉交互模式:
    運行AutoConfig獨立可執行程

    autoconfig ... –I
    autoconfig ... –i off
    運行AutoConfig maven插件mvn ... -Dautoconfig.interactive=false
    12.4.5.3. 指定user properties
    默認情況下,AutoConfig會按下列順序查找user properties:
    1. 當前目錄/antx.properties
    2. 當前用戶HOME目錄/antx.properties
    但你可以指定一個自己的properties文件,用下面的命令:
    運行AutoConfig獨立可執行程

    autoconfig ... –u my.props
    運行AutoConfig maven插件mvn ... -Dautoconfig.userProperties=my.props
    12.4.5.4. 顯示詳細的信息
    默認情況下,AutoConfig只輸出重要的信息,但有時你想了解更多內部的情況,只需要用下面
    的命令:
    運行AutoConfig獨立可執行程

    autoconfig ... –v
    運行AutoConfig maven插件不適用
    12.4.5.5. 指定輸出文件
    默認情況下,AutoConfig所生成的配置文件以及日誌信息會直接輸出到當前包文件或目錄中。
    例如以下命令會改變petstore.war的內容:
    autoconfig petstore.war
    但你可以指定另一個輸出文件或目錄,這樣,原來的文件或目錄就不會被修改:
    運行AutoConfig獨立可執行程

    autoconfig petstore.war –o petstore-configured.war
    運行AutoConfig maven插件不適用
    AutoConfig工具使用指南
    231
    12.4.5.6. 避免執行AutoConfig
    將AutoConfig和maven package phase綁定以後,每次build都會激活AutoConfig。假如
    你想跳過這一步,只需要下面的命令:
    運行AutoConfig獨立可執行程

    不適用
    運行AutoConfig maven插件mvn ... -Dautoconfig.skip
    12.4.5.7. 避免中斷maven build
    默認情況下,假如發現有未定義的placeholders,AutoConfig會報錯並中止maven的執行。
    假如你不想中斷maven build,可以這樣做:
    運行AutoConfig獨立可執行程

    不適用
    運行AutoConfig maven插件mvn ... -Dautoconfig.strict=false
    12.5. 本章總結
    AutoConfig是一個簡單而有用的小工具,彌補了Maven Filtering及類似機制的不足。但它還有
    不少改進的餘地。
    • 界面不夠直觀。如果能夠通過GUI或WEB界面來配置,就更好了。
    • Properties validator目前不易擴展。
    • 缺少集成環境的支持。
    232
    第 13 章 AutoExpand工具使用指南
    13.1. AutoExpand工具簡介 ......................................................................................... 232
    13.1.1. Java、JavaEE打包的格式 ......................................................................... 232
    13.1.2. 應用布署的方式 ......................................................................................... 233
    13.1.3. AutoExpand的用武之地 ........................................................................... 233
    13.2. AutoExpand的使用 ............................................................................................. 234
    13.2.1. 取得AutoExpand ..................................................................................... 234
    13.2.2. 執行AutoExpand ..................................................................................... 234
    13.2.3. AutoExpand和AutoConfig的合作 ............................................................ 235
    13.3. AutoExpand的參數 ............................................................................................. 236
    13.4. 本章總結 ............................................................................................................. 237
    13.1. AutoExpand工具簡介
    AutoExpand是一個小工具,可以快速地把應用包展開到目錄中。
    13.1.1. Java、JavaEE打包的格式
    Java和JavaEE的應用通常被打成一個ZIP格式的包。
    表 13.1. 標準的Java、JavaEE包的格式
    包類型說明
    jar Java ARchive。共享類庫,EJB,獨立應用。
    war Web application ARchive。WEB應用。
    ear Enterprise ARchive。企業級應用。
    rar Resource Adapter Archive。
    其中jar和war包是當今最常用的格式。其次還有ear。
    • Ear包中可以包含多個jar包(包括ejb-jar包)、rar包、war包。
    • War包中可以包含多個jar包。
    例 13.1. 包的內部格式
    下面是一個典型的ear包的內部格式:
    myapp.ear
    │ foo-ejb.jar // 內嵌ejb-jar包。
    │ bar.jar // 內嵌普通jar包。
    │ myweb.war // 內嵌war包。

    └─META-INF
    application.xml // EAR描述文件。
    下面是一個典型的war包的內部格式:
    AutoExpand工具使用指南
    233
    myweb.war
    │ index.jsp

    ├─images
    ├─META-INF
    └─WEB-INF
    │ web.xml // WAR描述文件。

    ├─classes
    │ foo.class // Java類文件。

    └─lib
    bar.jar // 內嵌jar包。
    baz.jar
    13.1.2. 應用布署的方式
    多數應用伺服器都支持兩種類型的布署方式:以包的形式布署,或者以展開目錄的形式布署。
    表 13.2. 布署應用的方法
    方法用途
    以包的形式布署你可以把WEB應用的war包直接發布在應用伺服器上,應用伺服器不需要把包打
    開就能運行它。
    發布一個應用包(只有一個文件),對於deployer來說是比較方便的。通常他只
    需要把這個包往指定的應用伺服器目錄一丟,就可以把應用跑起來。
    但是以包形式發布的應用不太好調試,因為你為了修改包中的任何一個文件,都
    必須重新打包。這是很費時的工作。
    以展開目錄的形式布署以展開目錄的形式布署的應用,更適合於開發階段。這樣,開發者可以方便地替
    換應用中的任何一個文件,而不需要重新打包。這節省了很多時間。
    13.1.3. AutoExpand的用武之地
    AutoExpand的功能就是把包文件展開到文件夾中。
    事實上,對於多數情況來說,用AutoExpand來展開一個包,和直接使用jar命令來展開包是沒
    有差別的。例如,展開一個war包,可以用下面兩種方法:
    例 13.2. 用AutoExpand展開一個war包
    $ autoexpand myweb.war
    Detected system charset encoding: UTF-8
    If your can't read the following text, specify correct one like this:
    autoexpand -c mycharset
    Expanding: /.../myweb.war
    To: /.../myweb
    done.
    總耗費時間:762毫秒
    例 13.3. 用jar命令展開一個war包
    $ mkdir myweb
    $ cd myweb
    $ jar xvf ../myweb.war
    創建:META-INF/
    解壓 META-INF/MANIFEST.MF
    ...
    AutoExpand工具使用指南
    234
    但是使用AutoExpand有如下好處:
    • 可展開嵌套的包,例如:一個ear中包含war包,用AutoExpand可以一舉把它們同時展開。
    • 支持更多選項,例如:更新、覆蓋、刪除多餘文件等。
    • 比jar命令速度更快。
    13.2. AutoExpand的使用
    13.2.1. 取得AutoExpand
    請參考第 12.4.1.1 節 「取得可執行文件」。
    13.2.2. 執行AutoExpand
    直接輸入autoexpand可得到如下幫助信息。
    例 13.4. AutoExpand的幫助信息
    $ autoexpand
    Detected system charset encoding: UTF-8
    If your can't read the following text, specify correct one like this:
    autoexpand -c mycharset
    使用方法:antxexpand [可選參數] 文件名 [目標目錄]
    可選參數:
    -c,--charset 輸入/輸出編碼字符集
    -e,--expand-ejb-jar 是否展開ejb-jar(yes|no),默認為no
    -h,--help 顯示幫助信息
    -k,--keep-redundant-files 如果目標目錄中有多餘的文件,是否保持而不刪除,默認為no
    -o,--overwrite 如果目標目錄中的文件比zip文件中的項要新,是否覆蓋之,默認為no
    -r,--expand-rar 是否展開rar(yes|no),默認為yes
    -v,--verbose 顯示更多信息
    -w,--expand-war 是否展開war(yes|no),默認為yes
    總耗費時間:203毫秒
    最簡單的AutoExpand命令如下:
    例 13.5. 最簡單的AutoExpand命令
    autoexpand myweb.war
    這條命令將myweb.war展開到myweb目錄中。
    你也可以指定一個輸出目錄:
    例 13.6. 執行AutoExpand命令:指定輸出目錄
    autoexpand myweb.war myweb-expanded.war
    這條命令將myweb.war展開到myweb-expanded.war目錄中。在目錄名中指定後綴(如.war)
    是一個好主意。這樣,同一個名稱(*.war)既可作為目錄名,也可作為包名。你可以在目錄和
    包文件之間自由地切換,而不需要改動伺服器的腳本或配置。
    AutoExpand工具使用指南
    235
    你可以用一條命令展開嵌套的包。例如:
    例 13.7. 執行AutoExpand命令:展開嵌套的包
    autoexpand myapp.ear myapp-expanded.ear
    這條命令可以一舉把ear以及ear中的所有war都展開。
    例 13.8. 展開ear以及ear中的所有war
    myapp-expanded.ear
    │ foo-ejb.jar
    │ bar.jar

    ├─myweb.war // 展開嵌套的war
    │ │ index.jsp
    │ │
    │ ├─images
    │ ├─META-INF
    │ └─WEB-INF
    │ │ web.xml
    │ │
    │ ├─classes
    │ │ foo.class
    │ │
    │ └─lib
    │ bar.jar
    │ baz.jar

    └─META-INF
    application.xml
    13.2.3. AutoExpand和AutoConfig的合作
    你可以讓AutoConfig maven插件在配置完應用以後,將應用展開到指定的目錄。
    AutoExpand工具使用指南
    236
    例 13.9. AutoConfig完成後,再用AutoExpand展開
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    ...
    <properties>
    ...
    <!-- 定義autoconfig的版本,建議將此行寫在parent pom.xml中。 -->
    <autoconfig-plugin-version>1.0.10</autoconfig-plugin-version>
    </properties>
    ...
    <build>
    <plugins>
    <plugin>
    <groupId>com.alibaba.citrus.tool</groupId>
    <artifactId>maven-autoconfig-plugin</artifactId>
    <version>${autoconfig-plugin-version}</version>
    <configuration>
    ...
    <!-- 配置后,是否展開目標文件,默認為false,不展開。 -->
    <exploding>true</exploding>
    <!-- 展開到指定目錄,默認為${project.build.directory}/${project.build.finalName}。 -->
    <explodedDirectory>
    ${project.build.directory}/${project.build.finalName}
    </explodedDirectory>
    </configuration>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>autoconfig</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    </project>
    關於AutoConfig詳情,請看第 12 章 AutoConfig工具使用指南。
    13.3. AutoExpand的參數
    AutoExpand可用的參數包括:
    表 13.3. AutoExpand命令的參數
    參數名說明
    -w 或 --expand-war 是否展開war(yes或no),默認為yes。
    -r 或 --expand-rar 是否展開rar(yes或no),默認為yes。
    -e 或 --expand-ejb-jar 是否展開ejb-jar(yes或no),默認為no。
    展開ejb-jar需要讀取/META-INF/application.xml文件,因此會降低展開的速
    度。
    -o 或 --overwrite 如果目標目錄中的文件比包文件中的項要新,是否覆蓋之,默認為no。
    指定該參數可強制覆蓋文件。
    如果你不信任文件中的時間戳,就可以指定這個參數。
    -k 或 --keep-redundantfiles
    如果目標目錄存在多餘的文件(也就是包里不存在的文件),AutoExpand會將它
    們刪除,以確保展開后的目錄內容與包的內容完全一致,不多不少。
    AutoExpand工具使用指南
    237
    參數名說明
    指定這個參數可以避免AutoExpand刪除多餘的文件。
    -v 或 --verbose 顯示更多信息。
    -c 或 --charset 如果AutoExpand顯示的信息是亂碼,請通過這個參數指定正確的字符集編碼。
    13.4. 本章總結
    AutoExpand雖然小,但很好用,可以作為jar命令部分功能的替代品。此外,AutoConfig也利
    用AutoExpand來展開經過配置的包文件。
    Bookmark the permalink ,來源:互聯網
    One thought on “webx小結

    推薦閱讀文章