JSR303进行数据校验
1.给Bean添加校验注解
可以多个注解校验
@NotBlank(message = "品牌名不能为空")
private String name;
//不能为空 + 自定义规则
@NotEmpty
@Pattern(regexp = "!/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
private String firstLetter;
//不能为空 + 最小值
@NotEmpty
@Min(value = 0,message = "排序最小值是0")
private Integer sort;
Tips:@JsonInclude(JsonInclude.Include.NON_EMPTY) 标注的字段返回不为空时才会返回
2.在提交数据时开启数据校验功能
/**
* 保存
* @Valid 注解告诉springmvc需要校验
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
}
3.校验错误时会有默认的响应
在被校验的Bean后紧跟BindResult 可以获取校验结果
/**
* 保存
* @Valid 注解告诉springmvc需要校验
* BindingResultresult 封装了校验结果
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResultresult) {
brandService.save(brand);
return R.ok();
}
测试:
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {
if (result.hasErrors()){
Map<String,String> map = new HashMap<>();
//获取校验的错误结果
result.getFieldErrors().forEach((item) -> {
//错误提示消息
String message = item.getDefaultMessage();
//获取错误的字段
String field = item.getField();
map.put(field, message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else {
}
brandService.save(brand);
return R.ok();
}
返回结果
{
"msg": "提交的数据不合法",
"code": 400,
"data": {
"name": "品牌名不能为空"
}
}
统一异常处理类
@ControllerAdvice
- ControllerAdvice本质上是一个Component
- 就是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来,具体你想做更细致的拦截筛选和拦截之后的处理,通过@ExceptionHandler、@InitBinder 或 @ModelAttribute这三个注解以及被其注解的方法来自定义。
- @ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理
- 全局数据预处理
- 全局数据绑定 @ControllerAdvice修饰的类中数据 在每一个controller都能访问
//放入数据
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ModelAttribute(name = "md")
public Map<String,Object> mydata() {
HashMap<String, Object> map = new HashMap<>();
map.put("age", 99);
map.put("gender", "男");
return map;
}
}
//取出数据
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
Map<String, Object> map = model.asMap();
System.out.println(map);
int i = 1 / 0;
return "hello controller advice";
}
}
1.抽取一个异常处理类
package cn.cloud.xmall.product.exception;
import cn.cloud.xmall.common.exception.ExceptionEnume;
import cn.cloud.xmall.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.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 集中处理所有异常
* 指定的Controller的异常被抛出时会被拦截
* basePackages 处理哪个Controller的异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "cn.cloud.xmall.product.controller")
public class XmallExceptionControllerAdvice {
/**
* 此方法处理数据校验异常
* @ExceptionHandler 告诉springmvc这个注解标注的这个方法能处理什么异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleVailException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
log.error("数据校验异常"+e.getMessage()+e.getClass());
Map<String,String> map = new HashMap<>();
//获取校验的错误结果
result.getFieldErrors().forEach((item) -> {
//错误提示消息
String message = item.getDefaultMessage();
//获取错误的字段
String field = item.getField();
map.put(field, message);
});
return R.error(ExceptionEnume.VATLD_EXCEPTION.getCode(),ExceptionEnume.VATLD_EXCEPTION.getMsg()).put("data",map);
}
/**
* 最大的异常处理 处理任意类型
* @return
*/
@ExceptionHandler(value = Exception.class)
public R handleException(){
return R.error(ExceptionEnume.UNKNOW_EXCEPTION.getCode(),ExceptionEnume.UNKNOW_EXCEPTION.getMsg());
}
}
定义异常信息枚举类
package cn.cloud.xmall.common.exception;
/**
* 系统错误代码枚举类
*/
public enum ExceptionEnume {
UNKNOW_EXCEPTION(10001,"系统为止异常"),
VATLD_EXCEPTION(10001,"参数格式校验失败");
private Integer code;
private String msg;
ExceptionEnume(Integer code,String msg){
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
数据分组校验
在不同情况下触发的校验规则可以使用分组校验来区分
可以完成多场景的复杂校验
@Valid 为JSR303规范提供的注解 @Validated为spring框架提供的注解
1.给校验注解标注,什么情况下需要校验
@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定品牌id",groups = {AddGroup.class})
@TableId
private Long brandId;
@Validated 可以指定一个或多个校验分组
2.将@Valid 注解修改为@Validated 并指定校验分组(可以指定多个)
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand) {
}
默认没有指定分组的校验注解 例如@NULL等 在分组校验情况下不会生效,只会在不分组的情况下生效
自定义注解校验
现已有的注解无法满足校验需求
1.编写自定义校验注解
需要引入依赖
<!-- 校验API -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
package cn.cloud.xmall.common.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.RetentionPolicy.RUNTIME;
/**
* 注解复制其他校验注解就可
*/
@Documented
//自定义校验器对象
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
//变量为校验注解的全类名 此参数必须
String message() default "{cn.cloud.xmall.common.valid.ListValue.message}";
//此参数必须
Class<?>[] groups() default { };
//此参数必须
Class<? extends Payload>[] payload() default { };
//value属性 默认空
int[] vals() default { };
}
2.编写自定义校验器
@Constraint(validatedBy = { }) validatedBy = { } 可以指定使用什么来校验
public interface ConstraintValidator<A extends Annotation, T> 是一个接口 泛型一是注定注解,第二个指定校验什么类型的数据 ,自定义校验器需实现此接口
package cn.cloud.xmall.common.valid;
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<>();
/**
* 初始化方法
* 会将@ListValue 注解的详细信息获取
* vals 即 @ListValue(vals = {0,1})
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
//将注解上的校验信息存入Set集合
if (vals.length != 0){
for (int val : vals) {
set.add(val);
}
}
}
/**
* 判断是否校验成功
* @param value 传过来需要校验的值
* @param context 整个校验的上下文
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
//返回是否包含规定的值
return set.contains(value);
}
}
3.关联自定义校验器和注解
给校验注解添加 即自定义校验器
可以使用多个校验器,例如需要校验的类型不同时
@Constraint(validatedBy = { ListValueConstraintValidator.class })