Spring - JSR303数据校验

9 篇文章 0 订阅

在做项目的时候需要对表单的值进行校验,只有校验通过才能提交,一般来说前端和后端都需要做校验,JSR303是做后端校验的一种方式。

JSR303简介

JSRJava Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务,JSR已成为Java界的一个重要标准。
JSR-303JAVA EE 6 中的一项子规范,叫做Bean ValidationHibernate ValidatorBean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint

一、Bean Validation 中内置的 constraint

在这里插入图片描述

二、Hibernate Validator 附加的 constraint

在这里插入图片描述
更多的constraint请见百度。

JSR303的使用

下文需要的基础返回类型 - R
public class R extends HashMap<String, Object> {
	private static final long serialVersionUID = 1L;
	
	public R() {
		put("code", 0);
		put("msg", "success");
	}
	
	public static R error() {
		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
	}
	
	public static R error(String msg) {
		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
	}
	
	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 static R ok(Map<String, Object> map) {
		R r = new R();
		r.putAll(map);
		return r;
	}
	
	public static R ok() {
		return new R();
	}

	public R put(String key, Object value) {
		super.put(key, value);
		return this;
	}
}
一、常规使用
1、标注校验注解

给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示。

// Brand对象里的属性
@NotBlank(message = "品牌名至少包含一个非空字符")
private String name;  // 品牌名

@NotNull(message = "必须指定品牌Id")
private Long id;      // 品牌id

@URL(message = "logo必须是一个合法的url地址")       // URL校验
private String logo;  // 品牌logo的url

@NotEmpty
@Pattern(regexp = "^[a-zA-Z]", message = "检索首字母必须是一个字母")   // 正则校验
Private String firstLetter;   // 品牌首字母,如小米就是X

@NotNull
@Min(value = 0, message = "排序必须大于等于0")
private Integer sort;        // 排序字段
2、开启校验功能

在需要校验的对象前添加@Valid注解开启校验功能,在被校验的对象之后添加BindingResult对象可以获取校验结果。

// Controller层,返回的是JSON数据(即@RestController)
@RequestMapping("/save")
public R save(@Valid @RequestBody Brand brand, BindingResult result){
	if(result.hasErrors()){   // 获取校验的错误结果
		Map<String, String> resultMap = new HashMap<>();
		result.getFieldErrors().forEach((item) -> {
			// 获取错误的校验消息
			String message = item.getDefaultMessage();
			// 获取错误的属性名
			String field = item.getField();
			resultMap.put(field,message);
		});
		return R.error(400,"提交的数据不合法").put("data",map);
	}
	// 正常保存 - 调用Service层进行保存
	brandService.save(brand);
	return R.ok();
}
3、全局异常处理

上面的步骤已经完成了基本校验,但是有点复杂,需要在Controller方法里添加重复代码。
错误需要返回一个错误码及错误消息,因此使用一个枚举类型来定义,然后这种方式也挺适合项目里使用。

/***
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 */
public enum BizCodeEnum {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败");

    private int code;
    private String msg;
    BizCodeEnum(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

处理全局异常 - 处理校验错误异常:

@Slf4j
@RestControllerAdvice(basePackages = "top.liuchengyin.product.controller")
public class ExceptionControllerAdice {
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	 public R handleVaildException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
        // 获取错误结果
        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> errorMap = new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError)->{
            errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        // 返回错误信息
        return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
    }
}

这样,Controller层的代码就变得十分简略了:

@RequestMapping("/save")
public R save(@Valid @RequestBody Brand brand){
	brandService.save(brand);
	return R.ok();
}
二、分组校验

分组校验用于多场景的复杂校验,比如说我们在做新增功能的时候,这是不需要校验ID的(ID是自动生成的),但是做修改的时候就需要校验ID了(没有ID,就没办法判断修改的谁)。也就是说,给校验注解标注什么情况需要进行校验。

1、创建分组标识

分组标识是一个空的接口即可,只是用来标识是那种情况。这里创建两个标识表示:新增、修改。

// 修改分组标识
public interface UpdateGroup {

}

// 新增分组标识
public interface AddGroup {

}
2、在Bean中添加分组
// Brand对象
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
private Long brandId;      // 品牌ID

@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
private String name;	 // 品牌名

@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
private String logo;     // logo的url地址
3、在Controller方法中指定分组

注意:上面的Bean中,如果@NotBlank没有指定分组的话,那么在分组校验@Validated({AddGroup.class})的情况下是不会生效的,只会在@Validated生效,因此需要指定分组。即默认没有指定分组的校验注解在有分组注解Vlaidated指定分组时不会生效。

@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody Brand brand){
	brandService.save(brand);
	return R.ok();
}

@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class) @RequestBody Brand brand){
	brandService.updateDetail(brand);
	return R.ok();
}
三、自定义校验

上面的校验可能有时候满足不了我们的需求,因此我们也可以自己自定义一个校验注解,编写一个校验器进行关联。

1、引入依赖
<!-- 校验 -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
2、Bean上加入自定义校验注解
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateGroup.class})
private Integer showStatus;
3、编写自定义校验注解
@Documented
// 指定自定义校验器,可以指定多个类型的,使用逗号隔开
@Constraint(validatedBy = { ListValueConstraintValidator.class })   
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
	// 错误信息默认会从配置文件中获取即下面的top.xxxx的值(自定义的)
    String message() default "{top.liuchengyin.product.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };   // 对应注解中的vals
}
4、编写自定义校验器

自定义校验器,需要实现ConstraintValidator接口。

// ConstraintValidator<校验注解,类型>
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    private Set<Integer> set = new HashSet<>();
    // 初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        // 取出注解中的值,放入Set
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }

    }
    
    /**
     * 判断是否校验成功
     * @param value 需要校验的值
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值