A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

恕我斗胆直言,对开源的 WEB 框架了解多少,有没有尝试写过框架呢?XML 的解析方式有哪些?能答出来吗?!
心中没有答案也没关系,因为通过今天的分享,能让你轻松 get 如下几点,绝对收获满满。
a)XML 解析的方式;
b)digester 的用法;
c)  Java WEB 框架的实现思路;
d)从 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 的方式进行解析,应该怎么做?(配置文件的内容有似曾相识的感觉没?文末解谜)
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<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>
复制代码
2.1. 定义解析规则文件 rule.xml
digester 进行解析 xml,需要依赖解析规则(就是告诉 digester 怎么个解析法)。可以使用 Java 硬编码的方式指定解析规则;也可以采用零配置思想,使用注解的方式来指定解析规则;还可以使用 xml 方式配置解析规则。

为了清晰起见,本次就采用 xml 方式进行配置解析规则,解析规则 rule.xml 内容如下。

复制代码
<?xml version='1.0' encoding='UTF-8'?>
<digester-rules>
    <pattern value="action-mappings">
        <!-- value是匹配的xml标签的名字,匹配<action>标签 -->
        <pattern value="action">
            <!--每碰到一个action标签,就创建指定类的对象-->
            <object-create-rule classname="org.yyxj.miniframework.config.ActionMapping"/>
            <!--
                对象创建后,调用ActionMappings的addActionMapping()方法,
                将其加入它上一级元素所对应的对象ActionMappings中
            -->
            <set-next-rule methodname="addActionMapping"/>
            <!--
                将action元素的各个属性按照相同的名称
                赋值给刚刚创建的ActionMapping对象
            -->
            <set-properties-rule/>
            <!-- 匹配<forward>标签 -->
            <pattern value="forward">
                <!--每碰到一个forward标签,就创建指定类的对象-->
                <object-create-rule classname="org.yyxj.miniframework.config.ForwardBean"/>
                <!--
                    对象创建后,调用ActionMapping的addForwardBean()方法,
                    将其加入它上一级元素所对应的对象ActionMapping中
                -->
                <set-next-rule methodname="addForwardBean"/>
                <!--
                    将forward元素的各个属性按照相同的名称
                    赋值给刚刚创建的ForwardBean对象
                -->
                <set-properties-rule/>
            </pattern>
        </pattern>
    </pattern>
</digester-rules>
复制代码
2.2. 创建规则解析依赖的 Java 类
首先是 ActionMappings 类,要提供 addActionMapping 方法以便添加 ActionMapping 对象,考虑到后面会依据请求路径找 ActionMapping,索性也定义一个 findActionMapping 的方法,代码如下。

复制代码
package org.yyxj.miniframework.config;

import java.util.HashMap;
import java.util.Map;

/**
* @author 一猿小讲
*/
public class ActionMappings {

    private Map<String, ActionMapping> mappings = new HashMap<String, ActionMapping>();

    public void addActionMapping(ActionMapping mapping) {
        this.mappings.put(mapping.getPath(), mapping);
    }

    public ActionMapping findActionMapping(String path) {
        return this.mappings.get(path);
    }

    @Override
    public String toString() {
        return mappings.toString();
    }
}
复制代码
依据解析规则文件,接下来会匹配到 miniframework-config.xml 文件的 action 标签,要定义对应的 ActionMapping 类,包含请求路径及让谁处理的类路径,当然也要提供 addForwardBean 方法用于添加 ForwardBean 对象,代码定义如下。

复制代码
package org.yyxj.miniframework.config;

import java.util.HashMap;
import java.util.Map;

/**
* @author 一猿小讲
*/
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;
    }

    @Override
    public String toString() {
        return path + "==" + type + "==" + this.forwards.toString();
    }
}
复制代码
依据解析规则文件,接下来会匹配到 miniframework-config.xml 文件的 forward 标签,那么就要创建与之对应的 ForwardBean 类,并且拥有 name、path、redirect 三个属性,代码定义如下。

