自定義JSR validation及Swagger的一些坑
地址:https://blog.csdn.net/z28126308/article/details/77748798
http://sishuok.com/forum/blogPost/list/7798.html
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。
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註解的一些坑
Swagger有不少坑,其中以下@ApiImplicitParams佔了不少。
@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去掉後效果圖:
很明顯很坑!
還有當使用@ApiImplicitParams主鍵時若引數中既含@RequestParam又含@RequestBody引數如(@RequestParam Long id,@RequestBody Worker vo)將導致接受不了@RequestParam引數而返回(僅僅是Swagger接收不到,不用Swagger是都能接收到的)。此時需把@ApiImplicitParams中@RequestParam @ApiImplicitParam註解去掉,在@RequestParam引數前使用@ApiParam代替@ApiImplicitParam的作用,如:
@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
BaseController:
@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); }
感覺廢話比較多,可能是無關緊要的細節多了點。