java接口如何接受语音参数_Java 是如何优雅地实现接口数据校验的?

baca0a066278213504073cd169152401.gif

ea5fbd7dff8031cb106626595fa1d2c5.png

作者 | 无敌码农  责编 | 张文头图 | CSDN 下载自东方 IC来源 | 无敌码农(ID:jiangqiaodege)本篇文章给大家分享平时开发中总结的一点小技巧!在工作中写过 Java 程序的朋友都知道,目前使用 Java 开发服务最主流的方式就是通过 Spring MVC 定义一个 Controller 层接口,并将接口请求或返回参数分别定义在一个 Java 实体类中,这样 Spring MVC 在接收到 Http 请求(POST/GET) 后,就会自动将请求报文自动映射成一个 Java 对象。这样的代码通常是这样写的:
@RestControllerpublic class OrderController {    @Autowired    private OrderService orderServiceImpl;    @PostMapping("/createOrder")    public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) {        return orderServiceImpl.createOrder(createOrderDTO);    }}
这样的代码相信大家并不陌生,但在后续的逻辑实现过程中却会遇到这样的问题:“在接收请求参数后如何实现报文对象数据值的合法性校验?”。一些同学也可能认为这并不是什么问题,因为具体某个参数字段是否为空、值的取值是否在约定范围、格式是否合法等等,在业务代码中校验就好了。例如可以在 Service实现类中对报文格式进行各种 if-else 的数据校验。从功能上说冗余的 if-else 代码没啥毛病,但从代码的优雅性来说冗长的 if-else 代码会显得非常臃肿。接下来的内容将给大家介绍一种处理此类问题的实用方法。具体将从以下几个方面进行介绍:
  • 使用@Validated 注解实现 Controller 接口层数据直接绑定校验;

  • 扩展约束性注解实现数据取值范围的校验;

  • 更加灵活的对象数据合法性校验工具类封装;

  • 数据合法性校验结果异常统一返回处理;

05b69087e43b9f8f08cb13e8fac76709.png

Controller 接口层数据绑定校验实际上在 Java 开发中目前普通使用的 Bean 数据校验工具是"hibernate-validator",它是一个 hibernete 独立的 jar 包,所以使用这个 jar 包并不需要一定要集成 Hibernete 框架。该jar包主要实现并扩展了 javax.validation (是一个基于 JSR-303 标准开发出来的 Bean 校验规范)接口。由于 Spring Boot 在内部默认集成了"hibernate-validator",所以使用Spring Boot 构建的 Java 工程可以直接使用相关注解来实现 Bean 的数据校验。例如我们最常编写的 Controller 层接口参数对象,可以在定义 Bean 类时直接编写这样的代码:
@Datapublic class CreateOrderDTO {    @NotNull(message = "订单号不能为空")    private String orderId;    @NotNull(message = "订单金额不能为空")    @Min(value = 1, message = "订单金额不能小于0")    private Integer amount;    @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "用户手机号不合法")    private String mobileNo;    private String orderType;    private String status;}
如上所示代码,我们可以使用@NotNull 注解来约束该字段必须不能为空,也可以使用@Min 注解来约束字段的最小取值,或者还可以通过@Pattern 注解来使用正则表达式来约束字段的格式(如手机号格式)等等。以上这些注解都是“hibernate-validator”依赖包默认提供的,更多常用的注解还有很多,例如:d06153175504efbb0dd76b25c484cf1a.png利用这些约束注解,我们就可以很轻松的搞定接口数据校验,而不需要在业务逻辑中编写大量的 if-else 来进行数据合法性校验。而定义好 Bean 参数对象并使用相关注解实现参数值约束后,在 Controller 层接口定义中只需要使用@Validated 注解就可以实现在接收参数后自动进行数据绑定校验了,具体代码如下:
@PostMapping("/createOrder")public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) {    return orderServiceImpl.createOrder(createOrderDTO);}
如上所示,在 Controller 层中通过 Spring 提供的@Validated 注解可以自动实现数据 Bean 的绑定校验,如果数据异常则会统一抛出校验异常!

b4335f90615c7bc0c9ffe546fd21ac60.png