复制代码
package org.yyxj.miniframework.config;

/**
* @author 一猿小讲
*/
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;
    }

    @Override
    public String toString() {
        return name + "==" + path + "==" + redirect;
    }
}
复制代码
2.3. 引入依赖包,编写测试类
<dependency>
    <groupId>commons-digester</groupId>
    <artifactId>commons-digester</artifactId>
    <version>2.1</version>
</dependency>
编写测试类。

复制代码
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;

/**
* Digester用法测试类
*
* @author 一猿小讲
*/
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);
    }
}
复制代码
2.4. 跑起来,看看解析是否 OK?
程序输出如下:
{/doOne=/doOne==org.yyxj.miniframework.action.OneAction=={one=one==/one.jsp==false}, /doTwo=/doTwo==org.yyxj.miniframework.action.TwoAction=={two=two==/two.jsp==true}}
到这儿 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复制代码
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;

/**
* 中央控制器
* 1. 接收客户端的请求;
* 2. 根据请求的 path 找到对应的 Action 来处理业务。
*
* @author 一猿小讲
*/
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";

    @Override
    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) {
            // LOG
        }
    }

    @Override
    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("."));

        // 1:根据请求路径获取对应的 Action 来处理具体的业务
        ActionMapping mapping = mappings.findActionMapping(path);
        try {
            Action action = (Action) Class.forName(mapping.getType()).newInstance();
            // 2:进行业务处理,并返回执行的结果
            String result = action.execute(request, response);
            // 3:依据执行结果找到对应的 ForwardBean
            ForwardBean forward = mapping.findForwardBean(result);
            // 4:响应
            if (forward.isRedirect()) {
                response.sendRedirect(request.getContextPath() + forward.getPath());
            } else {
                request.getRequestDispatcher(forward.getPath()).forward(request, response);
            }
        } catch (Exception e) {
            // LOG
            System.err.println(String.format("service ex [%s]", e.getMessage()));
        }
    }
}
复制代码
4.2. 业务控制器 Action 及业务实现
复制代码
package org.yyxj.miniframework.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 业务控制器
* @author 一猿小讲
*/
public abstract class Action {
    public abstract String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
复制代码
紧接着就定义具体的业务实现呗。

复制代码
package org.yyxj.miniframework.action;

import org.yyxj.miniframework.controller.Action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 业务实现
*
* @author 一猿小讲
*/
public class OneAction extends Action {

    @Override
    public String execute(HttpServletRequest request,
                          HttpServletResponse response) throws Exception {
        return "one";
    }
}
复制代码
TwoAction 与 OneAction 一样都是继承了 Action,实现 execute 方法。

复制代码
package org.yyxj.miniframework.action;

import org.yyxj.miniframework.controller.Action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 业务实现
*
* @author 一猿小讲
*/
public class TwoAction extends Action {
    @Override
    public String execute(HttpServletRequest request,
                          HttpServletResponse response) throws Exception {
        return "two";
    }
}
复制代码
4.3. 配置 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">
    <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>
复制代码
4.4. 画三个 JSP 页面出来,便于验证
index.jsp 内容如下。

复制代码
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML 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>
复制代码
one.jsp 内容如下。

<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

一猿小讲 say one .... ...
two.jsp 内容如下。

<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

一猿小讲 say two .... ...
4.5. 部署、启动 WEB 服务
到这一个迷你版的 WEB 框架就完事啦,把项目打成 war 包,放到 tomcat 里跑起来,验证一下。

4.6. 项目结构一览

蓝色圈住部分可以打成 miniframework.jar 包,当做可复用类库,在其它项目中直接引入,只需编写红色圈住部分的业务 Action 以及页面就好啦。


以上内容转载自网络


1 个回复

倒序浏览
更多讯息欢迎添加小优:DKA-2018
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马