一 前言.
在我们接收从网页传过来的数据时,首要的就是要对其进行校验,避免垃圾数据和保障系统安全,这里推荐一种注解校验方式,也就是JSR303.
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation
而所谓JSR,也就是一个规范.
JSR-303主要有以下注解:
二.使用入门步骤.
- 在实体类字段上添加对应的注解校验,并自定义返回信息
举例:
/**
* 品牌名,必须包含非空格字符
*/
@NotBlank(message = "品牌名不能为空")
private String name;
@NotBlank
@URL(message = "logo必须为url格式")
private String logo;
/**
* 检索首字母,我们自己自定义规则
*/
@NotBlank
@Pattern(regexp = "[a-zA-Z]",message = "检索首字母必须有且只有一个英文字母")
private String firstLetter;
- 在controller中的方法请求体中增加@Valid注解,即可对brand对象进行校验
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
}
-
完善返回校验信息.
默认的返回错误信息不是我们想要的,会把异常返回出去, 正常我们应该封装统一的返回对象,这里就是R,然后将错误信息封装到该对象中.进行返回.
实现步骤:
(1) 在方法参数后紧跟一个BindingResult参数,这个参数中封装了所有的校验返回信息
(2)针对BindingResult中的参数进行封装举例:
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult){
if(bindingResult.hasErrors()){
HashMap<String, String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
String field = item.getField();
String defaultMessage = item.getDefaultMessage();
map.put(field,defaultMessage);
});
return R.error(400,"数据校验异常").put("data",map);
}else{
brandService.save(brand);
return R.ok();
}
}
三.全局异常处理.
-
使用原因.
在之前我们通过JSR完成对指定参数的校验,在方法中封装了对异常的处理,但是这种方式很麻烦,需要在每个方法中都写一遍,且内容都差不多一样.
这里Spring MVC还提供了一种全局处理的方式,让我们方便的处理异常.其实就是ControllerAdvice. -
使用步骤.
(1). 新建一个全局异常类.类上增加注解
@ControllerAdvice(“需要处理的类的路径”).
注意:由于一般我们需要返回JSON格式,所以就直接换成这个注解,@RestControllerAdvice,它是@ControllerAdvice和@ResponseBody的结合.
(2).在该类中新建方法,方法上增加注解@ExceptionHander(“需要处理的异常类”)
方法体中写上对异常的处理即可. -
原理(猜测)
解析@ControllerAdvice注解中设置的路径,对于这个路径下的所有的对象都进行代理,当代理执行方法异常的时候,进行异常的捕获.
捕获后判断异常类型是否与@ExceptionHander注解中的异常对象相等.相等则执行对应方法. -
样例.
统一异常处理类:
@Slf4j
@RestControllerAdvice("com.*.*.product.controller")
public class ControllerExceptionAdvice {
/**
* 处理JSR的校验异常
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R exceptionHander(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
HashMap<String, String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach(fieldError -> {
String field = fieldError.getField();
String defaultMessage = fieldError.getDefaultMessage();
map.put(field,defaultMessage);
});
return R.error(ExceptionEnum.CHECK_EXCEPTION.getCode(),ExceptionEnum.CHECK_EXCEPTION.getMsg()).put("data",map);
}
/**
* 处理其他全局异常
* @param e
* @return
*/
@ExceptionHandler(Throwable.class)
public R exceptionHander(Throwable e){
log.error(e.getMessage());
return R.error(ExceptionEnum.UNKNOW_EXCEPTION.getCode(),ExceptionEnum.UNKNOW_EXCEPTION.getMsg());
}
}
校验方法:
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
四.分组校验.
-
使用原因.
在我们新加或者更新操作的时候,对于同一个对象的校验值是不一样的,比如说新增的时候不能有数据id,更新的时候一定要有数据id.这个时候我们就需要用到分组校验的功能.
其实本质就是给校验注解进行分组,然后在校验时选择使用哪一个分组. -
使用步骤.
(1).新增两个分组类.这个类其实就是标识一下,所以我们新建两个接口即可.
public interface UpdateGroup {
}
public interface AddGroup {
}
(2).对每个注解指定属于哪个分组.
/ **
* 品牌
*
* @author bling
* @email llwang.tim@gmail.com
* @date 2020-05-08 22:07:26
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
@Null(message="新增数据不能包含id",groups = AddGroup.class)
@NotNull(message="更新数据必须指定id",groups = UpdateGroup.class)
private Long brandId;
/**
* 品牌名,必须包含非空格字符
*/
@NotNull(message="品牌名不能为空",groups = AddGroup.class)
@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})
private String name;
/**
* 品牌logo地址,符合url校验规则
*/
@NotBlank(message="品牌名不能为空",groups = AddGroup.class)
@URL(message = "logo必须为url格式",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母,我们自己自定义规则
*/
@NotBlank(message="检索首字母不能为空",groups = AddGroup.class)
@Pattern(regexp = "[a-zA-Z]",message = "检索首字母必须有且只有一个英文字母",groups = {AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(message="排序id不能为空",groups = AddGroup.class)
@Min(value = 0,message = "logo必须为url格式",groups = {AddGroup.class,UpdateGroup.class})
private Integer sort;
}
(3). 使用校验注解@Validated并指定分组.
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
注意:如果使用了分组校验,但是某些属性校验并没有明确的指定分组,是不起作用的.
五.自定义注解校验.
-
使用原因.
之前我们说没有我们想要的注解的话,可以使用@Pattern注解,里面给定正则,就可以使用
那有时候我们有其他的通用的规则,不用每次都写一个正则,可以直接编写一个自定义注解.
这个注解其实是使用的JSR规范,我们不需要去通过反射什么使用这个,只需要利用它的规范,实现接口即可. -
举例.
这里我们编写一个ListValue注解,这个注解的作用是校验必须为我们指定的值,否则校验失败,像这样.
@ListValue({0,1})
private Integer value;
那这里的value值就必须为0或者1,否则不得行.
- 具体步骤.
(1).编写自定义校验器
编写一个类实现ConstraintValidator接口.注意ConstraintValidator这个类一定是javax下面的,否则不生效.
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> listValueSet = new HashSet<>();
/**
* 初始化方法
* 这个参数就是我们自定的注解,可以通过参数获取到指定的值
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int value : values) {
listValueSet.add(value);
}
}
/**
* 进行真正的自定义校验
* @param value 就是我们真正传入的属性的值
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
if(value == null)
return false;
return listValueSet.contains(value);
}
}
(2).编写自定义注解,并绑定注解和校验器.
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
//这个注解就是JSR的规范,用来指定校验器的,这个校验器就是上面我们自定义的,绑定了注解和校验器
@Constraint(validatedBy = {ListValueConstraintValidator.class})
public @interface ListValue {
//下面这三个属性也是JSR的规范,必须要有这三个属性
String message() default "{显示状态必须为指定的值}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
//这个属性是我们自己定义的
int[] values() default {};
}
做好这两步就可以正常使用自己的自定义注解了,像使用官方的注解一样自然.
今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.