搞懂 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,不过在追逐新技术的同时,老技术的思想理念也别全抛在脑后,如果真能打通任督二脉,做到融会贯通那就最好啦。

好了,本次的分享就到这里,希望你们喜欢。

欢迎关注「一猿小讲」,了解更多精彩。


分享到:


相關文章: