恕我斗膽直言,對開源的 WEB 框架了解多少,有沒有嘗試寫過框架呢?XML 的解析方式有哪些?能答出來嗎?!
心中沒有答案也沒關係,因為通過今天的分享,能讓你輕鬆 get 如下幾點,絕對收穫滿滿。
1. XML 解析的方式;
2. digester 的用法;
3. Java WEB 框架的實現思路;
4. 從 0 到 1 徒手實現一個迷你 WEB 框架。
1. XML 解析方式
在 Java 項目研發過程中,不論項目大小,幾乎都能見到 XML 配置文件的蹤影。使用 XML 可以進行項目配置;也可以作為對接三方 API 時數據封裝、報文傳輸轉換,等等很多使用場景。
而 XML 文件該如何解析?則是一個老生常談的問題,也是研發中選型經常面臨的一個問題。通過思維導圖梳理,把問題都扼殺在搖籃裡。
如導圖所示,DOM 和 SAX 是 XML 常見的兩大核心解析方式,兩者的主要區別在於它們解析 XML 文件的方式不同。使用 DOM 解析,XML 文件以 DOM 樹形結構加載入內存,而 SAX 採用的是事件模型。
基於這兩大解析方式,衍生了一系列的 API,也就是造出了一大批輪子,到底用哪款輪子呢?下面就叨咕叨咕。
上面羅列的這些,你都知道或者用過嗎?為了便於你記憶,咱們就聊聊發展歷史吧。
首先 JAXP 的出現是為了彌補 JAVA 在 XML 標準制定上的空白,而制定的一套 JAVA XML 標準 API,是對底層 DOM、SAX 的 API 簡單封裝;而原始 DOM 對於 Java 開發者而言較為難用,於是一批 Java 愛好者為了能讓解析 XML 得心應手,碼出了 jdom;另一批人在 jdom 的基礎上另起爐灶,碼出了 dom4j,由於 jdom 性能不抵 dom4j,dom4j 則獨佔鰲頭,很多開源框架都用 dom4j 來解析配置文件。
XStream 本不應該出現在這裡,但是鑑於是經驗分享,索性也列了出來,在以往項目中報文轉換時用的稍微多些,尤其是支付 API 對接時用的超級多,使用它可以很容易的實現 Java 對象和 XML 文檔的互轉(感興趣的可以自行填補一下)。
digester 是採用 SAX 來解析 XML 文件,在 Tomcat 中就用 Digester 來解析配置,在 Struts 等很多開源項目,也都用到了 digester 來解析配置文件,在實際項目研發中,也會用它來做協議解析轉換,所以這塊有必要深入去說一下,對你看源碼應該會有幫助。
2. digester 的用法
弱弱問一句:有沒有聽過 digester,若沒有聽過,那勢必要好好讀本文啦。
假如要對本地的 miniframework-config.xml 文件,採用 digester 的方式進行解析,應該怎麼做?(配置文件的內容有似曾相識的感覺沒?文末解謎)
<code><
action-mappings
><
action
path
="/doOne"
type
="org.yyxj.miniframework.action.OneAction"
><
forward
name
="one"
path
="/one.jsp"
redirect
="false"
/>action
><
action
path
="/doTwo"
type
="org.yyxj.miniframework.action.TwoAction"
><
forward
name
="two"
path
="/two.jsp"
redirect
="true"
/>action
>action-mappings
>/<code>
2.1. 定義解析規則文件 rule.xml
digester 進行解析 xml,需要依賴解析規則(就是告訴 digester 怎麼個解析法)。可以使用 Java 硬編碼的方式指定解析規則;也可以採用零配置思想,使用註解的方式來指定解析規則;還可以使用 xml 方式配置解析規則。
為了清晰起見,本次就採用 xml 方式進行配置解析規則,解析規則 rule.xml 內容如下。
<code><
digester-rules
><
pattern
value
="action-mappings"
><
pattern
value
="action"
><
object-create-rule
classname
="org.yyxj.miniframework.config.ActionMapping"
/><
set-next-rule
methodname
="addActionMapping"
/><
set-properties-rule
/><
pattern
value
="forward"
><
object-create-rule
classname
="org.yyxj.miniframework.config.ForwardBean"
/><
set-next-rule
methodname
="addForwardBean"
/><
set-properties-rule
/>pattern
>pattern
>pattern
>digester-rules
>/<code>
2.2. 創建規則解析依賴的 Java 類
首先是 ActionMappings 類,要提供 addActionMapping 方法以便添加 ActionMapping 對象,考慮到後面會依據請求路徑找 ActionMapping,索性也定義一個 findActionMapping 的方法,代碼如下。
<code>package
org.yyxj.miniframework.config;import
java.util.HashMap;import
java.util.Map;public
class
ActionMappings
{private
Map mappings =new
HashMap();public
void
addActionMapping
(ActionMapping mapping)
{this
.mappings.put(mapping.getPath(), mapping); }public
ActionMappingfindActionMapping
(String path)
{return
this
.mappings.get(path); }public
StringtoString
()
{return
mappings.toString(); } }/<code>
依據解析規則文件,接下來會匹配到 miniframework-config.xml 文件的 action 標籤,要定義對應的 ActionMapping 類,包含請求路徑及讓誰處理的類路徑,當然也要提供 addForwardBean 方法用於添加 ForwardBean 對象,代碼定義如下。
<code>package org.yyxj.miniframework.config;import
java.util.HashMap;import
java.util.Map;public
class
ActionMapping {private
String
path;private
String
type
;private
Map<String
, ForwardBean> forwards =new
HashMap<String
, ForwardBean>();public
void
addForwardBean(ForwardBean bean) { forwards.put(bean.getName(), bean); }public
ForwardBean findForwardBean(String
name) {return
forwards.get(name); }public
String
getPath() {return
path; }public
void
setPath(String
path) {this
.path = path; }public
String
getType() {return
type
; }public
void
setType(String
type
) {this
.type =type
; }public
String
toString() {return
path +"=="
+type
+"=="
+this
.forwards.toString(); } }/<code>
依據解析規則文件,接下來會匹配到 miniframework-config.xml 文件的 forward 標籤,那麼就要創建與之對應的 ForwardBean 類,並且擁有 name、path、redirect 三個屬性,代碼定義如下。
<code>package
org.yyxj.miniframework.config;public
class
ForwardBean
{private
String name;private
String path;private
boolean
redirect;public
StringgetName
()
{return
name; }public
void
setName
(String name)
{this
.name = name; }public
StringgetPath
()
{return
path; }public
void
setPath
(String path)
{this
.path = path; }public
boolean
isRedirect
()
{return
redirect; }public
void
setRedirect
(
boolean
redirect) {this
.redirect = redirect; }public
StringtoString
()
{return
name +"=="
+ path +"=="
+ redirect; } }/<code>
2.3. 引入依賴包,編寫測試類
<code><
dependency
><
groupId
>commons-digestergroupId
><
artifactId
>commons-digesterartifactId
><
version
>2.1version
>dependency
>/<code>
編寫測試類。
<code>package
org.yyxj.miniframework.config;import
org.apache.commons.digester.Digester;import
org.apache.commons.digester.xmlrules.DigesterLoader;import
org.xml.sax.SAXException;import
java.io.IOException;public
class
Test
{public
static
void
main
(String[] args)
throws
IOException, SAXException { String rlueFile ="org/yyxj/miniframework/config/rule.xml"
; String configFile ="miniframework-config.xml"
; Digester digester = DigesterLoader.createDigester( Test.
class
.getClassLoader
().getResource
(rlueFile
)); ActionMappings mappings =new
ActionMappings(); digester.push(mappings); digester.parse(Test.
class
.getClassLoader
().getResource
(configFile
)); System.out.println(mappings); } }/<code>
2.4. 跑起來,看看解析是否 OK?
<code>程序輸出如下: {/doOne=/doOne==org.yyxj.miniframework.action.OneAction=={one=one==/
one.jsp==false
}, /doTwo=/doTwo==org.yyxj.miniframework.action.TwoAction=={two=two==/
two.jsp==true
}}/<code>
到這兒 digester 解析 xml 就算達到了預期效果,digester 解析其實起來很簡單,照貓畫虎擼兩遍,就自然而然掌握,所以不要被烏央烏央的代碼給嚇退縮(代碼只是方便你施展 CV 大法)。
不過,會用 digester 解析 xml 還不算完事,還想擴展一下思路,站在上面代碼的基礎之上,去嘗試實現一個迷你版的 WEB 框架。
3. WEB 框架的實現思路
此時請忘記 digester 解析的事情,腦海裡只需保留開篇提到的 miniframework-config.xml 文件,怕你忘記,就再貼一遍。
圖中紅色圈住部分,其實可以這麼理解,當用戶請求的 path 為 /doOne 時,會交給 OneAction 去處理,處理完之後的返回結果若是 one,則跳轉到 one.jsp,給前端響應。
為了說的更清晰,說清楚思路,還是畫一張圖吧。
ActionServlet 主要是接收用戶請求,然後根據請求的 path 去 AcitonMappings中尋找對應的 ActionMapping,然後依據 ActionMapping 找到對應的 Action,並調用 Action 完成業務處理,然後把響應視圖返回給用戶,多少都透漏著 MVC 設計模式中 C 的角色。
Action 主要是業務控制器,其實很簡單,只需提供抽象的 execute 方法即可,具體怎麼執行交給具體的業務實現類去實現吧。
4. 徒手實現迷你版的 WEB 框架
鑑於 ActionMappings、ActionMapping、ForwardBean 已是可複用代碼,主要是完成 miniframework-config.xml 文件的解析,那接下來只需把圖中缺失的類定義一下就 Ok 啦。
4.1. 中央控制器 ActionServlet
<code>package
org.yyxj.miniframework.controller;import
org.apache.commons.digester.Digester;import
org.apache.commons.digester.xmlrules.DigesterLoader;import
org.yyxj.miniframework.config.ActionMapping;import
org.yyxj.miniframework.config.ActionMappings;import
org.yyxj.miniframework.config.ForwardBean;import
javax.servlet.http.HttpServlet;import
javax.servlet.http.HttpServletRequest;import
javax.servlet.http.HttpServletResponse;import
java.io.IOException;public
class
ActionServlet
extends
HttpServlet
{private
static
final
long
serialVersionUID =1L
;private
ActionMappings mappings =new
ActionMappings();public
static
final
String RULE_FILE ="org/yyxj/miniframework/config/rule.xml"
;public
static
final
String EASY_STRUTS_CONFIG_FILE ="miniframework-config.xml"
;public
void
init
()
{ Digester digester = DigesterLoader.createDigester(ActionServlet.
class
.getClassLoader
().getResource
(RULE_FILE
)); digester.push(mappings);try
{ digester.parse(ActionServlet.
class
.getClassLoader
().getResource
(EASY_STRUTS_CONFIG_FILE
)); }catch
(Exception e) { } }public
void
service
(HttpServletRequest request, HttpServletResponse response)
throws
IOException { request.setCharacterEncoding("UTF-8"
); response.setContentType("text/html;charset=utf-8"
); String uri = request.getRequestURI(); String path = uri.substring(uri.lastIndexOf("/"
), uri.lastIndexOf("."
)); ActionMapping mapping = mappings.findActionMapping(path);try
{ Action action = (Action) Class.forName(mapping.getType()).newInstance(); String result = action.execute(request, response); ForwardBean forward = mapping.findForwardBean(result);if
(forward.isRedirect()) { response.sendRedirect(request.getContextPath() + forward.getPath()); }else
{ request.getRequestDispatcher(forward.getPath()).forward(request, response); } }catch
(Exception e) { System.err.println(String.format("service ex [%s]"
, e.getMessage())); } } }/<code>
4.2. 業務控制器 Action 及業務實現
<code>package
org.yyxj.miniframework.controller;import
javax.servlet.http.HttpServletRequest;import
javax.servlet.http.HttpServletResponse;public
abstract
class
Action
{public
abstract
Stringexecute
(HttpServletRequest request, HttpServletResponse response)
throws
Exception; }/<code>
緊接著就定義具體的業務實現唄。
<code>package
org.yyxj.miniframework.action;import
org.yyxj.miniframework.controller.Action;import
javax.servlet.http.HttpServletRequest;import
javax.servlet.http.HttpServletResponse;public
class
OneAction
extends
Action
{public
Stringexecute
(HttpServletRequest request, HttpServletResponse response)
throws
Exception {return
"one"
; } }/<code>
TwoAction 與 OneAction 一樣都是繼承了 Action,實現 execute 方法。
<code>package
org.yyxj.miniframework.action;import
org.yyxj.miniframework.controller.Action;import
javax.servlet.http.HttpServletRequest;import
javax.servlet.http.HttpServletResponse;public
class
TwoAction
extends
Action
{public
Stringexecute
(HttpServletRequest request, HttpServletResponse response)
throws
Exception {return
"two"
; } }/<code>
4.3. 配置 web.xml,配置服務啟動入口
<code><
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"
><
servlet
><
servlet-name
>ActionServletservlet-name
><
servlet-class
>org.yyxj.miniframework.controller.ActionServletservlet-class
>servlet
><
servlet-mapping
><
servlet-name
>ActionServletservlet-name
><
url-pattern
>*.dourl-pattern
>servlet-mapping
><
welcome-file-list
><
welcome-file
>index.jspwelcome-file
>welcome-file-list
>web-app
>/<code>
4.4. 畫三個 JSP 頁面出來,便於驗證
index.jsp 內容如下。
<code><
%@
page
pageEncoding
="UTF-8"
%> ><
html
><
head
><
title
>My JSP 'index.jsp' starting pagetitle
>head
><
body
><
a
href
="doOne.do"
>DoOneActiona
><
br
/><
a
href
="doTwo.do"
>DoTwoActiona
><
br
/>body
>html
>/<code>
one.jsp 內容如下。
<code><
%@
page
pageEncoding
="UTF-8"
%> > 一猿小講 say one .... .../<code>
two.jsp 內容如下。
<code><
%@
page
pageEncoding
="UTF-8"
%> > 一猿小講 say two .... .../<code>
4.5. 部署、啟動 WEB 服務
到這一個迷你版的 WEB 框架就完事啦,把項目打成 war 包,放到 tomcat 裡跑起來,驗證一下。
4.6. 項目結構一覽
藍色圈住部分可以打成 miniframework.jar 包,當做可複用類庫,在其它項目中直接引入,只需編寫紅色圈住部分的業務 Action 以及頁面就好啦。
5. 答疑解謎
本次主要聊了聊 xml 解析的方式,著重分享了 digester 的用法,並站在 digester 解析 xml 的基礎之上,徒手模擬了一個 WEB 的迷你版的框架。
如果你研究過 Tomcat 的源碼或者使用過 Struts 的話,今天的分享應該很容易掌握,因為它們都用到了 digester 進行解析配置文件。
鑑於目前據我知道的很多公司的老項目,技術棧還停留在 Struts 上,所以有必要進行一次老技術新談。
坊間這麼說「只要會 XML 解析,搞懂反射,熟悉 Servlet,面試問到什麼框架都不怕,因為打通了任督二脈,框架看一眼就基本知道原理啦」。
不過,技術更新確實快,稍有不慎就 out,不過在追逐新技術的同時,老技術的思想理念也別全拋在腦後,如果真能打通任督二脈,做到融會貫通那就最好啦。
好了,本次的分享就到這裡,希望你們喜歡。
歡迎關注「一猿小講」,瞭解更多精彩。