hibernate-validator中有很多非常简单好用的校验注解,例如NotNull,@NotEmpty,@Min,@Max,@Email,@PositiveOrZero等等。这些注解能解决我们大部分的数据校验问题。如下所示:
package com.nobody.dto;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class UserDTO {
@NotBlank(message = "姓名不能为空")
private String name;
@Min(value = 18, message = "年龄不能小于18")
private int age;
@NotEmpty(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}
二 自定义参数校验器
但是,hibernate-validator中的这些注解不一定能满足我们全部的需求,我们想校验的逻辑比这复杂。所以,我们可以自定义自己的参数校验器。
首先引入依赖是必不可少的。
在springBoot中可以直接引用
org.springframework.boot spring-boot-starter-validation
这里面正是包含了我们真正需要的hibernate-validator依赖
org.hibernate.validator hibernate-validator 6.2.0.Final compile
向他提供的注解一样我们需要自定义自己的注解。
再建一个validator包来放我们的自定义注解类
package com.zry.seckill.validator;
import com.zry.seckill.vo.IsMobilevalidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RUNTIME)
@documented
@Constraint(validatedBy = {IsMobilevalidator.class})//指定我们的校验类,这里面实现了我们需要的具体校验方法
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
接下来我们完成校验类 它实现ConstraintValidator接口
实现里面的方法。
package com.zry.seckill.vo; import com.zry.seckill.utils.ValidatorUtil; import com.zry.seckill.validator.IsMobile; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class IsMobilevalidator implements ConstraintValidator{ private boolean required = false;// 是否强制校验 @Override public void initialize(IsMobile constraintAnnotation) { this.required = constraintAnnotation.required(); } @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { //我这里调用了自定义的工具类 return ValidatorUtil.isMobile(value); } }
最后我们需要在Controller层加上 @Valid 注解才能启用注解校验。
当然还有另一种@Validated,和@Valid有所不同,这里不做详述。
Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s
JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。
通过以上几个步骤,我们自定义的校验注解就完成了。
如果参数校验不通过,会抛出MethodArgumentNotValidException异常,我们全局处理下然后返回给接口。
SpringBoot全局处理异常 为什么要处理异常?在日常开发中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,且不美观,不利于后期维护。
为解决该问题,我们将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端的String、Map、JSONObject、ModelAndView等结果类型。
我们涉及到 这三个注解。
- @RestControllerAdvice
- @ControllerAdvice + @ExceptionHandler
@RestControllerAdvice与@ControllerAdvice的区别对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller
Advice,关于Advice,在Spring的AOP中,是用来封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ControllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行切面环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:1.结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
2.结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。
3.结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行。
@RestControllerAdvice与@ControllerAdvice的区别就和@RestController与@Controller的区别类似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。
(参考https://www.pianshen.com/article/6221932106/)
@RestControllerAdvice源码中有@ControllerAdvice注解和@ResponseBody注解,当自定义类加@RestControllerAdvice注解时,方法自动返回json数据,每个方法无需再添加@ResponseBody注解
我们这次提供的方案是@RestControllerAdvice + @ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
实现步骤我们先建一个excepttion包来放我们的全局异常类。
package com.zry.seckill.exception;
import com.zry.seckill.vo.RespBeanEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException{
private RespBeanEnum respBeanEnum;
}
自定义全局异常处理
package com.zry.seckill.exception;
import com.zry.seckill.vo.RespBean;
import com.zry.seckill.vo.RespBeanEnum;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)//处理哪些异常
public RespBean ExceptionHandler(Exception e){
if(e instanceof GlobalException){//如果是咱们之前自定义的异常
GlobalException ex = (GlobalException) e;
return RespBean.error(ex.getRespBeanEnum());
}else if(e instanceof BindException){ //如果异常是绑定异常(比如没有通过参数校验注解抛出的异常)
BindException be = (BindException) e;
RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
respBean.setMessage("参数校验异常" + be.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return respBean;
}
return RespBean.error(RespBeanEnum.ERROR);
}
}
这里我们就已经实现了对Controller层的全局异常处理。
下面是对上面方法中出现RespBeanEnum和RespBean的一些说明
GlobalExceptionHandler() 类中都是我们自己编写的处理异常的方法。
我这里的ExceptionHandler()方法只是个例子。
其中RespBeanEnum是我自定义的枚举类。它主要是为RespBean类服务。RespBean相当于统一返回实体类
用来返回结果信息。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
private long code;
private String message;
private Object obj;
public static RespBean success(){
return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),null);
}
public static RespBean success(Object obj){
return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),obj);
}
public static RespBean error(RespBeanEnum respBeanEnum){
return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),null);
}
public static RespBean error(RespBeanEnum respBeanEnum,Object obj){
return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),obj);
}
}



