SpringBoot参数校验的方法总结

一、前言

在上一篇MyBatis-plus 初体验 中已经简单实现了 MyBatis-Plus 数据库查询。我们知道 CURD 离不开前后端的数据交互,因此参数校验是必不可少的。这篇主要讲一下 SpringBoot 参数校验。

在 Web 开发中经常需要对前端传过来的参数进行校验,例如格式校验、非空校验等,基本上每个接口都需要进行校验。如果使用常规的 IF ELSE 进行校验,随着参数越来越多,校验逻辑的冗余度也越来越高,导致维护性变差。在 Java 中定义了一套基于注解的数据校验规范 Bean Validation ,通过一些简单的注解就能完成必要的逻辑校验,相对来说就方便了很多。而 Bean Validation 只是规范,并没有具体的实现,Hibernate 提供了具体的实现,也即 Hibernate Validator ,这个也是目前使用得比较多的验证器了。

二、注解介绍

validator 内置注解

  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素必须不为 null
  • @AssertTrue 被注释的元素必须为 true
  • @AssertFalse 被注释的元素必须为 false
  • @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max, min) 被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past 被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期
  • @Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

  • @Email 被注释的元素必须是电子邮箱地址
  • @Length 被注释的字符串的大小必须在指定的范围内
  • @NotEmpty 被注释的字符串的必须非空
  • @Range 被注释的元素必须在合适的范围内
  • @NotBlank 验证字符串非 null ,且长度必须大于0

注意

  • @NotNull 用于验证对象是否不为 null ,无法检测长度为0的字符串;
  • @NotEmpty 用于 String、Map 或者数组等集合类型不能为 null 且长度必须大于0;
  • @NotBlank 只能用于String,不能为 null ,且调用 trim() 后,长度必须大于0;

校验字符串是否为空,使用 @NotNull ,只有参数不传的时候才会检测到,传了空值(例如空字符串)仍然可以通过校验,因此应该使用 @NotBlank

三、添加依赖

SpringBoot 中 Bean Validation 已经集成在 starter-web 中,因此无需再添加依赖。但是本人实际测试发现,直接使用好像不行,因此添加了如下依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

四、创建用于校验的实体类

创建一个 validator 目录,在里面创建一个 UserDTO 类:

本来想直接复用之前创建的 entity 类,但是后来想了下,entity 用于建立数据库的映射关系,字段跟数据表是一一对应的,而这里的 validator 是用于校验前端传过来的参数,字段跟前端传的参数是对应的,因此不能复用,需要单独写一个 validator 类。

顺便提一下,在 RestController 中使用自己定义的对象,需要有 setter、getter 之类的方法,或者使用 lombok 的 @Data 注解。如果不加的话会报错:

No converter found for return value of type: class validator.UserDTO

使用 getter、setter 方法如下:

public class UserDTO {
	private String username;
	private Integer age;

	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}

	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}

使用 lombok 的 @Data 注解如下,代码与上面等效:

@Data
public class UserDTO {
	private String username;
	private Integer age;
}

五、写一个测试用的接口

添加一个 POST 接口,从请求体中获取参数,然后原封不动返回过去(主要是用来测试参数校验的,这里接口逻辑并不重要)

@PostMapping("validateUser")
public UserDTO userValidate(@RequestBody UserDTO userDTO) {
    return userDTO;
}

六、在实体类中添加注解

给需要校验的参数添加注解:

@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "手机号不能为空")
    private String mobile;

    @NotNull(message = "性别不能为空")
    private Integer sex;

    @NotNull(message = "年龄不能为空")
    private Integer age;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式错误")
    private String email;
}

七、在 controller 方法中添加 Validated 注解

然后需要在 controller 方法体添加 @Validated ,不加 @Validated 校验会不起作用。

用下面的数据测试一下:

{
    "username": "",
    "mobile": "2333",
    "sex": 0,
    "age": 0,
    "email": "233@dby.com"
}

可以看到校验是成功了,但是后台抛了一个异常:

Validation failed for argument [0] in public validator.UserDTO com.hhlnyfz.hhlnyfz.HelloController.userValidate(validator.UserDTO): [Field error in object ‘userDTO' on field ‘username': rejected value []; codes [NotBlank.userDTO.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.username,username]; arguments []; default message [username]]; default message [用户名不能为空]] ]

然后返回参数并不理想,前端也并不容易处理返回参数:

八、添加全局异常处理

