前言:
validation让我们简化了开发过程,可以使用简单的一个注解就实现了很多常见的检验数据的功能,同时支持自定义注解。
org.springframework.boot
spring-boot-starter-validation
作用:检验数据
基本注解:
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) 验证注解的元素值长度在min和max区间内
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 验证注解的元素值在最小值和最大值之间
@Range(min=10000,max=50000,message="range.bean.wage")
@Valid 写在方法参数前,递归的对该对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
比如,原本验证是否为空我们是这么做:
if(ObjectUtils.isEmpty(mobile)||ObjectUtils.isEmpty(password)){
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
if(!MobileUtil.checkChinaMobile(mobile)){
return RespBean.error(RespBeanEnum.MOBILE_ERROR);
}
现在只需要在方法参数里为需要验证的参数加@Valid
,然后在参数类中给每个属性添加@NotNull注解即可。
像这样:
@RequestMapping(value = "/doLogin",method = RequestMethod.POST)
@ResponseBody
public RespBean doLogin(@Valid LoginUser loginUser){
...
}
@Data
public class LoginUser {
@NotNull
private String mobile;
@NotNull(message = "密码不能为空")
@Length(min = 32)
private String password;
}
大大简化了后续编写代码的步骤。
上面是对整个对象的所有属性进行校验,那么如果我们只想对某个传参比如
String bookId
的校验,该怎么写呢?
@Validated
@RestController
public class BookController {
@RequestMapping(value = "/book/info", method = RequestMethod.GET)
public Object getBookInfo(@NotBlank(message = "书籍ID不能为空") String bookId) {
return "SUCCESS";
}
}
必须在类上标明@Validated注解才会生效
首先你可以使用Crtl+B进入任意一个注解查看源代码,发现它们都有类似的结构,因此我们可以根据这些共同点,来开发我们自定义注解,下面以开发自定义验证是否是中国手机号来举例说明。
省略了包名和各种导包...
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { IsMobileVaildator.class })
public @interface IsMobile {
//下面是可选的4个参数,且提前定义了默认值
//required:是否是必填项,默认是true
boolean required() default true;
String message() default "手机号格式错误";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
下面是实现验证过程的类,实现该接口ConstraintValidator
第一个T代表你自定义接口的名字,第二个T代表的是该注解支持哪种对象的类型,这里是手机号自然就是String类型。
public class IsMobileVaildator implements ConstraintValidator<IsMobile,String> {
private boolean required =false;
/**
* 初始化方法
*
*/
@Override
public void initialize(IsMobile constraintAnnotation) {
//初始为true意思就是你加了此注解就是保证它是个手机号
required= constraintAnnotation.required();
}
/**
* 在该方法中写具体验证过程
* @param value object to validate
* @param context context in which the constraint is evaluated
*
* @return 是否是个中国手机号
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(required){
return MobileUtil.checkPhone(value);
}else {
//否则就是不是必填,2种情况,一种是没填返回true,一种是填了,既然填了就要验证,将验证的结果返回
if(ObjectUtils.isEmpty(value)){
return true;
}else{
return MobileUtil.checkPhone(value);
}
}
}
}
以上就是实现自定义注解的全部内容了,类似的身份证验证等等基本都是这样子来写。
前言:
当我们使用了注解后发现,如果数据是异常的,异常信息只会在控制台展示,而前端是没有任何有意义的东西展示出来的。如何将异常展示给前端,下面我们就来实现。
想要了解如何处理异常,就要了解发生异常后程序是怎么执行下去的。
@ControllerAdvice Controller增强器,该注解就是给控制器做统一处理、操作,可入参包名/具体类来控制Controller范围。 @ExceptionHandler(value = Exception.class) 该注解作用就是异常处理,使用@ResquetMapping的方法,发生异常都会进入@ExceptionHandler注解的方法,其实我们可以在单个Controller类中写一个方法加上@ExceptionHandler注解处理,但是每个Controller类还是会有冗余,所以搭配@ControllerAdvice注解就达到了全局统一处理的效果。
说人话就是在某个类上加@ControllerAdvice目的就是将所有控制器都交由该类管理
而在方法上加@ExceptionHandler意思是使用@ResquetMapping的方法发生异常都会被此方法处理。
所以这2个一般是绑定使用,起到全局处理效果。
需要你定义一个全局异常类GlobalException
这个类的作用就是用于提示各种异常信息
。其实也可以不建这个类,真正处理异常的类是带有**@RestControllerAdvice和@ExceptionHandler(Exception.class)**注解的类。
为什么要定义一个GlobalException
,目的是为了手动抛出异常时可以带respBeanEnum中的信息。其实仔细想想共3种可能的异常:
①抛出异常时你想带一些code和message,需要的时候你要手动写。
②校验失败,系统自动抛出校验失败异常。
③很普通的异常你也不想带什么特殊信息,直接报个系统异常的那种,系统排除不是上述2种类型后会自动以该种类型异常抛出。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException{
private RespBeanEnum respBeanEnum;
}
声明:
BindException:校验不通过抛出此异常,系统自动抛出。
GlobalException:校验都通过了,但可能是在你登录功能上密码比对错误你手动抛出的异常(普通异常)。比如:
if (user == null) { throw new GlobalException(RespBeanEnum.LOGIN_ERROR); } 抛出的这个对象就会被@ExceptionHandler标记的方法检测到,instanceof是判断类型是否是GlobalException类型,如果是就将exception转为GlobalException对象,调用该对象自带Get方法(globalException.getRespBeanEnum())就可以拿到RespBeanEnum.LOGIN_ERROR信息,再把它放到RespBean.error()中返回即可。 这也解释了为什么下面方法的返回值是RespBean类型。
//声明为异常处理器
@SuppressWarnings("all")
@RestControllerAdvice
public class GlobalExceptionHandler {
//进行具体异常分类处理
//拦截所以异常进行抓取 处理
@ExceptionHandler(Exception.class)
public RespBean exceptionHandle(Exception exception) {
//判断异常类型
if (exception instanceof GlobalException) {
//拦截的是自定义的异常
GlobalException globalException = (GlobalException) exception;
// 参数为 全局异常的枚举
return RespBean.error(globalException.getRespBeanEnum());
} else if (exception instanceof BindException) {
//拦截的是validator绑定的异常
BindException bindException = (BindException) exception;
//定义返回为绑定错误
RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
//设置数据为绑定消息
respBean.setMessage(
RespBeanEnum.BIND_ERROR.getMessage()
+ ":::" + bindException.getBindingResult()
.getAllErrors()
.get(0)
.getDefaultMessage()
);
return respBean;
}
//不属于那两个异常 就返回系统异常错误
return RespBean.error(RespBeanEnum.ERROR);
}
}
bindException.getBindingResult().getAllErrors().get(0).getDefaultMessage()
是获取校验异常message的固定写法,不必纠结,想了解可以打断点。
以上就是validation的全部重点内容了,不足之处肯定是有的,希望对你们有帮助。