JSR-Java Specification Requests,是一个标准化技术规范的正式请求,如@Qualifier、@Inject、@Resource、参数校验bean validation等都被纳入为JSR的一部分。本文的主题是validation的一些细节问题(项目测试时发现)、如何自定义bean validation与个人使用Swagger2测试时遇到的一些坑。
1.validation细节
A.仅使用@Pattern、@Past、@Future一些注解而不使用@NotNull时若传值为null也不会报错。
B.日期传值可以是一串无需格式的数字,这是会根据long类型数字转日期处理(从1970年算起,具体可以百度或在接口输出传一串数字后接收到的日期,个人输入32132121后台日期sout的是Thu Jan 01 16:55:32 CST 1970),传空字符串时则为null。
C.@Pattern使用"||"与"|"的区别与java的Pattern的区别相同,|匹配空字符串""为false,||匹配""为true,具体如下图:
2.自定义validation(该demo地址在文末,含1个String与Enum自定义校验各一个,故会省略JSON配置、VO等一些非主题代码)
当Bean Validation不能满足自己的请求验证时就可能有自定义Validator的必要,但Vaidator的局限性是很明显的。Java目前支持的注解返回值包括基本类型、基本类型数组、枚举、Class、注解及它们的数组,而无法返回接口、具体类,而且枚举虽然能实现接口,但不能继承自定义枚举(所有枚举默认继承Enum),这也导致自定义Validator的局限性-------以上皆是因为接触的一个项目把所有常量放在接口中打算写自定义验证器时发现的问题,现在只能改用@Pattern,但也了解了自定义Validator的机制。
自定义validation需定义一个注解及该注解的验证器(注解不太了解的建议先去了解一些注解基础)。
自定义注解(通过validatedBy指定校验器)
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = {WorkerValidator.class}) public @interface WorkerAnnotation { /** * @return the error message template */ String message() default "参数错误"; /** * @return the groups the constraint belongs to */ Class<?>[] groups() default {}; /** * @return the payload associated to the constraint */ Class<? extends Payload>[] payload() default {}; WorkerEnum target(); }
自定义校验器(泛型指定了该校验器校验被WorkerAnnotation注解的字段,接收的参数为String类型)
枚举WorkerEnum(为了扩大枚举的可用性枚举中添加了一个List属性,为了校验方便在枚举中添加了一个check方法):public class WorkerValidator implements ConstraintValidator<WorkerAnnotation,String> { private WorkerEnum allEnum; @Override //验证前获取WorkerAnnotation注解中的方法值并根据需要进行初始化 public void initialize(WorkerAnnotation constraintAnnotation) { // String msg = constraintAnnotation.message(); allEnum = constraintAnnotation.target(); } /** * * @param value 需要校验的对象,该处验证的类型是字符串,当然也可以自定义 * @param context 约束验证上下文 * disableDefaultConstraintViolation():用于使默认ConstraintViolation失效是的能够设置不同的违反信息或生成一个基于不同属性的 * ConstraintViolation * getDefaultConstraintMessageTemplate():获取默认的约束信息模板 * buildConstraintViolationWithTemplate(String messageTemplate):新建一个ConstraintViolation与信息模板,需在disableDefaultConstraintViolation() * 后调用 * @return 校验通过返回true,否则返回false */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { /*if(StringUtils.isEmpty(value)){ context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate("工作者参数不能为null") // .addPropertyNode("status") 该方法用于针对校验类中的属性,如value类型是一个User对象,则为user.status属性添加非空约束,失败信息为工作者参数不能为null .addConstraintViolation(); return false; }*/ return allEnum.check(value); } }
public enum WorkerEnum { STATUS(Worker.STATUS), SEX( Worker.SEX); public List<String> list; WorkerEnum( List<String> list) { this.list = list; } public boolean check(String name) { return this.list.contains(name); } }常量存放接口Woeker(名字随便取的,接口中的属性默认pubic static final,十分方便于存放常量)
public interface Worker { List<String> STATUS = Lists.newArrayList("ENABLE", "UNABLE"); List<String> SEX = Lists.newArrayList("FEMALE", "MALE"); }
Bean Validation中的校验注解都必须有Class<?>[] groups() default{} 与 Class<? extends Payload>[]() default{}这2个方法,其中groups用于指定哪个类传入该vo时需校验该注解标注的字段,如PeopleVO中@NotNull和@Pattern,表示只有WorkVO中的方法含有对PeopleVO的校验时才对name这个字段进行正则和非空校验,而在其他类中则无需校验,范例如下2图
@ApiModel public class PeopleVO { @NotNull(groups = {WorkerVO.class}) @Pattern(regexp = "AAA|BBB",groups = {WorkerVO.class}) @ApiModelProperty("姓名") private String name; @SexEnumAnnotation(value = {"MALE","FEMALE"}) private String sex; @Past private Date date; public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
如下为Application中含people接口参数的校验
Application:
当使用swagger对worker校验出错时将返回如下信息(看起来不太友好,下一篇将设置全局验证的返回信息):@SpringBootApplication(scanBasePackages = "per.wilson.validation") @RestController public class Application implements BaseController { public String people(@Validated @RequestBody PeopleVO vo) { return "success"; } @Override public String uncustom(@Valid @RequestBody UncustomVO vo) { return "success"; } @Override public String worker(@PathVariable("id") Long id, @Valid @RequestBody WorkerVO vo) { return id + ":" + vo.getSex(); } @Override public String name(@RequestParam String name, @RequestBody WorkerVO vo) { return name + ":" + vo.getSex(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3.Swagger注解的一些坑
@ApiImplicitParams({@ApiImplicitParam(name = "id", value = "主键", required = true, dataType = "long", paramType = "path"),
@ApiImplicitParam(name = "vo", value = "职员信息", required = true, dataType = "WorkerVO", paramType = "body")})
当使用@ApiImplicitParam标注VO时,若VO标注了@ApiModel且其中含值如@ApiModel("职员VO")时,SwaggerUI将变成这样:
接触不多的可能没发现什么问题(虽然可能不影响传参),但对比把@ApiModel的value去掉后效果图:
很明显很坑!
@PostMapping("/worker/name") @ApiOperation("参数为普通类型与VO") @ApiImplicitParams({/*@ApiImplicitParam(name = "name", value = "姓名", required = true, dataType = "String", paramType = "form"),*/ @ApiImplicitParam(required = true, name = "vo", value = "职员信息", dataType = "WorkerVO", paramType = "body")}) String name(@ApiParam(name = "name", value = "姓名", required = true) @RequestParam String name,@RequestBody WorkerVO vo);虽然可以使用@ApiParam来代替@ApiImplicitParams的功能,但一个个注解完参数后IDE生成的实现类中的方法参数前会带上这些注解,显得代码有点冗余,所以个人还是倾向在接口中没有body和form的混合参数则全用 @ApiImplicitParams,若有混合则混合使用@ApiImplicitParams和@ApiParam