上面这种情况需要添加一下全局异常处理,这样比较规范。创建一个 GlobalExceptionHandler 类,在类的上面添加 @RestControllerAdvice 注解,然后添加如下代码:

/**
 * 全局异常处理类
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    // 捕获 MethodArgumentNotValidException 异常
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public HashMap<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", 400);
        map.put("msg", e.getMessage());
        map.put("url", request.getRequestURL());
        return map;
    }

    // 其他异常处理方法
}

这边 @ExceptionHandler 注解中的 MethodArgumentNotValidException.class 用于捕获请求参数异常。如果是 Exception.class 表示捕获全部异常。不要用一个方法处理所有的异常,而是一个方法处理一种异常。如果需要处理其他异常,可以在下面添加方法。

还是用刚才的测试用例,这次异常被捕获到了,返回的内容如下:

可以看到 e.getMessage() 把整个错误堆栈信息全部打印出来了,但我们只需要把最后的 default message 返回给前端就行,因此改用 e.getBindingResult().getFieldError().getDefaultMessage() ,然后 IDE 给了提示:

Method invocation ‘getDefaultMessage' may produce ‘NullPointerException'

也就是说 e.getBindingResult().getFieldError() 可能会是一个空指针 null ,于是按照 IDE 的提示用 Objects.requireNonNull 包裹一下,最终代码如下:

/**
 * 全局异常处理类
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    // 捕获 MethodArgumentNotValidException 异常
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public HashMap<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", 400);
        map.put("msg", Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
        map.put("url", request.getRequestURL());
        return map;
    }

    // 其他异常处理方法
}

响应内容如下:

当然这边还是有不规范的地方,没有用统一响应体进行返回,后面会介绍如何封装统一响应体。

九、分组校验

@Valid@Validated 两个注解都可以实现校验,前面的功能用 @Valid 也是可以的,但是 @Validated 功能更强大,可以实现分组校验。什么是分组校验,分组校验实际上实现了实体类的复用,有时候并不希望对所有的参数都进行校验,例如下面这个情况:

@Data
public class Route {
	@NotNull(message = "始发地省id不能为空")
	private Integer startProvinceId;

	@NotNull(message = "目的地省id不能为空")
	private Integer endProvinceId;

	@NotBlank(message = "详细地址不能为空")
	private String address;
}

假如在一个接口中只希望校验 startProvinceIdaddress ,而在另一个接口中只希望校验 endProvinceIdaddress ,这个时候就可以用分组校验。可以定义一个接口:

public interface ValidateGroup {
	interface RouteValidStart {}
	interface RouteValidEnd {}
}

然后在实体类中添加分组:

@Data
public class Route {
	@NotNull(groups = {RouteValidStart.class}, message = "始发地省id不能为空")
	private Integer startProvinceId;

	@NotNull(groups = {RouteValidEnd.class}, message = "目的地省id不能为空")
	private Integer endProvinceId;

	@NotBlank(groups = {RouteValidStart.class, RouteValidEnd.class}, message = "详细地址不能为空")
	private String address;
}

然后在校验的时候只需要把分组传入 @Validate 就可以实现指定参数的校验:

@RequestMapping("addRoute")
public ServerResponse addRoute(@RequestBody @Validated({RouteValidStart.class}) Route route) {
	// ...
	return ServerResponse.success();
}

十、单个参数校验

在参数前面加上注解即可:

@PostMapping("/get")
public ReturnVO getUserInfo(@RequestParam("userId") @NotNull(message = "用户ID不能为空") String userId){
    return new ReturnVO().success();
}

然后在 Controller 类上面增加 @Validated 注解,注意不是增加在参数前面。

到此这篇关于SpringBoot参数校验的方法总结的文章就介绍到这了,更多相关SpringBoot参数校验内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot使用validation-api实现参数校验的示例

    我们在开发Java项目的时候,经常需要对参数进行一些必填项.格式.长度等进行校验,如果手写代码对参数校验,每个接口会需要很多低级的代码,这样会降低代码的可读性.那么我们能不能使用一种比较优雅的方式来实现,对请求中的参数进行校验呢? knife4j的安装与使用可参考我的博客:SpringBoot使用knife4j进行在线接口调试 正文 ValidationApi框架就是用来解决参数校验中代码冗余问题,ValidationApi框架提供一些注解用来帮助我们对请求参数进行校验: SpringBoot使

  • Springboot集成JSR303参数校验的方法实现

    JSR303 是一套 JavaBean 参数校验的标准 1.pom导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 2.注解类型 (1)空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null,

  • springboot使用hibernate validation对参数校验的实现方法

    springboot天生支持使用hibernate validation对参数的优雅校验,如果不使用它,只能对参数挨个进行如下方式的手工校验,不仅难看,使用起来还很不方便: if(StringUtils.isEmpty(userName)){ throw new RuntimeException("用户名不能为空"); } 下面将介绍hibernate validation的基本使用方法. 一.引入依赖 这里在springboot 2.4.1中进行实验,引入以下依赖: <pare

  • SpringBoot中自定义注解实现参数非空校验的示例

    前言 由于刚写项目不久,在写 web 后台接口时,经常会对前端传入的参数进行一些规则校验,如果入参较少还好,一旦需要校验的参数比较多,那么使用 if 校验会带来大量的重复性工作,并且代码看起来会非常冗余,所以我首先想到能否通过一些手段改进这点,让 Controller 层减少参数校验的冗余代码,提升代码的可阅读性. 经过阅读他人的代码,发现使用 annotation 注解是一个比较方便的手段,SpringBoot 自带的 @RequestParam 注解只会校验请求中该参数是否存在,但是该参数是

  • Springboot使用@Valid 和AOP做参数校验及日志输出问题

    项目背景 最近在项目上对接前端的的时候遇到了几个问题 1.经常要问前端要请求参数 2.要根据请求参数写大量if...else,代码散步在 Controller 中,影响代码质量 3.为了解决问题1,到处记日志,导致到处改代码 解决方案 为了解决这类问题,我使用了@Valid 做参数校验,并使用AOP记录前端请求日志 1.Bean实体类增加注解 对要校验的实体类增加注解,如果实体类中有List结构,就在List上加@Valid @Valid注解 注解 备注 @Null 只能为null @NotNu

  • SpringBoot使用validation做参数校验的实现步骤

    1.添加依赖 直接添加 hibernate-validator <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.2.Final</version> </dependency> 添加spring-boot-starter-validat

  • SpringBoot如何优雅的处理校验参数的方法

    前言 做web开发有一点很烦人就是要校验参数,基本上每个接口都要对参数进行校验,比如一些格式校验 非空校验都是必不可少的.如果参数比较少的话还是容易 处理的一但参数比较多了的话代码中就会出现大量的IF ELSE就比如下面这样: 这个例子只是校验了一下空参数.如果需要验证邮箱格式和手机号格式校验的话代码会更多,所以介绍一下validator通过注解的方式进行校验参数. 什么是Validator Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.

  • SpringBoot + validation 接口参数校验的思路详解

    有参数传递的地方都少不了参数校验.在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全.试想一下,如果在controller层中没有经过任何校验的参数通过service层.dao层一路来到了数据库就可能导致严重的后果,最好的结果是查不出数据,严重一点就是报错,如果这些没有被校验的参数中包含了恶意代码,那就可能导致更严重的后果. 实践 一.引入依赖 <!--引入spring-boot-starter-validation--> <dependency> <gr

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • springboot+dubbo+validation 进行rpc参数校验的实现方法

    注意:本文dubbo 版本 2.8.4 springboot 版本 2.0.4.RELEASE 项目结构 test-rest (前端消费着,controller 层,springboot+maven项目) test-api (dubbo服务 的 api ,只记录 service 接口和 model ,maven 项目) test-provider(dubbo 服务提供者,实际的数据库操作及业务层, springboot+maven项目 ) 背景: 使用springmvc做restful,使用du

  • SpringBoot参数校验与国际化使用教程

    一.参数校验 springboot 使用校验框架validation校验方法的入参 SpringBoot的Web组件内部集成了hibernate-validator,所以我们这里并不需要额外的为验证再导入其他的包. 1.bean 中添加标签 标签需要加在属性上,@NotEmpty标签String的参数不能为空 @Data public class DemoDto { @NotEmpty(message = "名称不能为空") private String name; @Length(m

  • SpringBoot中的异常处理与参数校验的方法实现

    兄弟们好,这次来跟老铁交流两个问题,异常和参数校验,在说参数校验之前我们先来说异常处理吧,因为后面参数的校验会牵扯到异常处理这块的内容. 异常处理 说到异常处理,我不知道大家有没有写过或者遇到过如下的写法. public void saveUser() { try { // 所有的业务内容,目测几百行 }catch (Exception e) { e.printStackTrace(); } } 如果出现上述的代码,里面包含了大量的业务代码,如果是你写的,赶紧改掉,不是你写的找写的,吐槽赶紧改掉

随机推荐