约束性注解扩展在“hibernate-validator”依赖 jar 包中,虽然提供了很多很方便的约束注解,但是也有不满足某些实际需要的情况,例如我们想针对参数中的某个值约定其值的枚举范围,如 orderType 订单类型只允许传“pay”、“refund”两种值,那么现有的约束注解可能就没有特别适用的了。此外,如果对这样的枚举值,我们还想在约束定义中直接匹配代码中的枚举定义,以更好地统一接口参数与业务逻辑的枚举定义。那么这种情况下,我们还可以自己扩展定义相应地约束注解逻辑。接下来我们定义新的约束注解@EnumValue,来实现上面我们所说的效果,具体代码如下:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})@Retention(RUNTIME)@Documented@Constraint(validatedBy = {EnumValueValidator.class})public @interface EnumValue {    //默认错误消息    String message() default "必须为指定值";    //支持string数组验证    String[] strValues() default {};    //支持int数组验证    int[] intValues() default {};    //支持枚举列表验证    Class>[] enumValues() default {};    //分组    Class>[] groups() default {};    //负载    Class extends Payload>[] payload() default {};    //指定多个时使用    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})    @Retention(RUNTIME)    @Documented    @interface List {        EnumValue[] value();    }    /**     * 校验类逻辑定义     */    class EnumValueValidator implements ConstraintValidatorObject> {        //字符串类型数组        private String[] strValues;        //int类型数组        private int[] intValues;        //枚举类        private Class>[] enumValues;        /**         * 初始化方法         *         * @param constraintAnnotation         */        @Override        public void initialize(EnumValue constraintAnnotation) {            strValues = constraintAnnotation.strValues();            intValues = constraintAnnotation.intValues();            enumValues = constraintAnnotation.enumValues();        }        /**         * 校验方法         *         * @param value         * @param context         * @return         */        @SneakyThrows        @Override        public boolean isValid(Object value, ConstraintValidatorContext context) {            //针对字符串数组的校验匹配            if (strValues != null && strValues.length > 0) {                if (value instanceof String) {                    for (String s : strValues) {//判断值类型是否为Integer类型                        if (s.equals(value)) {                            return true;                        }                    }                }            }            //针对整型数组的校验匹配            if (intValues != null && intValues.length > 0) {                if (value instanceof Integer) {//判断值类型是否为Integer类型                    for (Integer s : intValues) {                        if (s == value) {                            return true;                        }                    }                }            }            //针对枚举类型的校验匹配            if (enumValues != null && enumValues.length > 0) {                for (Class> cl : enumValues) {                    if (cl.isEnum()) {                        //枚举类验证                        Object[] objs = cl.getEnumConstants();                        //这里需要注意,定义枚举时,枚举值名称统一用value表示                        Method method = cl.getMethod("getValue");                        for (Object obj : objs) {                            Object code = method.invoke(obj, null);                            if (value.equals(code.toString())) {                                return true;                            }                        }                    }                }            }            return false;        }    }}
如上所示的@EnumValue 约束注解,是一个非常实用的扩展,通过该注解我们可以实现对参数取值范围(不是大小范围)的约束,它支持对 int、string 以及 enum 三种数据类型的约束,具体使用方式如下:
/** * 定制化注解,支持参数值与指定类型数组列表值进行匹配(缺点是需要将枚举值写死在字段定义的注解中) */@EnumValue(strValues = {"pay", "refund"}, message = "订单类型错误")private String orderType;/** * 定制化注解,实现参数值与枚举列表的自动匹配校验(能更好地与实际业务开发匹配) */@EnumValue(enumValues = Status.class, message = "状态值不在指定范围")private String status;
如上所示代码,该扩展注解既可以使用strValues 或intValues 属性来编程列举取值范围,也可以直接通过enumValues 来绑定枚举定义。但是需要注意,处于通用考虑,具体枚举定义的属性的名称要统一匹配为 value、desc,例如  Status 枚举定义如下:
public enum Status {    PROCESSING(1, "处理中"),    SUCCESS(2, "订单已完成");    Integer value;    String desc;    Status(Integer value, String desc) {        this.value = value;        this.desc = desc;    }    public Integer getValue() {        return value;    }    public String getDesc() {        return desc;    }}
通过注解扩展,就能实现更多方便的约束性注解!

f7a55663b02167d601b1148bcfd7988e.png

更加灵活的数据校验工具类封装除了上面直接在 Controller 层使用@Validated 进行绑定数据校验外,在有些情况,例如你的参数对象中的某个字段是一个复合对象,或者业务层的某个方法所定义的入参对象也需要进行数据合法性校验,那么这种情况下如何实现像  Controller 层一样的校验效果呢?需要说明在这种情况下@Validated 已经无法直接使用了,因为@Validated 注解发挥作用主要是 Spring MVC 在接收参数的过程中实现了自动数据绑定校验,而在普通的业务方法或者复合参数对象中是没有办法直接绑定校验的。这种情况下,我们可以通过定义 ValidateUtils 工具类来实现一样的校验效果,具体代码如下:
public class ValidatorUtils {    private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();    /**     * bean整体校验,有不合规范,抛出第1个违规异常     */    public static void validate(Object obj, Class>... groups) {        Set> resultSet = validator.validate(obj, groups);        if (resultSet.size() > 0) {            //如果存在错误结果,则将其解析并进行拼凑后异常抛出            List errorMessageList = resultSet.stream().map(o -> o.getMessage()).collect(Collectors.toList());            StringBuilder errorMessage = new StringBuilder();            errorMessageList.stream().forEach(o -> errorMessage.append(o + ";"));            throw new IllegalArgumentException(errorMessage.toString());        }    }}
如上所示,我们定义了一个基于"javax.validation"接口的工具类实现,这样就可以在非@Validated 直接绑定校验的场景中通过校验工具类来实现对 Bean 对象约束注解的校验处理,具体使用代码如下:
public boolean orderCheck(OrderCheckBO orderCheckBO) {    //对参数对象进行数据校验    ValidatorUtils.validate(orderCheckBO);    return true;}
而方法入参对象则还是可以继续使用前面我们介绍的约束性注解进行约定,例如上述方法的入参对象定义如下:
@Data@Builderpublic class OrderCheckBO {    @NotNull(message = "订单号不能为空")    private String orderId;    @Min(value = 1, message = "订单金额不能小于0")    private Integer orderAmount;    @NotNull(message = "创建人不能为空")    private String operator;    @NotNull(message = "操作时间不能为空")    private String operatorTime;}
这样在编程体验上就可以整体上保持一致!

e6512017908f29d4899f8c82d94725d0.png

数据合法性校验结果异常统一处理通过前面我们所讲的各种约束注解,我们实现了对 Controller 层接口以及业务方法参数对象的统一数据校验。而为了保持校验异常处理的统一处理和错误报文统一输出,我们还可以定义通用的异常处理机制,来保证各类数据校验错误都能以统一错误格式反馈给调用方。具体代码如下:
@Slf4j@ControllerAdvicepublic class GlobalExceptionHandler {    /**     * 统一处理参数校验错误异常(非Spring接口数据绑定验证)     *     * @param response     * @param e     * @return     */    @ExceptionHandler(BindException.class)    @ResponseBody    public ResponseResult> processValidException(HttpServletResponse response, BindException e) {        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());        //获取校验错误结果信息,并将信息组装        List errorStringList = e.getBindingResult().getAllErrors()                .stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());        String errorMessage = String.join("; ", errorStringList);        response.setContentType("application/json;charset=UTF-8");        log.error(e.toString() + "_" + e.getMessage(), e);        return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(),                errorMessage);    }    /**     * 统一处理参数校验错误异常     *     * @param response     * @param e     * @return     */    @ExceptionHandler(IllegalArgumentException.class)    @ResponseBody    public ResponseResult> processValidException(HttpServletResponse response, IllegalArgumentException e) {        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());        String errorMessage = String.join("; ", e.getMessage());        response.setContentType("application/json;charset=UTF-8");        log.error(e.toString() + "_" + e.getMessage(), e);        return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(),                errorMessage);    }    ...}
如上所示,我们定义了针对前面两种数据校验方式的统一异常处理机制,这样数据校验的错误信息就能通过统一的报文格式反馈给调用端,从而实现接口数据报文的统一返回!其中通用的接口参数对象 ResponseResult 的代码定义如下:
@Data@Builder@NoArgsConstructor@AllArgsConstructor@JsonPropertyOrder({"code", "message", "data"})public class ResponseResult<T> implements Serializable {    private static final long serialVersionUID = 1L;    /**     * 返回的对象     */    @JsonInclude(JsonInclude.Include.NON_NULL)    private T data;    /**     * 返回的编码     */    private Integer code;    /**     * 返回的信息     */    private String message;    /**     * @param data 返回的数据     * @param   返回的数据类型     * @return 响应结果     */    public static ResponseResultOK(T data) {        return packageObject(data, GlobalCodeEnum.GL_SUCC_0);    }    /**     * 自定义系统异常信息     *     * @param code     * @param message 自定义消息     * @param      * @return     */    public static ResponseResultsystemException(Integer code, String message) {        return packageObject(null, code, message);    }}
当然,这样的统一报文格式也不仅仅只处理异常返回,正常的数据报文格式也可以通过该对象来进行统一封装!本文内容从实用的角度给大家演示了,如何在日常工作中编写通用的数据校验逻辑,希望能对大家有所帮助。faeee2fb8ffc69e0108a4d46cd03c91d.png27828d2e1b61f197f56ba0eb52168ea4.png
更多精彩推荐

☞程序员有钱了都干什么?买豪宅,玩跑车,上太空!| 涛滔不绝

☞分库分表:TiDB,求别抢饭碗!

☞他被称为印度“ IT 大王”,富可敌国,却精打细算如守财奴

☞带你「周游世界」的 MODNet 算法

☞一文聊“图”,从图数据库到知识图谱

☞红帽"干掉" CentOS 8,CentOS Stream 上位

☞科技垄断正在朝着纵向发展
点分享点点赞点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/552702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

msp430单片机 温度计编程_MSP430系列与89C5l系列的比较

1、89C51单片机是8位单片机。其指令是采用的被称为“CISC”的复杂指令集&#xff0c;共具有111条指令。而MSP430单片机是16位的单片机&#xff0c;采用了精简指令集(RISC)结构&#xff0c;只有简洁的27条指令&#xff0c;大量的指令则是模拟指令&#xff0c;众多的寄存器以及片…

php 正则表达式提取出合法的时间_PHP正则表达式核心技术完全详解 第1节

PHP正则表达式核心技术 第1节正则表达式: 就是描述字符串排列模式的一种自定义语法规则、也是用于描述字符串排列 或 匹配模式的一种语法规则、正则表达式: 就是用于描述字符串排列 或 匹配模式的一种语法规则、它主要用于字符串的: 分割字符串、查找字符串、替换字符串 的一系…

anacoda2如何连接使用mysql_Anaconda 安装 Python 库(MySQLdb)的方法-(转)

安装python库的过程中&#xff0c;最重要的地方就是版本需要兼容。其中操作系统为64位&#xff0c;Python为2.X 64位&#xff0c;下载安装文件的时候也要注意版本匹配。其中文件名中包含的cp27表示CPython 2.7版本&#xff0c;cp34表示CPython 3.4&#xff0c;win_amd64指的是6…

php mysql随机记录_php随机取mysql记录方法小结

这篇文章主要介绍了php随机取mysql记录方法,实例分析了几种常见的随机获取mysql数据的方法,是非常实用的技巧,具有一定的参考借鉴价值,需要的朋友可以参考下本文实例总结了php随机取mysql记录方法。分享给大家供大家参考。具体分析如下&#xff1a;在php中要随机取mysql记录我们…

中西方对时间的差异_中西文化的差异-2 时间观

中西文化的差异-2 时间观时间和空间存在不可思议的相似性。因此&#xff0c;人们对时间的不同理解可分为两种时空观念———直线型和循环型。大体而言&#xff0c;英语国家的人认为时间是直线型&#xff0c;客观顺序是把事件分为过去&#xff0c;现在&#xff0c;将来三种时态(…

java开发环境怎么写_Java开发基础设置:如何配置Java运行环境

很多朋友都有自学Java的愿望&#xff0c;但是自学一门语言何其艰难&#xff0c;没人指引的话往往不得其门而入。那么&#xff0c;今天千锋老师就给大家带来Java入门课程中配置Java运行环境的方法&#xff0c;希望能够对大家有所帮助。一、JDK安装 什么是JVMJava Virtual Machin…

被动声呐 相移波束形成_100天计划-DAY9-拖曳声呐

拖曳线列阵声纳是拖曳在距舰船尾部一定距离的声接收系统&#xff08;通常称为线列阵&#xff09;&#xff0c;通过接收航行目标自身辐射的噪声或者通过接收目标反射回来的信号&#xff0c;来检测目标的有无并估计目标有关参数。拖曳线列阵声纳可以分为主动、被动和主被动联合三…

mysql唯一索引弊端_MySQL 关于唯一索引和普通索引的抉择

想象这样一个场景&#xff0c;在设计一张用户表时&#xff0c;每人的身份证号是唯一的&#xff0c;需要搜索。但由于身份证号字段较大&#xff0c;不好将其作为主键。在业务代码已经保证插入身份证唯一的情况下&#xff0c;可以选择建立唯一索引和普通普通索引&#xff0c;这时…

java 反射 父类的属性_用反射的方式获取父类中的所有属性和方法

package com.syh.jdbc.reflection_super;/*** 父类* author syh**/public class Parent {public String publicField "1";String defaultField "2";protected String protectedField "3";private String privateField "4" ;public…

java中变量命名规范_关于java中变量命名规范的详细介绍

Java是一种区分字母的大小写的语言&#xff0c;所以我们在定义变量名的时候应该注意区分大小写的使用和一些规范&#xff0c;接下来我们简单的来讲讲Java语言中包、类、变量等的命名规范。(一)Package(包)的命名Package的名字应该都是由一个小写单词组成&#xff0c;例如com、x…

java找重复字符串_在java中怎样查找重复字符串

在一段java编程代码中&#xff0c;字符串是不可缺少的一个要素&#xff0c;属于java中的基础知识&#xff0c;字符串不仅在java面试题中会出现&#xff0c;在编写代码时更要掌握怎样使用字符串。在前面我们也学习过关于字符串截取的知识&#xff0c;你应该有所掌握吧、格式化字…

python excel 单元格格式_python设置单元格数值格式

python xlwt如何设置单元格格式python xlwt模块怎么设置excel单元格的属性如图,默认是general。我想写入的时候就是Text类型.请问应该怎么做。from xlwt import Workbook,Stylewb Workbook()ws wb.add_sheet(Type examples)ws.row(0).write(0,1)ws.row(0).set_cell_text(1,1)…

python调用.a静态库_Python 调用 C

了解了相关资料不折腾的方法有(以往文章有)&#xff1a;pypy&#xff0c;numba&#xff0c;numpy但都不是 纯正的 C折腾的&#xff1a;cffi&#xff0c;Cython&#xff0c;Boost.Python&#xff0c;Cpython 自带模块&#xff0c;SWIG 等挺折腾的You can write an extension you…

java给一个数组随机数_Java案例-数组随机数

.数组案例分析定义一个int型的一维数组&#xff0c;包含10个元素&#xff0c;分别赋一些随机整数&#xff0c;然后求出所有元素的最大值Max&#xff0c;最小值Min&#xff0c;平均值Avg&#xff0c;和Sum值&#xff0c;并输出出来。具体实现代码&#xff1a;package teacher01;…

java 自旋方法_JAVA循环使用CAS实现自旋操作

大家碰到了实现一个线程安全的计数器的需求改怎么做呢&#xff1f;根据经验你应该知道我们要在多线程中实现共享变量的原子性和可见性问题&#xff0c;于是锁成为一个不可避免的话题&#xff0c;下文讨论的是与之对应的无锁CAS。为什么要无锁我们一想到在多线程下保证安全的方式…

java实现extended smtp_java实现发送邮件(SMTP)

1.pom引入包javax.mailmail1.4.72.Email实体类import lombok.Data;import lombok.EqualsAndHashCode;import lombok.experimental.Accessors;import java.util.List;import java.util.Map;/*** ClassName: Email* author: mxy* Description: Email的实体类*/DataEqualsAndHashC…

java addcallback函数_java中怎么使用callback函数?

UYOU在很多场景&#xff0c;作为开发都会想到&#xff0c;在执行完毕一个任务的时候&#xff0c;能执行一个callback函数是多么好的事情。现在模拟一下这个情景&#xff1a;定义三个类。分别是主函数类。callback函数的接口类。业务处理类。在业务处理类中&#xff0c;处理完业…

在java中的交换方法有哪些_java中交换两个变量的值有哪几种方法,交换两个变量a和b的值...

java中交换两个变量的值有哪几种方法在Java中&#xff0c;有哪些方法可以交换两个变量的值&#xff0c;方法&#xff1a;1.定义临时变量2.没有必要定义临时变量3.使用位运算符(学习视频分享&#xff1a;java课程)代码示例&#xff1a;公共类SwapTest {公共静态void main(String…

opc客户端读取数据品质是bad_听说看了这篇文章就彻底搞懂了什么是OPC(上)

从2000年初以来&#xff0c;我们就一直在使用OPC软件互操作性标准&#xff0c;而那些正准备踏入和想要踏入工业自动化领域的人们却对这些含义感到困惑。所以在本中&#xff0c;我将系统地为你梳理OPC知识。OPC首字母缩写词代表什么&#xff1f;问一个OPC老手&#xff0c;他们或…

php导入json文件_[php]导入超大json文件

前言在之前的文章《做一个twitter的插件玩玩》中&#xff0c;我做了一个批量删除推文(转发推文)的工具&#xff0c;该工具能够删除前3200条推文&#xff0c;但因为总数太多(4.3万条)&#xff0c;api接口不支持获取所有的推文&#xff0c;所以我采用了下载的方式&#xff0c;直接…