## 1.为何需要自定义?
虽然JSR303和springboot-validator 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。
例如BrandEntity中的showStatus,用 0代表不显示 1代表显示,我们自定义一个校验注解@ListValue,指定取值只能0和1。
```
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
```
## 2.自定义校验步骤:
- 1、编写一个自定义的校验注解
- 2、编写一个自定义的校验器
- 3、关联自定义的校验器和自定义的校验注解
### 2.1创建注解
创建文件 `com/lcl/common/valid/ListValue.java`
```
package com.lcl.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 校验注解
*
* @description
* 可以参考 NotBlank 注解
*/
@Documented
// 会在下边定义一个校验器类 validatedBy = ListValueConstraintValidator.class
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// String message() default "{javax.validation.constraints.NotBlank.message}";
// 这里使用自定义的message,在resource下创建新的配置文件
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default {};
}
```
### 2.2创建自定义注解消息配置文件:
gulimall-common/src/main/resources/ValidationMessages.properties
```
com.atguigu.common.valid.ListValue.message=必须提交指定的值
```
### **2.3自定义注解说明:**
一个标注(annotation) 是通过@interface关键字来定义的. 这个标注中的属性是声明成类似方法 的样式的. 根据Bean Validation API 规范的要求:
- message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,通过 此属性来输出错误信息。
- groups 属性, 用于指定这个约束条件属于哪(些)个校验组. 这个的默认值必须是Class<?>类型数组。
- payload 属性, Bean Validation API 的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用。
除了这三个强制性要求的属性(message, groups 和 payload) 之外, 我们还添加了一个属性用来指定所要求的值. 此属性的名称vals在annotation的定义中比较特殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称。
另外, 我们还给这个annotation标注了一些元标注( metaannotatioins):
- @Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者 annotation声明上。
- @Retention(RUNTIME): 表示这个标注信息是在运行期通过反射被读取的.
- @Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪个校验器(类) 去校验使用了此标注的元素.
- @Documented: 表示在对使用了该注解的类进行javadoc操作到时候, 这个标注会被添加到javadoc当中.
### 2.4创建校验器
com/lcl/common/valid/ListValueConstraintValidator.java
```
package com.lcl.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* 约束校验器
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<Integer>();
/**
* 初始化方法
*
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判断是否校验成功
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
```
ListValueConstraintValidator定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在我们的例子中即ListValue), 第二个这个校验器所支持到被校验元素的类型 (即Integer)。
如果一个约束标注支持多种类型的被校验元素的话, 那么需要为每个所支持的类型定义一个ConstraintValidator,并且注册到约束标注中。
这个验证器的实现就很平常了, `initialize()`方法传进来一个所要验证的标注类型的实例, 在本例中, 我们通过此实例来获取其vals属性的值,并将其保存为Set集合中供下一步使用。
`isValid()`是实现真正的校验逻辑的地方, 判断一个给定的int对于@ListValue这个约束条件来说是否是合法的。
### 2.5在参数对象中为 `showStatus` 属性使用 `@ListValue` 注解。
`com/atguigu/gulimall/product/entity/BrandEntity.java`
```
package com.atguigu.gulimall.product.entity;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.group.AddGroup;
import com.atguigu.common.valid.group.UpdateGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
/**
* 品牌
*
* @author kaiyi
* @email corwienwong@gmail.com
* @date 2020-08-10 15:45:20
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotEmpty(message = "URL地址不能为空", groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址", groups = {AddGroup.class, UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
//@Pattern()
// 使用自定义校验注解
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class})
private Integer sort;
}
```
### 2.6最后,在Postman中测试一下,自定义的注解是否起作用,重启服务: