Springboot使用JSR303完成Controller或服务接口参数校验

本文主要是介绍Springboot使用JSR303完成Controller或服务接口参数校验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。定义了很多常用的校验注解可以直接将这些注解加在我们JavaBean的属性上面就可以在需要校验的时候进行校验。例如:表单提交后台接口时或在各接口调用时进行校验。

Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint,在本章末尾附录注解清单。

一、编写Controller接口参数校验代码

  • springboot工程导入JSR303的依赖
<!--使用jsr303数据校验--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
  • 定义API参数DTO
package com.imk.cases.springboot.jsr303.controller.dto;import com.imk.cases.springboot.jsr303.controller.validator.AddGroup;
import com.imk.cases.springboot.jsr303.controller.validator.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.*;
import java.util.Date;/**- 描述-  3. @author darrn.xiang- @date 2022/9/12 10:15*/
@Data
public class UserDTO {@NotNull(groups = UpdateGroup.class)private String id;@NotEmpty(groups = {AddGroup.class,UpdateGroup.class})@Length(min = 2,max = 30,groups = {AddGroup.class,UpdateGroup.class})private String name;@Min(value = 1,message = "年龄必须大于等于1")@Max(value = 120,message = "年龄必须小于等于120")private Integer age;@Length(max = 50)private String address;@Pastprivate Date birthday;
}
  • 编写controller接口
package com.imk.cases.springboot.jsr303.controller;import com.imk.cases.springboot.core.api.ApiResult;
import com.imk.cases.springboot.jsr303.controller.dto.UserDTO;
import com.imk.cases.springboot.jsr303.controller.validator.AddGroup;
import com.imk.cases.springboot.jsr303.controller.validator.UpdateGroup;
import com.imk.cases.springboot.jsr303.controller.validator.ValidList;
import lombok.RequiredArgsConstructor;
import org.hibernate.validator.constraints.Length;
import org.springframework.lang.NonNull;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import javax.validation.constraints.NotEmpty;/*** 描述** @author darrn.xiang* @date 2022/9/12 10:14*/
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users")
public class UserController {private @NonNull UserService userService;/*** 创建 校验测试** @param dto* @return*/@PostMappingpublic ApiResult createUser(@RequestBody @Validated(AddGroup.class) UserDTO dto){return ApiResult.success();}/*** 更新 校验测试** @param dto* @return*/@PutMappingpublic ApiResult updateUser(@RequestBody @Validated(UpdateGroup.class) UserDTO dto){return ApiResult.success();}/*** 批量创建校验测试,需要借助 ValidList ,否则不支持** @param list* @return*/@PostMapping("/batch")public ApiResult createUserList(@RequestBody @Validated(AddGroup.class) ValidList<UserDTO> list){return ApiResult.success();}@GetMappingpublic ApiResult findList(){int user = userService.createUser(new UserDTO());return ApiResult.success("findList");}@GetMapping("/{id}")public ApiResult findById(@NotEmpty(message = "id不能为空") @PathVariable("id") String id){return ApiResult.success("findById");}@GetMapping("/param")public ApiResult findByParam(@Length(min = 2) @RequestParam String name){return ApiResult.success("findByParam");}
}

二、参数校验-参数分组

在开发中,我们通常会将一个实体的DTO共用,如场景的场景创建和更新。但是有的时候不同逻辑校验的参数不一致,所以我们进行分组处理。

  • 自定义分组接口
public interface AddGroup {
}public interface UpdateGroup {
}
  • 在校验DTO中标识分组
// 插入时候id自动生成,更新的时候必须传值
@NotNull(groups = UpdateGroup.class)
private String id;// 不论是创建还是更新逻辑 name 字段的值都不能为空
@NotEmpty(groups = {AddGroup.class,UpdateGroup.class})
@Length(min = 2,max = 30,groups = {AddGroup.class,UpdateGroup.class})
private String name;
  • 在接口校验参数中指定分组
/*** 创建 校验测试** @param dto* @return*/
@PostMapping
public ApiResult createUser(@RequestBody @Validated(AddGroup.class) UserDTO dto){return ApiResult.success();
}/*** 更新 校验测试** @param dto* @return*/
@PutMapping
public ApiResult updateUser(@RequestBody @Validated(UpdateGroup.class) UserDTO dto){return ApiResult.success();
}

