Java工具類——通過配置XML驗證Map

来源:https://www.cnblogs.com/lger/archive/2019/04/02/10645453.html
-Advertisement-
Play Games

Java工具類——通過配置XML驗證Map 背景 在JavaWeb項目中,接收前端過來的參數時通常是使用我們的實體類進行接收的。但是呢,我們不能去決定已經搭建好的框架是怎麼樣的,在我接觸的框架中有一種就是通過Map來接收前端過來的所有參數,框架中沒有實體類的說法,從接收參數,驗證參數到參數至持久層整 ...


Java工具類——通過配置XML驗證Map

背景

在JavaWeb項目中,接收前端過來的參數時通常是使用我們的實體類進行接收的。但是呢,我們不能去決定已經搭建好的框架是怎麼樣的,在我接觸的框架中有一種就是通過Map來接收前端過來的所有參數,框架中沒有實體類的說法,從接收參數,驗證參數到參數至持久層整個過程都是通過Map來傳遞數據。

而在開發的過程中,減少了實體類的存在,有時是感覺挺方便的,比如:一個系統中有100多個表,這裡我們可以減少工作量(雖然對應表的實體可以代碼生成),因為我們開發過程中是需要返回多個表關聯後的結果的,這裡可能我們需要創建DTO,這些步驟確實是挺煩人的。但是,前端過來的參數我們需不需要驗證呢?客戶的輸入不管有意或者是無意,我認為都應該讓系統的容錯能力更強悍一些。所以,在驗證前端過來的參數時,使用了Map就著實讓人頭痛。每個需要強制驗證的參數都需要get,然後判斷類型,強制轉型,判斷參數符不符合期望值邊界等。

所以,我就考慮了,實體類可以通過Spring MVCHibernateValidation使用註解的方式進行參數校驗,那麼,少了實體類,我是不是可以通過配置XML的方式來達到類似有實體類的效果。網上找了類似關鍵詞的工具類,發現沒有我所期望的,所以就動手來了一個。

大致的想法

Web開發時,有許多if-else語句的出現都是在為了驗證前端參數合不合法真的是挺無奈的,而且有些代碼雖然長起來類似但是呢要去重構成一個公用的方法好像有些困難,時常問自己,要怎麼去搞,Java不是JavaScript,語句沒那麼靈活。

於是想著通過XML配置試試,大致就是通過配置好的XML代替我們的實體類,並且有個入口將XML中的實體映射,並傳入待驗證的Map,驗證之後傳出一個數組,如果驗證通過數組為空,不通過則是我們XML中配置的對應錯誤語句。

如何設計XML格式

動手在這之前,需要想好我們大致的XML結構是怎麼樣的。這裡,我的想法是,在我們一般遇到的參數主要就是IntegerStringDoubleDateList了(這裡居然沒有考慮Boolean,算了,之後再做補充也行)。所以基於以上,設計的結構大致如下:

<?xml version="1.0" encoding="UTF-8"?>
<map-verify
    xmlns="https://www.lger.cn/verify"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.lger.cn/verify map-verify-util.xsd"
    >
    <!--
    在長度的方面分別使用了
    lt; lte; gt; gte; eq;分別代表<; <=; >; >=; =
    -->
    <Entity name="User01">
        <!--castErrMsg為當驗證過程中類型解析錯誤時返回提示-->
        <String name="username" castErrMsg="username必須為字元串類型">
            <length>
                <lt errMsg="當前值不能小於2">2</lt>
            </length>
            <notNull errMsg="當前值不能為空"/>
            <notBlank errMsg="當前值不能為空(去掉首尾空格)"/>
            <pattern>
                <!--如果不匹配則發出錯誤-->
                <value errMsg="號碼D[0-8]{4}">[0-8]{4}</value>
                <value errMsg="號碼D[0-7]{4}">[0-7]{4}</value>
            </pattern>
        </String>
        <Integer name="age" castErrMsg="必須為整型">
            <lt errMsg="歲數必須大於2">2</lt>
            <gt>150</gt>
            <notNull/>
        </Integer>
        <List name="details">
            <notEmpty/>
            <size>
                <gte>0</gte>
            </size>
            <!-- 遍歷List內容,遍歷的Entity映射為name為User02的實體 -->
            <forEach entity="User02" />
        </List>
        <Date name="birthday">
            <lt>1900-07-21</lt>
            <notNull/>
        </Date>
        <Double name="money">
            <lt>1</lt>
            <gt>150</gt>
            <notNull/>
        </Double>
    </Entity>

    <Entity name="User02">
        <String name="username">
            <length>
                <lt errMsg="當前值不能小於2">2</lt>
                <gt errMsg="當前值不能大於10">10</gt>
            </length>
            <notBlank errMsg="當前值不能為空(去掉空格)"/>
        </String>
    </Entity>
