文章目录
- 一.引入
- 二.实例需求
- 三.环境搭建
- 1.依赖引入
- 2.常用注解
- 四.使用
- 1.简单使用
- 2.自定义错误信息
- 3.添加多个字段错误信息回去
- 4.改进
- 5.全局异常处理
- 五.异常代码
- 1.图解
- 2.实例
- 六.分组校验
- 1.引入
- 2.groups属性
- 3.修改实体类
- 3.controller修改
一.引入
日常开发中,需要使用参数校验。
前端防君子,后端防小人。
学习谷里商城~
JSR 303验证框架
- JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 中
- JSR 303 通过在Bean属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则, 并通过标准的验证接口对 Bean 进行验证
- Hibernate Validator扩展注解
- Hibernate Validator 和 Hibernate 没有关系,只是 JSR 303 实现的一个扩展.
- Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支 持以下的扩展注解:
二.实例需求
要求在图中都有显示。
三.环境搭建
1.依赖引入
引入spring-boot-starter-web
会自动导入,也可以单独导入
<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.1.8.RELEASE</version>
</parent><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
如果,不使用Spring-boot,也可以单独引入依赖。
版本由自己指定
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>版本按需指定。
</dependency>
2.常用注解
约束注解名称 | 约束注解说明 |
---|---|
@Null | 用于验证对象为null |
@NotNull | 用于对象不能为null,无法查检长度为0的字符串 |
@NotBlank | 只用于String类型上,不能为null且trim()之后的size>0 |
@NotEmpty | 用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来 |
@Pattern | 用于String对象是否符合正则表达式的规则 |
用于String对象是否符合邮箱格式 |
具体使用注解,可以根据源码中注释进行使用。
注解也可以组合使用【在同一个实体属性上使用2个注解】。
通常组合注解是,要求属性不为Null,且符合某个标准
四.使用
1.简单使用
在接受数据的Controller中加@Valid注解,才会触发校验规则。
我们这里简单测试一下
另外,我们可以将校验过程中发生的错误保存到对象中去。
现在实际操作。
/*** 1.@Valid Monster monster:表示对monster接受的数据进行校验* 2.Errors errors:如果校验出现错误,将校验的错误信息保存到errors* 3.Map<String, Object> map:表示如果校验出现错误,将校验的错误信息保存到map中,同时保存Monster信息* 4.校验发生的时机:在SpringMVC底层,反射调用目标方法时,会接收http请求的数据。* 然后,根据注解【Monster实体类中标注的】进行验证。* 验证过程中,如果出现了错误,将错误信息填充到errors和map中*/@RequestMapping("/save")public R save(@Valid @RequestBody BrandEntity brand, Errors errors, Map<String, Object> map){System.out.println("======map======");for (Map.Entry<String, Object> entry : map.entrySet()) {System.out.println("key = " + entry.getKey() + " ,,,value = " + entry.getValue());}System.out.println("======errors======");if(errors.hasErrors()){List<ObjectError> allErrors = errors.getAllErrors();for (ObjectError error : allErrors) {//在这里可以凭借error获取错误的属性名,以及默认错误的返回信息//System.out.println(error.getObjectName() + "," + error.getDefaultMessage());//brandEntity,不能为空System.out.println("error = " + error);}//在这里,写入错误信息return R.error(400, "消息不能为空");}brandService.updateById(brand);return R.ok();}
----map----
key = org.springframework.validation.BindingResult.brandEntity,value = org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object ‘brandEntity’ on field ‘name’: rejected value []; codes [NotBlank.brandEntity.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandEntity.name,name]; arguments []; default message [name]]; default message [不能为空]
------errors------
Field error in object ‘brandEntity’ on field ‘name’: rejected value []; codes [NotBlank.brandEntity.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandEntity.name,name]; arguments []; default message [name]]; default message [不能为空]
2.自定义错误信息
3.添加多个字段错误信息回去
/*** @param bindingResult 封装前面对象的结果信息【可以查看是否有错误】* 这里写bindingResult,该方法发生异常就会在这里捕获。不会抛出;* 如果不写bindingResult,就会抛出异常。这个在之后简化的时候使用*/@RequestMapping("/save")public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult) {if (bindingResult.hasErrors()){//如果校验有错误Map<String,String> map = new HashMap<>();//遍历所有的错误bindingResult.getFieldErrors().forEach((item) -> {//获取到发生错误的属性名String field = item.getField();//获取到错误提示//这里的defaultMessage来自我们实体类上配置的message//如果没有配置,默认使用系统自动添加的String defaultMessage = item.getDefaultMessage();map.put(field,defaultMessage);});return R.error(400, "提交的数据不合法").put("data",map);}brandService.updateById(brand);return R.ok();}
添加其他Field检验注解
package com.atguigu.gulimall.product.entity;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* * @author sht* @email sht@gmail.com* @date 2022-10-30 13:47:36*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须提交")private String name;/*** 品牌logo地址* logo必须是一个URL,我们在设置一个自定义信息提示*/@URL(message = "logo必须是一个合法的url地址")@NotBlank(message = "不能为空")private String logo;/*** 介绍*/@NotBlank(message = "不能为空")private String descript;/*** 显示状态[0-不显示;1-显示]*/@NotNull(message = "不能为空")private Integer showStatus;/*** 检索首字母* 2.@Pattern是正则* 使用组合注解*/@Pattern(regexp = "^[a-zA-Z]$",message = "需要填写a-z或A-Z")@NotBlank(message = "不能为空")private String firstLetter;/*** 排序*/@Min(value = 0,message = "排序必须大于等于0")@NotNull(message = "不能为空")private Integer sort;}
4.改进
目前,我们将数据校验放到特定Controller中。
每次执行controller,都需要调用代码。
多个controller,需要在每个controller写一个校验,非常麻烦。
将校验提取出来,放到一个专门的类中。
使用ControllerAdvice,全局异常处理。
/*** 1.@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")* basePackages = "com.atguigu.gulimall.product.controller":异常处理扫描的包* 2.因为,要以Json格式返回,添加@ResponseBody* 3.有@ControllerAdvice、@ResponseBody,简化这2个注解@RestControllerAdvice[包含2个注解]**/
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionAdvice {/*** 1.value = Exception.class,可以处理的异常信息* 2.Exception e:捕获到的异常信息*/@ExceptionHandler(value = Exception.class)public R handlerValidException(Exception e){log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());return R.error();}
}
然后,发送一个请求,查看idea报的异常信息是什么。
主要是异常类型是什么
然后根据获取到的异常信息,修改全局异常处理类
package com.atguigu.gulimall.product.exception;import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;
import java.util.Map;/*** 1.@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")* basePackages = "com.atguigu.gulimall.product.controller":异常处理扫描的包* 2.因为,要以Json格式返回,添加@ResponseBody* 3.有@ControllerAdvice、@ResponseBody,简化这2个注解@RestControllerAdvice[包含2个注解]**/
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionAdvice {/*** 1.value = Exception.class,可以处理的异常信息* 2.Exception e:捕获到的异常信息* 捕获异常类型, 一般根据业务进行判断*/@ExceptionHandler(value = MethodArgumentNotValidException.class)public R handlerValidException(MethodArgumentNotValidException e){log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());//这个和之前是一样的,niceBindingResult bindingResult = e.getBindingResult();Map<String,String> errMap = new HashMap<>();bindingResult.getFieldErrors().forEach((fieldError -> {errMap.put(fieldError.getField(),fieldError.getDefaultMessage());}));return R.error(400, "数据校验出现问题").put("data", errMap);}
}
5.全局异常处理
/*** 处理其他出现的任何异常*/@ExceptionHandler(value = Throwable.class)public R handlerException(Throwable throwable){return R.error();}
五.异常代码
1.图解
将错误代码,使用枚举一一列举出来,使用的时候,用枚举引入。
2.实例
在gulimall-common中创建一个枚举类
package com.atguigu.common.exception;import lombok.AllArgsConstructor;
import lombok.Getter;@Getter
@AllArgsConstructor
public enum BizCodeEnums {UNKNOW_EXCEPTION(10000,"系统未知异常"),VALID_EXCEPTION(10001,"参数格式校验失败");private Integer code;private String msg;
}
此时,数据校验错误,我们直接用枚举中的。
package com.atguigu.gulimall.product.exception;import com.atguigu.common.exception.BizCodeEnums;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;
import java.util.Map;/*** 1.@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")* basePackages = "com.atguigu.gulimall.product.controller":异常处理扫描的包* 2.因为,要以Json格式返回,添加@ResponseBody* 3.有@ControllerAdvice、@ResponseBody,简化这2个注解@RestControllerAdvice[包含2个注解]**/
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionAdvice {/*** 1.value = Exception.class,可以处理的异常信息* 2.Exception e:捕获到的异常信息*/@ExceptionHandler(value = MethodArgumentNotValidException.class)public R handlerValidException(MethodArgumentNotValidException e){log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());//这个和之前是一样的,niceBindingResult bindingResult = e.getBindingResult();Map<String,String> errMap = new HashMap<>();bindingResult.getFieldErrors().forEach((fieldError -> {errMap.put(fieldError.getField(),fieldError.getDefaultMessage());}));return R.error(BizCodeEnums.VALID_EXCEPTION.getCode(), BizCodeEnums.VALID_EXCEPTION.getMsg()).put("data", errMap);}/*** 处理其他出现的任何异常*/@ExceptionHandler(value = Throwable.class)public R handlerException(Throwable throwable){return R.error(BizCodeEnums.UNKNOW_EXCEPTION.getCode(),BizCodeEnums.UNKNOW_EXCEPTION.getMsg());}
}
六.分组校验
1.引入
一个实体类,对应数据库一张表。
在对一张表,进行增删查改操作时,校验规则有时候不一样。新增情况、修改情况,校验规则不同。
新增,不需要携带品牌id,且一些数据必须填写。
修改,必须携带品牌id,而且一些不用改的数据不需要填写。此时,我们就要使用JSR303提供的分组校验功能。我们使用groups,这个在每个校验规则中有这个属性。
2.groups属性
groups属性,是一个类型数组。因此,可以有多种情况。我们需要定义几个类型,使用接口。这里接口只起到标注作用。因为,很多微服务有这个需求,我们放到gulimall-common中。
![在这里插入图片描述](https://img-blog.csdnimg.cn/caad3e8a48834c878746cb3a14981550.png)
3.修改实体类
package com.atguigu.gulimall.product.entity;import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.UpdateGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* * @author sht* @email sht@gmail.com* @date 2022-10-30 13:47:36*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id* 修改时,提交该字段* 新增时,不提交该字段*/@NotNull(message = "修改不需指定id",groups = {UpdateGroup.class})@Null(message = "新增不能指定id",groups = {AddGroup.class})@TableIdprivate Long brandId;/*** 品牌名* 新增,修改时,必须提交改字段*/@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})private String name;}
3.controller修改
需要在待校验的对象之前添加注解@Validated
添加@Validated注解后,指定分组后。
其他未指定分组的校验,就不会生效。
只会按照分组进行校验。
想要生效,必须在其他校验上写上分组。
在其他所有校验注解中添加groups = {AddGroup.class}
package com.atguigu.gulimall.product.entity;import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.UpdateGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* * @author sht* @email sht@gmail.com* @date 2022-10-30 13:47:36*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id**/@NotNull(message = "修改不需指定id",groups = {UpdateGroup.class})@Null(message = "新增不能指定id",groups = {AddGroup.class})@TableIdprivate Long brandId;/*** 品牌名**/@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})private String name;/*** 品牌logo地址* logo必须是一个URL,我们在设置一个自定义信息提示*/@URL(message = "logo必须是一个合法的url地址",groups = {AddGroup.class})@NotBlank(message = "不能为空",groups = {AddGroup.class})private String logo;/*** 介绍*/@NotBlank(message = "不能为空",groups = {AddGroup.class})private String descript;/*** 显示状态[0-不显示;1-显示]*/@NotNull(message = "不能为空",groups = {AddGroup.class})@ListValue(vals = {0,1},message = "状态必须为0或1",groups = {AddGroup.class})private Integer showStatus;/*** 检索首字母* 2.@Pattern是正则* 使用组合注解*/@Pattern(regexp = "^[a-zA-Z]$",message = "需要填写一个字母[a-z或A-Z]",groups = {AddGroup.class})@NotBlank(message = "不能为空",groups = {AddGroup.class})private String firstLetter;/*** 排序*/@Min(value = 0,message = "排序必须大于等于0",groups = {AddGroup.class})@NotNull(message = "不能为空",groups = {AddGroup.class})private Integer sort;}