三、URL参数校验

url参数校验需要在类上添加@Validated注解、在接口参数添加检查注解,如:

@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users")
public class UserController {@GetMapping("/{id}")
public ApiResult findById(@NotEmpty(message = "id不能为空") @PathVariable("id") String id){return ApiResult.success("findById");
}@GetMapping("/param")
public ApiResult findByParam(@Length(min = 2) @RequestParam String name){return ApiResult.success("findByParam");
}

四、批量嵌套校验

有时候我们的接口是支持批量操作的,@Validated默认不支持批量处理,需要和@valid结合使用。

  • 定义批量校验的List
package com.imk.cases.springboot.jsr303.controller.validator;import lombok.Data;import javax.validation.Valid;
import java.util.*;@Data
public class ValidList<E> implements List<E> {@Validprivate List<E> list = new LinkedList<>();@Overridepublic int size() {return list.size();}@Overridepublic boolean isEmpty() {return list.isEmpty();}@Overridepublic boolean contains(Object o) {return list.contains(o);}@Overridepublic Iterator<E> iterator() {return list.iterator();}@Overridepublic Object[] toArray() {return list.toArray();}@Overridepublic <T> T[] toArray(T[] a) {return list.toArray(a);}@Overridepublic boolean add(E e) {return list.add(e);}@Overridepublic boolean remove(Object o) {return list.remove(o);}@Overridepublic boolean containsAll(Collection<?> c) {return list.containsAll(c);}@Overridepublic boolean addAll(Collection<? extends E> c) {return list.addAll(c);}@Overridepublic boolean addAll(int index, Collection<? extends E> c) {return list.addAll(index, c);}@Overridepublic boolean removeAll(Collection<?> c) {return list.removeAll(c);}@Overridepublic boolean retainAll(Collection<?> c) {return list.retainAll(c);}@Overridepublic void clear() {list.clear();}@Overridepublic E get(int index) {return list.get(index);}@Overridepublic E set(int index, E element) {return list.set(index, element);}@Overridepublic void add(int index, E element) {list.add(index, element);}@Overridepublic E remove(int index) {return list.remove(index);}@Overridepublic int indexOf(Object o) {return list.indexOf(o);}@Overridepublic int lastIndexOf(Object o) {return list.lastIndexOf(o);}@Overridepublic ListIterator<E> listIterator() {return list.listIterator();}@Overridepublic ListIterator<E> listIterator(int index) {return list.listIterator(index);}@Overridepublic List<E> subList(int fromIndex, int toIndex) {return list.subList(fromIndex, toIndex);}
}
  • 使用校验的List
/*** 批量创建校验测试,需要借助 ValidList ,否则不支持*  * @param list* @return*/
@PostMapping("/batch")
public ApiResult createUserList(@RequestBody @Validated(AddGroup.class) ValidList<UserDTO> list){return ApiResult.success();
}

五、使用全局异常返回校验信息

  • 校验异常类型

Controller参数校验失败会返回MethodArgumentNotValidException异常,URL请求参数校验失败ConstraintViolationException,所以我们需要对以上异常进行捕捉统一处理即可。

  • 统一异常处理方案

使用@RestControllerAdvice+@ExceptionHandler实现统一异常捕捉处理。
使用异常Starter完成异常的格式组装处理,可以参考:全局异常处理