</map-verify>

需要寫出一個具有一定格式的XML那麼還需要寫出一個約束文件了驗證是否XML格式寫的正確。這裡還小學了一下XSD,這裡就不貼出代碼了。

以上的XML大致的說了下怎麼使用其替代實體類。寫到了這裡其實有時還覺得使用實體類可能更方便,何必使用Map呢?但是,別人框架已經寫了,參數只能接收到Map那我只能屈服了。

類結構設計

首先,每一個Entity就是一個實體對象,這裡我認為每個Entity都應該包含有一個驗證方法和一個初始化方法,因為在進行XML解析時就調用init,在進行Map驗證時就調用verify方法這樣,那些String節點也是類似的,解析初始化時就把XML中配置的信息保存起來,等到驗證時就通過之前保存的信息進行判斷即可,不必重新解析了。

這裡以解析一個IntegerEntity為例,首先是其父類,不論是XML中哪個節點,都是實現VerifyEntity介面,代碼如下:

public interface VerifyEntity {

    /**
     * 一個實體初始化,當實體被創建時將由創建方主動調用
     * @param currentEle 被創建的實體節點
     * @param factory 實體創建工廠,可以通過此工廠創建Entity
     */
    void init(Element currentEle, EntityFactory factory);

    /**
     * 驗證當前節點是否匹配XML的配置
     * @param value 需要驗證的值
     * @return 異常字元串集
     */
    String[] verify(Object value);

    /**
     * 初始化完畢後調用,傳入包含所有Entity的Map
     * @param entityMap entityMap
     */
    void finished(Map<String, VerifyEntity> entityMap);


}

首先解析XML時當獲取到Entity節點下的子節點將會通過一個工廠類創建子節點的對應實現VerifyEntity,之後調用init方法對當前的子節點進行解析。這裡先看下IntegerEntity的源碼:

public class IntegerEntity implements VerifyEntity {

    private AbstractEquation<Integer> integerEquation;

    private boolean notNull = false;
    private String notNullErrMsg;

    private String castErrMsg;

    @Override
    public void init(Element currentEle, EntityFactory factory) {
        // 開始解析當前節點<Integer/>
        String name = currentEle.attributeValue("name");
        // 獲取節點屬性castErrMsg,看是否存在castErrMsg
        this.castErrMsg = currentEle.attributeValue("castErrMsg");
        if (Util.isEmpty(this.castErrMsg)) {
            this.castErrMsg = name + ": this is not integer type.";
        } else {
            this.castErrMsg = name + ": " + this.castErrMsg;
        }
        // 獲取子節點notNull
        Element element = currentEle.element("notNull");
        if (element != null) {
            this.notNull = true;
            this.notNullErrMsg = element.attributeValue("errMsg");
            if (Util.isEmpty(this.notNullErrMsg)) {
                this.notNullErrMsg = name + ": this is not null";
            } else {
                this.notNullErrMsg = name + ": " + this.notNullErrMsg;
            }
        }
        // 這裡是新建一個抽象的Equation類,主要是因為lt和lte等等的這些節點在其他實體中也有,為了代碼復用所以使用了抽象類來定義
        //這裡實現後與上面解析notNull代碼差異不大
        integerEquation = new AbstractEquation<Integer>(currentEle, name) {
            @Override
            Integer valueOf(String value) {
                try {
                    return Integer.valueOf(value);
                }catch (NumberFormatException e) {
                    throw new ConvertException(castErrMsg);
                }
            }
            @Override
            boolean lessThan(Integer value, Integer lt) {
                return value < lt;
            }

            @Override
            boolean greaterThan(Integer value, Integer gt) {
                return value > gt;
            }

            @Override
            boolean lessThanOrEquals(Integer value, Integer lte) {
                return value <= lte;
            }

            @Override
            boolean greaterThanOrEquals(Integer value, Integer gte) {
                return value <= gte;
            }

            @Override
            boolean equals(Integer value, Integer eq) {
                return value.equals(eq);
            }
        };

    }
    
    @Override
    public String[] verify(Object value) {
        //正式驗證參數是否合法
        if (value == null) {
            //如果之前解析包含notNull,則這裡為true,那麼將返回解析的notNullErrMsg
            if (this.notNull) {
                return new String[]{this.notNullErrMsg};
            }
            return null;
        }
        try {
            //使用上面實現的抽象類進行驗證
            return integerEquation.verify(Integer.valueOf(value.toString()));
        }catch (NumberFormatException e) {
            return new String[]{this.castErrMsg};
        }
    }

