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. [dotnet core]落地微服务特色的DevOps管道,持续集成/部署到kubernetes。

    目录 前言 目标 工具 - 最小的学习成本 方案 - 愿景 1. 持续集成 - CI 2. 持续部署 - CD 部署环境 1. 部署gitlab-runner 2. 注册gitlab-runner 搭 ...

  2. 【JVM虚拟机】(6)---深入理解Class中访问标志、类索引、父类索引、接口索引

    JVM(6)访问标志,类索引 上一篇博客讲[JVM虚拟机](5)---深入理解JVM-Class中常量池 我们知道一个class文件正常可以分为7个部分: 魔数与class文件版本 常量池 访问标志 ...

  3. Jenkins 集成 SonarQube Scanner

    1.   安装Jenkins 下载安装包,这里我们下载war包 https://jenkins.io/download/ 运行jenkins.war的方式有两种: 第一种:将其放到tomcat中运行( ...

  4. Solr 17 - Solr的时间为什么比本地少8小时 (附修改方法)

    目录 1 为什么少8小时 2 如何查看Solr的时区 3 修改Solr的时区 3.1 Solr从数据库中同步数据的原理 3.2 为什么要修改时区 3.3 如何修改时区 1 为什么少8小时 (1) 原因 ...

  5. React 虚拟 DOM 的差异检测机制

    React 使用虚拟 DOM 将计算好之后的更新发送到真实的 DOM 树上,减少了频繁操作真实 DOM 的时间消耗,但将成本转移到了 JavaScript 中,因为要计算新旧 DOM 树的差异嘛.所以 ...

  6. 【推荐】.NETCore 简单且高级的库 csredis v3.0.0

    前言 .NETCore 从1.0发布历经坎坷,一开始各种库缺失到现在的部分完善,走到今天实属不易. 比如 redis-cli SDK 简直是坑出不穷. 过去 .net 最有名望的 ServiceSta ...

  7. 使用docker-compose 一键部署你的分布式调用链跟踪框架skywalking

    一旦你的程序docker化之后,你会遇到各种问题,比如原来采用的本地记日志的方式就不再方便了,虽然你可以挂载到宿主机,但你使用 --scale 的话,会导致 记录日志异常,所以最好的方式还是要做日志中 ...

  8. 设计模式 | 观察者模式/发布-订阅模式(observer/publish-subscribe)

    定义: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己. 结构:(书中图,侵删) 一个抽象的观察者接口, ...

  9. 学习笔记—HTML基础标签

    HTML的概念 概念: HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言 (Hyper Text Markup Language) HTML 不是一种编程语言,而是一种标记语言 ( ...

  10. C:\Program Files\Java\jdk1.7.0_79\bin\java.exe'' finished with non-zero exit value 1

    转载请标明出处:https://www.cnblogs.com/tangZH/p/10538982.html 今天,在项目过程中碰到了这个奇怪的问题,C:\Program Files\Java\jdk ...