  • 导入全局异常Starter
<dependency><groupId>com.imk.case</groupId><artifactId>springboot-02-exception-starter</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>
  • 异常校验异常处理代码实现
package com.imk.cases.springboot.jsr303.controller.handler;import com.imk.cases.springboot.core.api.ApiResult;
import com.imk.cases.springboot.exception.view.ExceptionResult;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;/*** 描述** @author darrn.xiang* @date 2022/9/12 21:21*/
@RestControllerAdvice
public class ValidExceptionHandler {/*** 校验DTO出现的异常处理** @param exception 异常信息* @return 错误信息*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Object> checkEntity(MethodArgumentNotValidException exception){HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);Map<String, String> checkResult = new HashMap<>();exception.getBindingResult().getFieldErrors().forEach(fieldError -> {checkResult.put(fieldError.getField(), fieldError.getDefaultMessage());});Map<String, Object> resultData = new HashMap<>();ExceptionResult exceptionResult = ExceptionResult.of("SYS_100002",null);resultData.put("exceptionResult",exceptionResult);resultData.put("checkResult",checkResult);ApiResult fail = ApiResult.fail(exceptionResult.getErrorMessage(),resultData);return new ResponseEntity<>(fail, headers, HttpStatus.valueOf(exceptionResult.getHttpCode()));}/*** 效验@requestParam的参数异常,非请求实体* @param exception 异常* @return 错误信息*/@ExceptionHandler(ConstraintViolationException.class)public ResponseEntity<Object> checkApiParam(ConstraintViolationException exception){HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);Map<String, String> checkResult = new HashMap<>();exception.getConstraintViolations().forEach(violation -> {checkResult.put(violation.getPropertyPath().toString(), violation.getMessage());});Map<String, Object> resultData = new HashMap<>();ExceptionResult exceptionResult = ExceptionResult.of("SYS_100003",null);resultData.put("exceptionResult",exceptionResult);resultData.put("checkResult",checkResult);ApiResult fail = ApiResult.fail(exceptionResult.getErrorMessage(),resultData);return new ResponseEntity<>(fail, headers, HttpStatus.valueOf(exceptionResult.getHttpCode()));}}

六、服务层接口参数校验

在工作开发中,有时候两个模块接口调用也需要进行参数校验,那我们是否可以使用Jsr303的注解进行检查呢,答案是必须的。下面我们一起看下如下Demo。

  • 服务接口参数校验demo
package com.imk.cases.springboot.jsr303.controller;import com.imk.cases.springboot.jsr303.controller.dto.UserDTO;
import com.imk.cases.springboot.jsr303.controller.validator.AddGroup;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;import javax.validation.Valid;/*** 描述*  * @author darrn.xiang* @date 2022/9/12 13:11*/
@Service
@Validated
public class UserService {@Validated(AddGroup.class)public synchronized int createUser(@Valid UserDTO dto){System.out.println(1);return 1;}
}
  • 服务接口demo说明
    经过多次调试验证,服务层接口参数要使用Jsr303校验必须在类上添加@Validated注解,在参数上添加@Valid注解,如果要使用分组还需要在接口方法上添加@Validated指定分组(可以在类上的@Validated指定分组,显然不符合业务需求)。

七、方法体中代码手动进行校验

以上的所有案例都是通过注解使用了参数校验,那么我们是否可以通过api在方法体中进行参数,答案也是必须的。如下:

  • 开发自定义校验工具类
package com.imk.cases.springboot.jsr303.utils;import org.hibernate.validator.HibernateValidator;
import org.springframework.util.CollectionUtils;import javax.validation.*;
import java.util.Set;/*** 描述*  * @author darrn.xiang* @date 2022/9/12 13:52*/
public class ValidatorUtils {public static void validate(Object object,Class<?>... groupClass){//初始化检查器ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ).configure().failFast( false ).buildValidatorFactory();Validator validator = validatorFactory.getValidator();//检查object参数Set<ConstraintViolation<Object>> violationSet = validator.validate(object,groupClass);if(CollectionUtils.isEmpty(violationSet)){return;}throw new ConstraintViolationException(violationSet);}
}
  • 在接口方法中的使用
ValidatorUtils.validate(dto,AddGroup.class);

八、部分场景案例调试样例

  • 创建分组校验

在这里插入图片描述

  • 批量创建校验

在这里插入图片描述

  • URL参数校验

在这里插入图片描述

附录:校验注解

引用Target-z博文:JSR 303 注解及使用方法

  • 非空检查

@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

  • boolean检查

@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

  • 字符长度检查

@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) 验证字符的长度范围

  • 日期检查

@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期

  • 正则表达匹配检查

@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式

  • 数值检查

@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@注意:建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null

  • 其它检查

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

@Validated: 用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid: 用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

这篇关于Springboot使用JSR303完成Controller或服务接口参数校验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/u012042111/article/details/126914078
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/732858

相关文章

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La