Java工具類——通過配置XML驗證Map 背景 在JavaWeb項目中,接收前端過來的參數時通常是使用我們的實體類進行接收的。但是呢,我們不能去決定已經搭建好的框架是怎麼樣的,在我接觸的框架中有一種就是通過Map來接收前端過來的所有參數,框架中沒有實體類的說法,從接收參數,驗證參數到參數至持久層整 ...
Java工具類——通過配置XML驗證Map
背景
在JavaWeb項目中,接收前端過來的參數時通常是使用我們的實體類進行接收的。但是呢,我們不能去決定已經搭建好的框架是怎麼樣的,在我接觸的框架中有一種就是通過Map來接收前端過來的所有參數,框架中沒有實體類的說法,從接收參數,驗證參數到參數至持久層整個過程都是通過Map來傳遞數據。
而在開發的過程中,減少了實體類的存在,有時是感覺挺方便的,比如:一個系統中有100多個表,這裡我們可以減少工作量(雖然對應表的實體可以代碼生成),因為我們開發過程中是需要返回多個表關聯後的結果的,這裡可能我們需要創建DTO
,這些步驟確實是挺煩人的。但是,前端過來的參數我們需不需要驗證呢?客戶的輸入不管有意或者是無意,我認為都應該讓系統的容錯能力更強悍一些。所以,在驗證前端過來的參數時,使用了Map
就著實讓人頭痛。每個需要強制驗證的參數都需要get
,然後判斷類型,強制轉型,判斷參數符不符合期望值邊界等。
所以,我就考慮了,實體類可以通過Spring MVC
中Hibernate
的Validation
使用註解的方式進行參數校驗,那麼,少了實體類,我是不是可以通過配置XML的方式來達到類似有實體類的效果。網上找了類似關鍵詞的工具類,發現沒有我所期望的,所以就動手來了一個。
大致的想法
在Web
開發時,有許多if-else
語句的出現都是在為了驗證前端參數合不合法真的是挺無奈的,而且有些代碼雖然長起來類似但是呢要去重構成一個公用的方法好像有些困難,時常問自己,要怎麼去搞,Java
不是JavaScript
,語句沒那麼靈活。
於是想著通過XML
配置試試,大致就是通過配置好的XML
代替我們的實體類,並且有個入口將XML
中的實體映射,並傳入待驗證的Map
,驗證之後傳出一個數組,如果驗證通過數組為空,不通過則是我們XML
中配置的對應錯誤語句。
如何設計XML格式
動手在這之前,需要想好我們大致的XML
結構是怎麼樣的。這裡,我的想法是,在我們一般遇到的參數主要就是Integer
,String
,Double
,Date
,List
了(這裡居然沒有考慮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