经典语录:SpringBoot
是为了简化 Spring
应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程
主要内容:
javax.validation
包和hibernate-validator
验证包中常用注解及作用普通参数属性与对象参数属性的验证方法
自定义注解的编写方式
利用一个验证类实现多个接口之间不同规则的验证
对于任何一个应用而言,客户端做的数据有效性验证都不是安全有效的,而数据验证又是一个企业级项目架构上最为基础的功能模块
,这时候就要求我们在服务端接收到数据的时候也对数据的有效性进行验证。
以前的验证方式
public String test1(String name) {
if (name == null) {
throw new NullPointerException("name 不能为空");
}
if (name.length() < 2 || name.length() > 10) {
throw new RuntimeException("name 长度必须在 2 - 10 之间");
}
return "success";
}
上面这段代码就是对参数进行有效性校验,但仔细观察的话就会发现;随着参数的增加,格式的变化,校验数据有效性的代码愈发的繁琐杂乱,一点都不轻松
这里只列举了 javax.validation
包下的注解,同理在 spring-boot-starter-web
包中也存在 hibernate-validator
验证包,里面包含了一些 javax.validation
没有的注解
注解 | 说明 |
---|---|
@NotNull |
限制必须不为null |
@NotEmpty |
验证注解的元素值不为 null 且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank |
验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@Pattern(value) |
限制必须符合指定的正则表达式 |
@Size(max,min) |
限制字符长度必须在 min 到 max 之间(也可以用在集合上) |
@Email |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
@Max(value) |
限制必须为一个不大于指定值的数字 |
@Min(value) |
限制必须为一个不小于指定值的数字 |
@DecimalMax(value) |
限制必须为一个不大于指定值的数字 |
@DecimalMin(value) |
限制必须为一个不小于指定值的数字 |
@Null |
限制只能为null(很少用) |
@AssertFalse |
限制必须为false (很少用) |
@AssertTrue |
限制必须为true (很少用) |
@Past |
限制必须是一个过去的日期 |
@Future |
限制必须是一个将来的日期 |
@Digits(integer,fraction) |
限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction (很少用) |
属性验证对象
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
对象属性类
*/
public class Book {
private Integer id;
@NotBlank(message = "name 不允许为空")
@Length(min = 2, max = 10, message = "name 长度必须在 {min} - {max} 之间")
private String name;
@NotNull(message = "price 不允许为空")
@DecimalMin(value = "0.1", message = "价格不能低于 {value}")
private BigDecimal price;
// 省略 GET SET ...
}
控制层添加验证后的代码(同理可以应用在其它层,但是一般用不着)
import com.battcn.pojo.Book;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;
/**
* 控制层参数校验
*/
@Validated
@RestController
public class ValidateController {
@GetMapping("/test2")
public String test2(@NotBlank(message = "name 不能为空") @Length(min = 2, max = 10, message = "name 长度必须在 {min} - {max} 之间") String name) {
return "success";
}
@GetMapping("/test3")
public String test3(@Validated Book book) {
return "success";
}
}
注解解释:
@Validated:
开启数据有效性校验,添加在类上即为验证方法,添加在方法参数中即为验证参数对象。(添加在方法上无效)@NotBlank:
被注释的字符串不允许为空(value.trim() > 0 ? true : false
)@Length:
被注释的字符串的大小必须在指定的范围内@NotNull:
被注释的字段不允许为空(value != null ? true : false
)@DecimalMin:
被注释的字段必须大于或等于指定的数值
开始测试
第一步:配置全局异常处理
测试普通参数属性
测试对象参数异常结果
对异常信息进行封装
自定义 Validator 注解
当系统自带的注解无法满足我们的要求时候应该咋办呢?这就需要我们的自定义 Validator 注解出现了。
javax.validation
包与 hibernate-validator
包中存在的注解几乎可以满足大部分的要求,又拥有基于正则表达式的@Pattern
,为什么还需要自己去定义呢?原因如下
- 正则效率不高
- 正则可读性不好
- 正则门槛较高,很多开发者并不会编写正则表达式
@DateTime
注解这里定义了一个 @DateTime
注解,在该注解上标注了 @Constraint
注解,它的作用就是指定一个具体的校验器类
import com.battcn.validator.DateTimeValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
自定义时间检验的注解
*/
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = DateTimeValidator.class)
public @interface DateTime {
String message() default "格式错误";
String format() default "yyyy-MM-dd";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
其中
message:
验证失败提示的消息内容groups:
为约束指定验证组
DateTimeValidator
定义校验器类 DateTimeValidator
实现 ConstraintValidator
接口,实现接口后需要实现它里的 initialize:
与 isValid:
方法。
package com.battcn.validator;
import com.battcn.annotation.DateTime;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* 日期格式验证
*/
public class DateTimeValidator implements ConstraintValidator {
private DateTime dateTime;
@Override
public void initialize(DateTime dateTime) {
this.dateTime = dateTime;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果 value 为空则不进行格式验证,为空验证可以使用 @NotBlank @NotNull @NotEmpty 等注解来进行控制,职责分离
if (value == null) {
return true;
}
String format = dateTime.format();
if (value.length() != format.length()) {
return false;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
simpleDateFormat.parse(value);
} catch (ParseException e) {
return false;
}
return true;
}
}
其中
initialize:
主要用于初始化,它可以获得当前注解的所有属性isValid:
进行约束验证的主体方法,其中value
就是验证参数的具体实例,context
代表约束执行的上下文环境。这里的验证方式虽然简单,但职责明确;为空验证可以使用
@NotBlank
、@NotNull
、@NotEmpty
等注解来进行控制,而不是在一个注解中做各种各样的规则判断,应该职责分离
控制层代码
import com.battcn.annotation.DateTime;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 参数校验
*/
@Validated
@RestController
public class ValidateController {
@GetMapping("/test")
public String test(@DateTime(message = "您输入的格式错误,正确的格式为:{format}", format = "yyyy-MM-dd HH:mm") String date) {
return "success";
}
}
有的时候,我们对一个实体类需要有多中验证方式,在不同的情况下使用不同验证方式,比如说对于一个实体类来的 id 来说,新增的时候是不需要的,对于更新时是必须的,这个时候你是选择写一个实体类呢还是写两个呢?
定义一个验证组,里面写上不同的空接口类即可
/**
* 验证组
*/
public class Groups {
public interface Update {
}
public interface Default {
}
}
groups
属性的作用就让 @Validated
注解只验证与自身 value 属性相匹配的字段,可多个,只要满足就会去纳入验证范围;我们都知道针对新增的数据我们并不需要验证 ID 是否存在,我们只在做修改操作的时候需要用到,因此这里将 ID 字段归纳到 Groups.Update.class
中去,而其它字段是不论新增还是修改都需要用到所以归纳到 Groups.Default.class
中…
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* @author Levin
* @since 2018/6/7 0005
*/
public class Book {
@NotNull(message = "id 不能为空", groups = Groups.Update.class)
private Integer id;
@NotBlank(message = "name 不允许为空", groups = Groups.Default.class)
private String name;
@NotNull(message = "price 不允许为空", groups = Groups.Default.class)
private BigDecimal price;
// 省略 GET SET ...
}
创建一个 ValidateController
类,然后定义好 insert
、update
俩个方法,比由于 insert
方法并不关心 ID 字段,所以这里 @Validated
的 value 属性写成 Groups.Default.class
就可以了;而 update
方法需要去验证 ID 是否为空,所以此处 @Validated
注解的 value 属性值就要写成 Groups.Default.class, Groups.Update.class
;代表只要是这分组下的都需要进行数据有效性校验操作
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 参数校验
*/
@RestController
public class ValidateController {
@GetMapping("/insert")
public String insert(@Validated(value = Groups.Default.class) Book book) {
return "insert";
}
@GetMapping("/update")
public String update(@Validated(value = {Groups.Default.class, Groups.Update.class}) Book book) {
return "update";
}
}