凯发k8天生赢家一触即发

如何在springboot中使用jsr303对后端数据进行校验 -凯发k8天生赢家一触即发

2024-01-04

如何在springboot中使用jsr303对后端数据进行校验?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

项目创建

首先创建一个springboot项目

使用的springboot版本为:(本文代码以该版本为准,不同版本springboot,在下面内容会出现一些差异


  org.springframework.boot
  spring-boot-starter-parent
  2.3.9.release
   
 

引入如下依赖

 
  
   org.springframework.boot
   spring-boot-starter-thymeleaf
  
  
   org.springframework.boot
   spring-boot-starter-web
  
  
   org.springframework.boot
   spring-boot-devtools
   runtime
   true
  
  
   org.projectlombok
   lombok
   true
  
  
   org.springframework.boot
   spring-boot-starter-test
   test
   
    
     org.junit.vintage
     junit-vintage-engine
    
   
  
  
   org.springframework.boot
   spring-boot-starter-validation
  
 

这个作标在新一点的springboot版本中,需要单独引入。在老版本是默认引入的。这个是用来引入对jsr303注解的支持。

 
   org.springframework.boot
   spring-boot-starter-validation
  

接着创建一个java bean

package cn.jxj4869.demo.entity;
import lombok.data;
import javax.validation.constraints.notnull;
@data
public class user {
 @notnull
 private integer id;
 private string username;
 private string password;
 private string email;
}

返回类型的javabean

package cn.jxj4869.demo.entity;
import java.util.hashmap;
public class r extends hashmap {
	private static final long serialversionuid = 1l;
	public r() {
		put("code", 0);
		put("msg", "success");
	}
	public static r error(int code, string msg) {
		r r = new r();
		r.put("code", code);
		r.put("msg", msg);
		return r;
	}
	public static r ok(string msg) {
		r r = new r();
		r.put("msg", msg);
		return r;
	}
	public r put(string key, object value) {
		super.put(key, value);
		return this;
	}
}

创建一个controller。

index方法用来跳转到凯发k8天生赢家一触即发首页。

package cn.jxj4869.demo.controller;
import org.springframework.stereotype.controller;
import org.springframework.web.bind.annotation.*;
@controller
public class usercontroller {
 @requestmapping("/")
 public string index(){
  return "index";
 }
}

凯发k8天生赢家一触即发首页代码放到resources/templates目录下


 
 title
 
 新增表单  

          
        
        
    
 更新表单  

             
        
        
    

传统的检验方式

要在后端进行数据校验,传统的校验方式在controller层接受数据后,按照要求对数据进行校验

比如要接收一个user bean对象。

现在要对user对象中的username属性进行非空校验,password属性进行非空校验和长度校验。

 @postmapping("/user")
 @responsebody
 public r user1(user user) throws exception {
  if(stringutils.isempty(user.getusername())) {
   return r.error(400,"username不能为空");
  }
  if(stringutils.isempty(user.getpassword())||user.getpassword().length()>8||user.getpassword().length() <4) {
   return r.error(400,"password无效");
  }
  return null;
 }

如果有多个方法都需要接受user对象, 而且要校验的属性可能不止usernamepassword这两个属性,如果每个方法里面都采用上面这种校验方式的话,代码就会很臃肿,而且不好维护,当改变了userbean的属性,或者对校验规则进行修改后,就得对所有的校验代码进行更新。 这是一件工程量很大的事。

使用jsr303

为了解决上述问题,我们可以使用jsr303提供的注解进行校验。

jsr是java specification requests的缩写,意思是java 规范提案。jsr303也就是第303号提案。

使用jsr303的方法很简单,例如上面的需求,我们只需要在user的属性上加上注解即可。

步骤如下:

1、给bean添加校验注解,一般是在 javax.validation.constraints这个包下,也还有一些是hibernate提供的。

2、开启校验功能@valid。

3、当校验失败的时候,会抛出org.springframework.validation.bindexception异常。

常用的校验注解在文末

package cn.jxj4869.demo.entity;
import lombok.data;
import org.hibernate.validator.constraints.length;
import javax.validation.constraints.notnull;
@data
public class user {
 private integer id;
 @notblank
 private string username;
 @notblank
 @length(min = 4,max = 8)
 private string password;
 private string email;
}

然后在controller里面的方法上,加上@valid注解即可

 @postmapping("/user2")
 @responsebody
 public r user2(@valid user user) throws exception {
  system.out.println(user);
  return null;
 }

当校验失败后,会出现如下错误。并且会给出默认的提示信息。

自定义错误信息

那这个错误信息是怎么来的呢。

进入@notnull注解的代码里面

@target({ method, field, annotation_type, constructor, parameter, type_use })
@retention(runtime)
@repeatable(list.class)
@documented
@constraint(validatedby = { })
public @interface notblank {
	string message() default "{javax.validation.constraints.notnull.message}";
	............
 ............
 ............
}

会有一个message属性。显然就是指定错误的提示内容。而这些错误提示是在一个叫validationmessages.properties的文件中,用idea的搜索工具可以找到,双击shift键打开搜索。

发现有这么多validationmessages.properties的文件,而且支持国际化。

打开validationmessages_zh.properties,可以看到里面定义了这么多的提示。而错误提示就是从这文件中获取的。

如果我们不想用默认的校验提示信息的话,可以自己指定。

指定message的值即可。

@notblank(message = "用户名不能为空")
 private string username;

错误信息的获取与响应

当校验出错时,会默认返回一个错误界面,或者返回错误提示的json数据。但默认提供的显然不是我们想要的,如果可以拿到错误信息,那我们就能自定义相应数据了。

拿到错误信息的方式也很简单,只要在方法中加上bindingresult result这个参数,错误信息就会封装这里面。

@postmapping("/user2")
 @responsebody
 public r user2(@valid user user, bindingresult result) throws exception {
  system.out.println(user);
  if(result.haserrors()) { //判断是否有错误
   map map = new hashmap<>();
   //1、获取校验的错误结果
   result.getfielderrors().foreach((item)->{
    //fielderror 获取到错误提示
    string message = item.getdefaultmessage();
    //获取错误的属性的名字
    string field = item.getfield();
    map.put(field,message);
   });
   return r.error(400,"提交的数据不合法").put("data",map);
  }
  // 若没有错误,则进行接下去的业务操作。
  return null;
 }

不过不推荐上面这种方式,理由同上,当校验的地方多了,每个方法里面都加上这么个异常处理,会让代码很臃肿。

不知道你们是否还记得,springmvc里面有个全局的异常处理,我们可以自定义一个异常处理,在这里面统一处理异常。

统一处理binexception。这样就可以不用在controller中去处理错误信息了。

package cn.jxj4869.demo.execption;
import cn.jxj4869.demo.entity.r;
import org.springframework.validation.bindexception;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;
import java.util.hashmap;
import java.util.map;
@restcontrolleradvice(basepackages = "cn.jxj4869.demo.controller")
public class myexceptioncontrolleradvice {
 @exceptionhandler(value = bindexception.class)
 public r handlevaildexception(bindexception e) {
   map map = new hashmap<>();
   //1、获取校验的错误结果
   e.getfielderrors().foreach((item)->{
    //fielderror 获取到错误提示
    string message = item.getdefaultmessage();
    //获取错误的属性的名字
    string field = item.getfield();
    map.put(field,message);
   });
   return r.error(400,"提交的数据不合法").put("data",map);
 }
}

错误异常类型补充

校验出错的时候,会抛出两种异常

org.springframework.validation.bindexception

使用@valid注解进行校验的时候抛出的

org.springframework.web.bind.methodargumentnotvalidexception

使用@validated校验的时候抛出的

在异常捕获中加入下面这个

 @exceptionhandler(value= methodargumentnotvalidexception.class)
 public r handlevaildexception(methodargumentnotvalidexception e){
  bindingresult bindingresult = e.getbindingresult();
  map map = new hashmap<>();
  bindingresult.getfielderrors().foreach((fielderror)->{
   map.put(fielderror.getfield(),fielderror.getdefaultmessage());
  });
  return r.error(400,"提交的数据不合法").put("data",map);
 }

分组校验

在不同业务场景下,校验规则是不一样的,比如user对象中id这个属性,在新增的时候,这个属性是不用填的,要为null,但是在修改的时候,id属性是不能为null的。

可以用注解中的groups属性来指定,在什么场合下使用改注解

@documented
@constraint(validatedby = { })
@target({ method, field, annotation_type, constructor, parameter, type_use })
@retention(runtime)
@repeatable(list.class)
public @interface notblank {
	class[] groups() default { };
	........
}

首先定义两个接口addgroupupdategroup,不需要做任何实现

package cn.jxj4869.demo.valid;
public interface updategroup {
}
package cn.jxj4869.demo.valid;
public interface addgroup {
}

在user中指定group。

  • id属性在addgroup的时候,要为null,在updategroup的时候不能为null

  • username属性在addgroup和update的时候,都要进行校验,不能为空。

  • password属性,当校验的时候指定分组的话,会不起作用,因为没有给它指定校验的分组

package cn.jxj4869.demo.entity;
import cn.jxj4869.demo.valid.addgroup;
import cn.jxj4869.demo.valid.updategroup;
import lombok.data;
import javax.validation.constraints.notblank;
import javax.validation.constraints.notempty;
import javax.validation.constraints.notnull;
import javax.validation.constraints.null;
@data
public class user {
 @null(groups = {addgroup.class})
 @notnull(groups = {updategroup.class})
 private integer id;
 
 
 @notblank(message = "用户名不能为空",groups = {addgroup.class,updategroup.class})
 private string username;
 @notempty
 private string password;
 private string email;
}

在controller中用@validated注解,指定校验的分组

 @postmapping("/user3")
 @responsebody
 public r user3(@validated(updategroup.class) user user) {
  system.out.println(user);
  return null;
 }

结果如下图所示,因为password属性没有指定校验的分组,所以在校验的时候,都不会对它进行合法性检查。

自定义校验

当提供的注解不能满足我们需求的时候,可以自定义注解。

例如我们现在给user新加一个属性status,并要求这个属性的值只能是0或者1。

新建一个@statusvalue注解。

根据jsr303的规范,校验注解得有三个属性。

  • message:用来获取错误提示的

  • groups:指定校验分组的。

  • payload:可以自定义一些负载信息

使用@constraint注解指定该注解的校验器

package cn.jxj4869.demo.valid;
import javax.validation.constraint;
import javax.validation.payload;
import java.lang.annotation.documented;
import java.lang.annotation.retention;
import java.lang.annotation.target;
import static java.lang.annotation.elementtype.*;
import static java.lang.annotation.elementtype.type_use;
import static java.lang.annotation.retentionpolicy.runtime;
@documented
@constraint(validatedby = { statusvalueconstraintvalidator.class })
@target({ method, field, annotation_type, constructor, parameter, type_use })
@retention(runtime)
@interface statusvalue {
 string message() default "{cn.jxj4869.valid.statusvalue.message}";
 class[] groups() default { };
 class[] payload() default { };
 int[] value() default { };
}

自定义校验器

需要实现constraintvalidator这个接口,第一个泛型是表示要校验哪个注解,第二个泛型是要校验的数据的类型。

  • initialize是初始化方法

  • isvalid校验方法,判断是否校验成功

package cn.jxj4869.demo.valid;
import javax.validation.constraintvalidator;
import javax.validation.constraintvalidatorcontext;
import java.util.hashset;
import java.util.set;
public class statusvalueconstraintvalidator implements constraintvalidator {
 private set set = new hashset<>();
 //初始化方法
 @override
 public void initialize(statusvalue constraintannotation) {
  int[] value = constraintannotation.value();
  for (int val : value) {
   set.add(val);
  }
 }
 /**
  * 判断是否校验成功
  * @param value
  * @param context
  * @return
  */
 @override
 public boolean isvalid(integer value, constraintvalidatorcontext context) {
  return set.contains(value);
 }
}

最后在resources目录下添加一个validationmessages.properties文件

用来指定错误信息。

cn.jxj4869.valid.statusvalue.message=必须提交指定的值

userbean

@data
public class user {
 @null(groups = {addgroup.class})
 @notnull(groups = {updategroup.class})
 private integer id;
 @notblank(message = "用户名不能为空",groups = {addgroup.class,updategroup.class})
 private string username;
 @notempty
 private string password;
 private string email;
 @statusvalue(value = {0,1},groups = {addgroup.class,updategroup.class})
 private integer status;
}

常用注解汇总

注解 功能
@null 对象必须为null
@notnull 对象必须不为null,无法检查长度为0的字符串
@notblank 字符串必须不为null,且去掉前后空格长度必须大于0
@notempty 字符串必须非空
@length(min = 1,max = 50) 字符串必须在指定长度内
@range(min = 0,max = 100) 必须在指定范围内
@asserttrue 对象必须为true
@assertfalse 对象必须为false
@max(value) 必须为数字,且小于或等于value
@min(value) 必须为数字,且大于或等于value
@decimalmax(value) 必须为数字( bigdecimal ),且小于或等于value。小数存在精度
@decimalmin(value) 必须为数字( bigdecimal ),且大于或等于value。小数存在精度
@digits(integer,fraction) 必须为数字( bigdecimal ),integer整数精度,fraction小数精度
@size(min,max) 对象(array、collection、map、string)长度必须在给定范围
@email 字符串必须是合法邮件地址
@past date和calendar对象必须在当前时间之前
@future date和calendar对象必须在当前时间之后
@pattern(regexp=“正则”) 字符串满足正则表达式的值

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注本站行业资讯频道,感谢您对本站的支持。

网站地图