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類型)
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); } }枚舉WorkerEnum(爲了擴大枚舉的可用性枚舉中添加了一個List屬性,爲了校驗方便在枚舉中添加了一個check方法):
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:
@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); } }當使用swagger對worker校驗出錯時將返回如下信息(看起來不太友好,下一篇將設置全局驗證的返回信息):
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
@RequestMapping("/test")
public interface BaseController {
@PostMapping(value = "/people")
@ApiOperation("參數僅混合實體")
@ApiImplicitParams({@ApiImplicitParam(name = "vo", value = "混合實體", required = true, dataType = "PeopleVO", paramType = "body")})
String people(@Validated @RequestBody PeopleVO vo);
@PostMapping("/uncustom")
@ApiOperation("參數僅自定義Validation實體")
@ApiImplicitParams({@ApiImplicitParam(name = "vo", value = "測試實體", required = true, dataType = "UncustomVO", paramType = "body")})
String uncustom(@RequestBody UncustomVO vo);
@PostMapping("/worker/{id}")
@ApiOperation("參數爲普通類型與VO")
@ApiImplicitParams({@ApiImplicitParam(name = "id", value = "主鍵", required = true, dataType = "long", paramType = "path"),
@ApiImplicitParam(name = "vo", value = "職員信息", required = true, dataType = "WorkerVO", paramType = "body")})
String worker(@PathVariable("id") Long id, @RequestBody WorkerVO vo);
@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);
}
感覺廢話比較多,可能是無關緊要的細節多了點。