    @Override
    public void finished(Map<String, VerifyEntity> entityMap) {
        //這裡是在Entity解析完畢後調用,並將保存Entity的Map傳入
    }

}

其實根據以上的代碼可以看出,就是XML的節點對應著一個VerifyEntity實現,每一個實現都保存著其中定義的<notNull/>等信息。

實現後運行效果

這裡我們的測試代碼如下,其中demo.xml設計XML所示的代碼:

        //初始化MapVerify,將定義好xml以數組方式傳入
        final String[] xmls = {"demo.xml"};
        MapVerify.init(xmls);

        final Map<String, Object> map = new HashMap<>(5);
        map.put("username", "12312371237123778");
        map.put("age", 1);
        map.put("birthday", "2000-07-21");
        map.put("money", 149);

        List<Map<String, Object>> list = new ArrayList<>(2);
        Map<String, Object> map1 = new HashMap<>(1);
        map1.put("username", "abcab");
        Map<String, Object> map2 = new HashMap<>(1);
        map2.put("username", "cc");
        list.add(map1);
        list.add(map2);
        map.put("details", list);
        System.out.println(Arrays.toString(MapVerify.verifyByReturnArr("User01", map)));

運行的結果如下:

[age: 歲數必須大於2]

總結

此想法是半年前的了,現在利用了空餘的時間實現了自己的想法,趁熱打鐵寫下博客,希望有這個需求的小伙伴能節省自己的時間(註:這裡Boolean的判斷沒有實現,如果有需要就需要自己動手了:-))。以後也希望自己如果有什麼小想法儘量的抽時間去做,不論做的好不好。

源碼已經上傳至 GitHub,包含demo


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

-Advertisement-
Play Games
更多相關文章
  • Lambda來源於希臘字母入,發音為 /'læmdə/對高數有所瞭解的人都知道λ用於聲明一個數學邏輯系統,表示根據XX的輸入參數,會返回某個Y結果。這正是編程語言中函數(方法)的意思。因此Lambda表達式在編程語言中說白了就是指的一個方法表達式。為什麼要搞出這麼一個東西來呢?這是由於我們的編程語言 ...
  • 1.表單提交方式 (1) 使用input控制項中的submit提交 代碼如下: 結果: 上圖是通過get方法使用input控制項中的submit的表單提交方式 (2)使用button提交表單 步驟: 通過ID獲取form標簽 設置action(要提交的頁面) 調用submit()方法提交form表單 代 ...
  • 上回說到我們如何如何把拉勾的數據抓取下來的,既然獲取了數據,就別放著不動,把它拿出來分析一下,看看這些數據裡面都包含了什麼信息。(本次博客源碼地址:https://github.com/MaxLyu/Lagou_Analyze) 一、前期準備 由於上次抓的數據裡面包含有 ID 這樣的信息,我們需要將 ...
  • 基類里的 public(大人) protect(青年) private(小孩) 在通過繼承時 繼承方式public(我是大人咯) protect(我是青少年) private(我系小孩紙啦)// "我"可以平視或俯視他們 如果看不到 例如:我是青少年protect 對面的是大人public 那對面的 ...
  • HBaseUtils工具類,實現了對hbase的put/get/scan/delete操作,直接操作對象即可 ...
  • Java是一門面向對象編程語言,不僅吸收了C++語言的各種優點,還摒棄了C++里難以理解的多繼承、指針等概念,因此Java語言具有功能強大和簡單易用兩個特征。Java語言作為靜態面向對象編程語言的代表,極好地實現了面向對象理論,允許程式員以優雅的思維方式進行複雜的編程 1、JVM、JRE和JDK的區 ...
  • #數組標記法在演算法題中的應用 什麼?!你還不知道數組在演算法題中不僅起儲存數據的作用,還可以起鏈接標記的作用?哈哈不要緊,原來我也是不知道的,我是看了我好哥們的做題思路才知道這個方法的。。。 我們先聲明一個長度為5數組arr[5],再為arr[5]賦值arr[]={"q","w","e","r",“t ...
  • Random類專門用於生成一個偽隨機數,它有兩個構造器:一個構造器使用預設的種子(以當前時間作為種子),另一個構造器需要程式員顯示傳入一個long型整數的種子。 Random類比Math類的random()方法提供了更多的方式來生成各種偽隨機數,可以生成浮點類型的偽隨機數,也可以生成整數類型的偽隨機 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...