JSR303校验,分组校验,自定义注解校验,全局异常处理

一 前言.

   在我们接收从网页传过来的数据时,首要的就是要对其进行校验,避免垃圾数据和保障系统安全,这里推荐一种注解校验方式,也就是JSR303.

JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation
而所谓JSR,也就是一个规范.
JSR-303主要有以下注解:

在这里插入图片描述

二.使用入门步骤.

  1. 在实体类字段上添加对应的注解校验,并自定义返回信息
    举例:
  /**
		 * 品牌名,必须包含非空格字符
		 */
		@NotBlank(message = "品牌名不能为空")
		private String name;
		
		
		@NotBlank
		@URL(message = "logo必须为url格式")
		private String logo;
		
		/**
		 * 检索首字母,我们自己自定义规则
		 */
		@NotBlank
		@Pattern(regexp = "[a-zA-Z]",message = "检索首字母必须有且只有一个英文字母")
		private String firstLetter;

  1. 在controller中的方法请求体中增加@Valid注解,即可对brand对象进行校验
     @RequestMapping("/save")
     public R save(@Valid @RequestBody BrandEntity brand){
     }
  1. 完善返回校验信息.
    默认的返回错误信息不是我们想要的,会把异常返回出去, 正常我们应该封装统一的返回对象,这里就是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();
			}

		}

三.全局异常处理.

  1. 使用原因.
         在之前我们通过JSR完成对指定参数的校验,在方法中封装了对异常的处理,但是这种方式很麻烦,需要在每个方法中都写一遍,且内容都差不多一样.
          这里Spring MVC还提供了一种全局处理的方式,让我们方便的处理异常.其实就是ControllerAdvice.

  2. 使用步骤.
    (1). 新建一个全局异常类.类上增加注解
         @ControllerAdvice(“需要处理的类的路径”).
         注意:由于一般我们需要返回JSON格式,所以就直接换成这个注解,@RestControllerAdvice,它是@ControllerAdvice和@ResponseBody的结合.
    (2).在该类中新建方法,方法上增加注解@ExceptionHander(“需要处理的异常类”)
         方法体中写上对异常的处理即可.

  3. 原理(猜测)
         解析@ControllerAdvice注解中设置的路径,对于这个路径下的所有的对象都进行代理,当代理执行方法异常的时候,进行异常的捕获.
    捕获后判断异常类型是否与@ExceptionHander注解中的异常对象相等.相等则执行对应方法.

  4. 样例.
    统一异常处理类:

    @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();
    }
   

四.分组校验.

  1. 使用原因.
        在我们新加或者更新操作的时候,对于同一个对象的校验值是不一样的,比如说新增的时候不能有数据id,更新的时候一定要有数据id.这个时候我们就需要用到分组校验的功能.
        其实本质就是给校验注解进行分组,然后在校验时选择使用哪一个分组.

  2. 使用步骤.
    (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();
		}

注意:如果使用了分组校验,但是某些属性校验并没有明确的指定分组,是不起作用的.

五.自定义注解校验.

  1. 使用原因.
       之前我们说没有我们想要的注解的话,可以使用@Pattern注解,里面给定正则,就可以使用
        那有时候我们有其他的通用的规则,不用每次都写一个正则,可以直接编写一个自定义注解.
        这个注解其实是使用的JSR规范,我们不需要去通过反射什么使用这个,只需要利用它的规范,实现接口即可.

  2. 举例.
        这里我们编写一个ListValue注解,这个注解的作用是校验必须为我们指定的值,否则校验失败,像这样.

   @ListValue({0,1})
   private Integer value;

        那这里的value值就必须为0或者1,否则不得行.

  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,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员bling

义父,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值