我正在参加「启航计划」
一、前言
咱们在日常开发中,避不开的便是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,咱们能够直接不论前端怎么样判断过滤,咱们后端都需求进行再次判断,为了安全
。因为前端很简单拜托,当测验运用PostMan
来测验,如果后端没有校验,不就乱了吗?必定会有很多反常的。今天小编和咱们一同学习一下JSR303专门用于参数校验的,算是一个东西吧!
二、JSR303简介
JSR-303 是 JAVA EE 6 中的一项子标准,叫做 Bean Validation,官方参阅实现是Hibernate Validator。
Hibernate Validator 供给了 JSR 303 标准中一切内置 constraint 的实现,除此之外还有一些附加的 constraint。
Hibernate官网
官网介绍:
验证数据是一项常见使命,它产生在从表明层到耐久层的一切应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又简单犯错。为了防止重复这些验证,开发人员经常将验证逻辑直接绑缚到域模型中,将域类与验证代码混在一同,而验证代码实际上是关于类自身的元数据。
Jakarta Bean Validation 2.0 – 为实体和办法验证界说了元数据模型和 API。默认元数据源是注释,能够经过运用 XML 覆盖和扩展元数据。API 不依靠于特定的应用程序层或编程模型。它特别不依靠于 Web 或耐久层,而且可用于服务器端应用程序编程以及富客户端 Swing 应用程序开发人员。
三、导入依靠
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
四、常用注解
束缚注解称号 | 束缚注解说明 |
---|---|
@Null | 用于验证目标为null |
@NotNull | 用于目标不能为null,无法查检长度为0的字符串 |
@NotBlank | 只用于String类型上,不能为null且trim()之后的size>0 |
@NotEmpty | 用于调集类、String类不能为null,且size>0。可是带有空格的字符串校验不出来 |
@Size | 用于目标(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length | 用于String目标的巨细有必要在指定的范围内 |
@Pattern | 用于String目标是否符合正则表达式的规则 |
用于String目标是否符合邮箱格局 | |
@Min | 用于Number和String目标是否大等于指定的值 |
@Max | 用于Number和String目标是否小等于指定的值 |
@AssertTrue | 用于Boolean目标是否为true |
@AssertFalse | 用于Boolean目标是否为false |
一切的咱们参阅jar包
五、@Validated、@Valid差异
@Validated:
- Spring供给的
- 支持分组校验
- 能够用在类型、办法和办法参数上。可是不能用在成员特点(字段)上
- 由于无法加在成员特点(字段)上,所以无法独自完结级联校验,需求配合@Valid
@Valid:
- JDK供给的(标准JSR-303标准)
- 不支持分组校验
- 能够用在办法、构造函数、办法参数和成员特点(字段)上
- 能够加在成员特点(字段)上,能够独自完结级联校验
总结:@Validated用到分组时运用,一个校园目标里还有很多个学生目标需求运用@Validated在Controller办法参数前加上,@Valid加在校园中的学生特点上,不加则无法对学生目标里的特点进行校验!
差异参阅博客地址
比如:
@Data
public class School{
@NotBlank
private String id;
private String name;
@Valid // 需求加上,否则不会验证student类中的校验注解
@NotNull // 且需求触发该字段的验证才会进行嵌套验证。
private List<Student> list;
}
@Data
public class Student {
@NotBlank
private String id;
private String name;
private int age;
}
@PostMapping("/test")
public Result test(@Validated @RequestBody School school){
}
六、常用运用测验
1. 实体类增加校验
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
@Data
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改有必要有品牌id")
private Long brandId;
/**
* 品牌名F
*/
@NotBlank(message = "品牌名有必要提交")
private String name;
/**
* 品牌logo地址
*/
@NotBlank(message = "地址有必要不为空")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 检索首字母
*/
//正则表达式
@Pattern(regexp = "^[a-zA-Z]$",message = "检索的首字母有必要是字母")
private String firstLetter;
/**
* 排序
*/
@Min(value = 0,message = "排序有必要大于等于0")
private Integer sort;
}
2. 统一回来类型
import com.alibaba.druid.util.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//统一回来成果
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel
public class Result<T> {
@ApiModelProperty("响应码")
private Integer code;
@ApiModelProperty("相应信息")
private String msg;
@ApiModelProperty("回来目标或许调集")
private T data;
//成功码
public static final Integer SUCCESS_CODE = 200;
//成功消息
public static final String SUCCESS_MSG = "SUCCESS";
//失利
public static final Integer ERROR_CODE = 201;
public static final String ERROR_MSG = "系统反常,请联系管理员";
//没有权限的响应码
public static final Integer NO_AUTH_COOD = 999;
//履行成功
public static <T> Result<T> success(T data){
return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);
}
//履行失利
public static <T> Result failed(String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(ERROR_CODE,msg,"");
}
//传入过错码的办法
public static <T> Result failed(int code,String msg){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,"");
}
//传入过错码的数据
public static <T> Result failed(int code,String msg,T data){
msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
return new Result(code,msg,data);
}
}
3. 测验类
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity) {
return Result.success("成功");
}
==遇到的坑==:小编在公司的项目中增加没什么问题,可是便是无法触发校验,看到的是Springboot版本太高了
,一切要增加下面的依靠才触发。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
</dependency>
4. 一般测验成果
5. 咱们把反常回来给页面
@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){
if (bindingResult.hasErrors()){
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach(item ->{
map.put(item.getField(),item.getDefaultMessage());
});
return Result.failed(400,"提交的数据不合标准",map);
}
return Result.success("成功");
}
6. 反常处理成果
{
"code": 400,
"data": {
"name": "品牌名有必要提交",
"logo": "地址有必要不为空"
},
"msg": "提交的数据不合标准"
}
七、抽离大局反常处理
1. 心得体会
上面咱们要在每个校验的接口上面写,所以咱们要抽离出来做个大局反常。而且要改进一下,原来的是把过错信息放到data里,可是正常情况下的data是回来给前端的数据。咱们这样把反常数据放进去,会使data的数据有二义性
。这样对于前端就不知道里边是数据还是报错信息了哈,这样就能够直接前端展现msg里边的提示即可!
2. 书写ExceptionControllerAdvice
import com.wang.test.demo.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice(basePackages = "com.wang.test.demo.controller")
public class ExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题:{},反常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
StringBuffer stringBuffer = new StringBuffer();
bindingResult.getFieldErrors().forEach(item ->{
//获取过错信息
String message = item.getDefaultMessage();
//获取过错的特点姓名
String field = item.getField();
stringBuffer.append(field + ":" + message + " ");
});
return Result.failed(400, stringBuffer + "");
}
@ExceptionHandler(value = Throwable.class)
public Result handleException(Throwable throwable){
log.error("过错",throwable);
return Result.failed(400, "系统反常");
}
}
3. 测验成果
{
"code": 400,
"data": "",
"msg": "logo:地址有必要不为空 name:品牌名有必要提交 "
}
八、分组校验
1. 需求
咱们在做校验的时候,通常会遇到一个实体类的增加和修改,他们的校验规则是不同的,所以分组显得尤为重要。他能够协助咱们少建一个冗余的实体类,所以咱们有必要要会的。
2. 创立分组接口(不需写任何内容)
public interface EditGroup {
}
public interface AddGroup {
}
3. 在需求二义性的字段上增加分组
/**
* 品牌id
*/
@NotNull(message = "修改有必要有品牌id",groups = {EditGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
private Long brandId;
// 其余特点咱们不变
4. 不同Controller增加校验规则
留意:咱们要进行分组,所以@Valid
不能运用了,要运用@Validated
。信任咱们现已看到上面的他俩差异了哈!
@PostMapping("/add")
public Result add(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity){
return Result.success("成功");
}
@PostMapping("/edit")
public Result edit(@Validated({EditGroup.class}) @RequestBody BrandEntity brandEntity){
return Result.success("成功");
}
5. 测验
九、自界说校验
1.界说自界说校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
//编写自界说的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<Integer>();
//初始化办法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = constraintAnnotation.vals();
for (int i : value) {
set.add(i);
}
}
/**
* 判断是否校验成功
* @param value 需求校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
2. 界说一个注解配合校验器运用
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 运用该特点去Validation.properties中取
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default {};
}
3. 实体类增加一个新的校验特点
==留意==:咱们上面做了分组,如果特点不指定分组,则不会生效,现在咱们的部分特点校验已没有起作用,现在只要brandId和showStatus
起作用。
/**
* 显现状态[0-不显现;1-显现]
*/
@NotNull(groups = {AddGroup.class, EditGroup.class})
@ListValue(vals = {0,1},groups = {AddGroup.class, EditGroup.class},message = "有必要为0或许1")
private Integer showStatus;
4. 测验
十、总结
这样就差不多对JSR303有了基本了解,满足基本开发没有什么问题哈!看到这里了,保藏点赞一波吧,整理了将近一天!!谢谢咱们了!!
欢迎咱们关注小编的微信公众号!!
有缘人才能看到,自己网站,欢迎拜访!!!
点击拜访!欢迎拜访,里边也是有很多好的文章哦!