一、前言

在上一篇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参数校验内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!