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

来源:https://www.cnblogs.com/socoool/archive/2020/04/20/12737170.html
-Advertisement-
Play Games

恕我斗膽直言,對開源的 WEB 框架瞭解多少,有沒有嘗試寫過框架呢?XML 的解析方式有哪些?能答出來嗎?! 心中沒有答案也沒關係,因為通過今天的分享,能讓你輕鬆 get 如下幾點,絕對收穫滿滿。 a)XML 解析的方式; b)digester 的用法; c) Java WEB 框架的實現思路; d ...


恕我斗膽直言,對開源的 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 以及頁面就好啦。

5. 答疑解謎

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

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

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

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

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

好了,本次的分享就到這裡,希望你們喜歡,請多關註一猿小講,後續會輸出更多原創精彩文章,敬請期待!

可以微信搜索公眾號「 一猿小講 」回覆「1024」get 精心為你準備的編程進階資料。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.in關鍵字 屬性名 in 對象,用來判斷某個屬性在對象中是否存在與其嚴格相等的屬性名,返回boolean值 屬性名必須是字元串或數字 var obj = { a : 1, b : 2 } console.log('a' in obj);//返回true,obj對象中存在a屬性名 var arr ...
  • flex很早就出來了,但是由於相容性很差,一直不火。 目前個人只在手機端中小心翼翼的使用flex,整理個模板出來,橫軸的! 模板css: .children{ height: 20px; border: 1px solid red; margin: 2px; } .parent{ width: 10 ...
  • Bootstrap 滾動監聽 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo1</title> <meta name="viewport" content="width=device-width, ...
  • 今天給大家整理出來了web前端工程師初級階段需要掌握的內容,很全面,希望大家好好閱讀,看看自己掌握的知識點和文章裡面寫的還相差多少。 什麼是初級web前端工程師? 按照我的想法,我把前端工程師分為了入門、初級、中級、高級這四個級別, 入門級別指的是瞭解什麼是前端(前端到底是什麼其實很多人還是不清楚的 ...
  • 北冥複習html(二) 一、表格的具體組成單位 由行和列組成的單元格 結構: <table border="1"> <tr> <td>row1,cell1</td> <td>row1,cell2</td> </tr> </table> table ==》 定義表格; border ==》 設置表格邊 ...
  • 面向對象的基本思想 基本思路就是,創建一個對象,給這個對象,添加上屬性和屬性值,還有函數等方法 之後通過操作這個對象,來完成需要的效果 1.先通過一個函數的方法,來創建對象 2.給對象添加屬性和屬性值 3.給對象添加方法 4.返回這個創建好的對象 return obj; 5.調用函數,函數創建對象 ...
  • 公司有一項儲值卡充值業務:客戶在微信公眾號開通儲值卡服務,通過微信支付往卡裡面充值,充值成功後客戶可收到消息通知,併進行消費。 看起來是一項很簡單的業務,最初我們儲值卡團隊的實現也確實很簡單。我們看看最初的實現: 相信聰明的你一眼就能看出問題: 1. 壓根沒有考慮分散式事務一致性,比如第 12 步根 ...
  • 關於消息隊列,我們來思考這麼幾個問題: 1、MQ為什麼再系統中使用?一定要在分散式系統中使用嗎? 2、MQ有哪些中間件?他們有哪些特點? 3、MQ給系統帶來好處的同時有沒有帶來什麼問題?如何解決? 一般在我們面試的時候,面試官一般會問如下問題: 1、你的項目中MQ的作用? 2、為什麼選擇這款MQ作為 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...