搞懂 XML 解析,徒手造 WEB 框架

恕我斗膽直言,對開源的 WEB 框架了解多少,有沒有嘗試寫過框架呢?XML 的解析方式有哪些?能答出來嗎?!

心中沒有答案也沒關係,因為通過今天的分享,能讓你輕鬆 get 如下幾點,絕對收穫滿滿。

1. XML 解析的方式;

2. digester 的用法;

3. Java WEB 框架的實現思路;

4. 從 0 到 1 徒手實現一個迷你 WEB 框架。


1. XML 解析方式


在 Java 項目研發過程中,不論項目大小,幾乎都能見到 XML 配置文件的蹤影。使用 XML 可以進行項目配置;也可以作為對接三方 API 時數據封裝、報文傳輸轉換,等等很多使用場景。


而 XML 文件該如何解析?則是一個老生常談的問題,也是研發中選型經常面臨的一個問題。通過思維導圖梳理,把問題都扼殺在搖籃裡。

搞懂 XML 解析,徒手造 WEB 框架

如導圖所示,DOM 和 SAX 是 XML 常見的兩大核心解析方式,兩者的主要區別在於它們解析 XML 文件的方式不同。使用 DOM 解析,XML 文件以 DOM 樹形結構加載入內存,而 SAX 採用的是事件模型。


基於這兩大解析方式,衍生了一系列的 API,也就是造出了一大批輪子,到底用哪款輪子呢?下面就叨咕叨咕。

搞懂 XML 解析,徒手造 WEB 框架

上面羅列的這些,你都知道或者用過嗎?為了便於你記憶,咱們就聊聊發展歷史吧。


首先 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

ActionMapping

findActionMapping

(String path)

{

return

this

.mappings.get(path); }

public

String

toString

()

{

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

String

getName

()

{

return

name; }

public

void

setName

(String name)

{

this

.name = name; }

public

String

getPath

()

{

return

path; }

public

void

setPath

(String path)

{

this

.path = path; }

public

boolean

isRedirect

()

{

return

redirect; }

public

void

setRedirect

(

boolean

redirect)

{

this

.redirect = redirect; }

public

String

toString

()

{

return

name +

"=="

+ path +

"=="

+ redirect; } }/<code>

2.3. 引入依賴包,編寫測試類

<code>

<

dependency

>

<

groupId

>

commons-digester

groupId

>

<

artifactId

>

commons-digester

artifactId

>

<

version

>

2.1

version

>

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 文件,怕你忘記,就再貼一遍。

搞懂 XML 解析,徒手造 WEB 框架

圖中紅色圈住部分,其實可以這麼理解,當用戶請求的 path 為 /doOne 時,會交給 OneAction 去處理,處理完之後的返回結果若是 one,則跳轉到 one.jsp,給前端響應。


為了說的更清晰,說清楚思路,還是畫一張圖吧。

搞懂 XML 解析,徒手造 WEB 框架

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

String

execute

(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

String

execute

(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

String

execute

(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

>

ActionServlet

servlet-name

>

<

servlet-class

>

org.yyxj.miniframework.controller.ActionServlet

servlet-class

>

servlet

>

<

servlet-mapping

>

<

servlet-name

>

ActionServlet

servlet-name

>

<

url-pattern

>

*.do

url-pattern

>

servlet-mapping

>

<

welcome-file-list

>

<

welcome-file

>

index.jsp

welcome-file

>

welcome-file-list

>

web-app

>

/<code>

4.4. 畫三個 JSP 頁面出來,便於驗證

index.jsp 內容如下。

<code>

<

%@

page

pageEncoding

=

"UTF-8"

%>

PUBLIC

"-//W3C//DTD HTML 4.01 Transitional//EN"

>

<

html

>

<

head

>

<

title

>

My JSP 'index.jsp' starting page

title

>

head

>

<

body

>

<

a

href

=

"doOne.do"

>

DoOneAction

a

>

<

br

/>

<

a

href

=

"doTwo.do"

>

DoTwoAction

a

>

<

br

/>

body

>

html

>

/<code>

one.jsp 內容如下。

<code>

<

%@

page

pageEncoding

=

"UTF-8"

%>

PUBLIC

"-//W3C//DTD HTML 4.01 Transitional//EN"

> 一猿小講 say one .... .../<code>

two.jsp 內容如下。

<code>

<

%@

page

pageEncoding

=

"UTF-8"

%>

PUBLIC

"-//W3C//DTD HTML 4.01 Transitional//EN"

> 一猿小講 say two .... .../<code>

4.5. 部署、啟動 WEB 服務

到這一個迷你版的 WEB 框架就完事啦,把項目打成 war 包,放到 tomcat 裡跑起來,驗證一下。

搞懂 XML 解析,徒手造 WEB 框架

4.6. 項目結構一覽

搞懂 XML 解析,徒手造 WEB 框架

藍色圈住部分可以打成 miniframework.jar 包,當做可複用類庫,在其它項目中直接引入,只需編寫紅色圈住部分的業務 Action 以及頁面就好啦。


5. 答疑解謎


本次主要聊了聊 xml 解析的方式,著重分享了 digester 的用法,並站在 digester 解析 xml 的基礎之上,徒手模擬了一個 WEB 的迷你版的框架。

如果你研究過 Tomcat 的源碼或者使用過 Struts 的話,今天的分享應該很容易掌握,因為它們都用到了 digester 進行解析配置文件。

鑑於目前據我知道的很多公司的老項目,技術棧還停留在 Struts 上,所以有必要進行一次老技術新談。

坊間這麼說「只要會 XML 解析,搞懂反射,熟悉 Servlet,面試問到什麼框架都不怕,因為打通了任督二脈,框架看一眼就基本知道原理啦」。

不過,技術更新確實快,稍有不慎就 out,不過在追逐新技術的同時,老技術的思想理念也別全拋在腦後,如果真能打通任督二脈,做到融會貫通那就最好啦。

好了,本次的分享就到這裡,希望你們喜歡。

歡迎關注「一猿小講」,瞭解更多精彩。


分享到:


相關文章: