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。

jar包请点击 此链接,使用jar包时需要多引入dom4j依赖包

Java工具类——通过配置XML验证Map的更多相关文章

  1. javaweb常用工具类及配置文件备份

    Javaweb常用工具类及配置文件备份   做一个代码备份,以后常用到的. hibernate工具类备份 package com.dly.service; /*  * hibernate获取sessi ...

  2. 排名前 16 的 Java 工具类

    在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取的5万个开源项目源码. 一. ...

  3. 排名前16的Java工具类

    原文:https://www.jianshu.com/p/9e937d178203 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法 ...

  4. 干货:排名前16的Java工具类

    在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取的5万个开源项目源码. 一. ...

  5. 常用高效 Java 工具类总结

    一.前言 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取的5万个开源项目源码 ...

  6. 几种高效的Java工具类推荐

    本文将介绍了十二种常用的.高效的Java工具类 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类. 在开发中,使用这些工具类,不仅可以提高编码效率,还 ...

  7. 16 个超级实用的 Java 工具类

    阅读本文大概需要 4 分钟. 出处:alterem juejin.im/post/5d4a25b351882505c105cc6e 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用 ...

  8. 超级实用的 Java 工具类

    超级实用的 Java 工具类 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取 ...

  9. 常用的Java工具类——十六种

    常用的Java工具类——十六种 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选 ...

随机推荐

  1. safari 浏览器 input textarea select 等不能响应用户输入

    解决办法 -webkit-user-select:auto; /*webkit浏览器*/ user-select:auto; -o-user-select:auto; -ms-user-select: ...

  2. 【ODI】| 数据ETL:从零开始使用Oracle ODI完成数据集成(二)

    前一节已经完成了Oracle数据库和ODI的安装,并已经为ODI在Oracle数据库中创建了两个用户,分别用于存放主资料库数据和工作资料库数据,在ODI中完成主资料库和工作资料库的创建,也分别为其创建 ...

  3. asp.net core系列 56 IS4使用OpenID Connect添加用户认证

    一.概述 在前二篇中讲到了客户端授权的二种方式: GrantTypes.ClientCredentials凭据授权和GrantTypes.ResourceOwnerPassword密码授权,都是OAu ...

  4. c#批量抓取免费代理并验证有效性

    之前看到某公司的官网的文章的浏览量刷新一次网页就会增加一次,给人的感觉不太好,一个公司的官网给人如此直白的漏洞,我批量发起请求的时候发现页面打开都报错,100多人的公司的官网文章刷新一次你给我看这个, ...

  5. Asp.Net Core对接钉钉群机器人

    钉钉作为企业办公越来越常用的软件,对于企业内部自研系统提供接口支持,以此来打通多平台下的数据,本次先使用最简单的钉钉群机器人完成多种形式的消息推送,参考钉钉开发文档中自定义机器人环节,此次尝试所花的时 ...

  6. Thymeleaf【快速入门】

    前言:突然发现自己给自己埋了一个大坑,毕设好难..每一个小点拎出来都能当一个小题目(手动摆手..),没办法自己选的含着泪也要把坑填完..先一点一点把需要补充的知识学完吧.. Thymeleaf介绍 稍 ...

  7. 安装Mysql时端口号3306被占用,解决方法

    当我们在卸载mysql数据库重新安装的时候,会出现端口号3306被占用的情况 有两种解决方案: 一:可以不使用3306端口,也可以换成别的端口,如3307,3308等等 二:可以打开命令窗口 1.wi ...

  8. JDK和Tomcat安装和配置过程

    Jdk: 第一步:在下载JDK 第二步:安装 更改安装路径 *JDK配置: JAVA_HOME 环境变量  D:\jdk1.7.0 CLASSPATH 环境变量   .,%JAVA_HOME%\lib ...

  9. DFS(深度优先搜索)

    简介 DFS的过程是一个递归过程,它是从图中的某个顶点开始,首先访问起始点v,然后选择一个与顶点v相邻的且没有被访问的顶点w,以w为起始顶点,在进行DFS,直到图中所有与v相邻的顶点都被访问过为止. ...

  10. 访问者模式 Visitor 行为型 设计模式(二十七)

    访问者模式 Visitor    <侠客行>是当代作家金庸创作的长篇武侠小说,新版电视剧<侠客行>中,开篇有一段独白:  “茫茫海外,传说有座侠客岛,岛上赏善罚恶二使,每隔十年 ...