最近在工做中遇到寫一些API,這些API的請求參數很是多,嵌套也很是複雜,若是參數的校驗代碼所有都手動去實現,寫起來真的很是痛苦。正好Spring輪子裏面有一個Validation,這裏記錄一下怎麼使用,以及怎麼自定義它的返回結果。
Bean Validation是Java中的一項標準,它經過一些註解表達了對實體的限制規則。經過提出了一些API和擴展性的規範,這個規範是沒有提供具體實現的,但願可以Constrain once, validate everywhere。如今它已經發展到了2.0,兼容Java8。java
hibernate validation實現了Bean Validation標準,裏面還增長了一些註解,在程序中引入它咱們就能夠直接使用。spring
Spring MVC也支持Bean Validation,它對hibernate validation進行了二次封裝,添加了自動校驗,並將校驗信息封裝進了特定的BindingResult類中,在SpringBoot中咱們能夠添加implementation('org.springframework.boot:spring-boot-starter-validation')引入這個庫,實現對bean的校驗功能。編程
gradle dependencies以下:框架
定義一個示例的Bean,例以下面的User.java。spring-boot
在name屬性上,添加@NotBlank和@Size(max=10)的註解,表示User對象的name屬性不能爲字符串且長度不能超過10個字符。測試
而後咱們暫時不添加任何多餘的代碼,直接寫一個UserController對外提供一個RESTful的GET接口,注意接口的參數用到了@Validated註解。gradle
啓動SpringBoot程序,發一個測試請求看一下:spa
http://127.0.0.1:8080/validation/get?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahibernate
返回的結果是,注意此時的HTTP STATUS CODE = 400: 3d
此時已經能夠實現參數的校驗了,可是返回的結果不太友好,下面看一下怎麼定製返回的消息。在定製返回結果前,先看下一下內置的校驗註解有哪些,在這裏我不一個個去貼了,寫代碼的時候根據須要進入到源碼裏面去看便可。
早期Spring版本中,都是在Controller的方法中添加Errors/BindingResult參數,由Spring注入Errors/BindingResult對象,再在Controller中手寫校驗邏輯實現校驗。新版本提供註解的方式(Controller上面bean加一個@Validated註解),將校驗邏輯和Controller分離。
3.1 自定義註解
顯然除了自帶的NotNull、NotBlank、Size等註解,實際業務上還會須要特定的校驗規則。
假設咱們有一個參數address,必須以Beijing開頭,那咱們能夠定義一個註解和一個自定義的Validator。
而後在User.java中增長一個address屬性,並給它加上上面這個自定義的註解,這裏咱們定義了一個能夠傳入start參數的註解,表示應該以什麼開頭。
除了定義能夠做用於屬性的註解外,其實還能夠定義做用於class的註解(@Target({TYPE})),用於校驗class的實例。
3.2 自定義Validator
第一步,實現一個Validator。(這種方法不須要咱們的bean裏面有任何註解之類的東西)
第二步,修改Controller代碼,注入上面的UserValidator實例,並給Controller的方法參數加上@Validated註解,便可完成和前面自定義註解同樣的校驗功能。
這個方法和自定義註解的區別在於不須要在Bean裏面添加註解,而且能夠更加靈活的把一個Bean裏面全部的Field的校驗代碼都搬到一塊兒,而不是每個屬性都去加註解,若是校驗的屬性很是多,且默認註解的能力又不夠的話,這種方式也是不錯的,能夠避免大量的自定義註解。
3.3 以編程的方式校驗(手動)
這種方式能夠算是原始的Hibernate-Validation的方式。直接看代碼,這裏有一個比較不一樣的是,可使用Hibernate-Validation的Fail fast mode。由於前面的方式,都將全部的參數都驗證完了,再把錯誤返回。有時咱們但願遇到一個參數錯誤,就當即返回。
設置fast-fail爲true能夠達到這個目的。不過貌似不能再用@Validated註解方法參數了,而是要用ValidatorFactory建立Validator。
在實際開發中,沒必要每次都編寫代碼建立Validator,能夠採用@Configuration的方式建立,而後再@Autowired注入到每一個須要使用Validator的Controller當中。
3.4 定義分組校驗
有的時候,咱們會有兩個不一樣的接口,可是會使用到同一個Bean來做爲VO(意思是兩個接口的URI不一樣,但參數中都用到了同一個Bean)。而在不一樣的接口上,對Bean的校驗需求可能不同,好比接口2須要校驗studentId,而接口1不須要。那麼此時就能夠用到校驗註解的分組groups。
到這裏,也能夠帶一嘴Valid和Validated註解的區別,其代碼註釋寫着後者是對前者的一個擴展,支持了group分組的功能。
3.5 定製返回碼和消息
第二節中定義了一個ServiceResponse,其實做爲一個開放的API,不論用戶傳入任何參數,返回的結果都應該是預先定義好的格式,而且能夠寫明在接口文檔中,即便發生了校驗失敗,應該返回一個包含錯誤碼code(發生錯誤時通常大於0)和message字段。
的結果,而HTTP STATUS CODE一直都是200。
爲了實現這個目的,咱們加一個全局異常處理方法。
在上面的方法中,咱們處理了BindException(非請求body參數,例如@RequestParam接收的)和MethodArgumentNotValidException(請求body裏面的參數,例如@RequestBody接收的),這兩類Exception裏面都有一個BindingResult對象,它裏面有一個包裝成FieldError的List,保存着Bean對象出現錯誤的Field等信息。
取出它裏面defaultMessage,放到統一的ServiceResponse返回便可實現返回碼和消息的定製。因爲消息內容是有註解默認的DefaultMessage決定的,爲了按照自定義的描述返回,在Bean對象的註解上須要手動賦值爲但願返回的消息內容。
這樣當name參數長度超過10時,就會返回
這裏的FieldError fieldError = ex.getFieldError();只會隨機返回一個出錯的屬性,若是Bean對象的多個屬性都出錯了,能夠調用ex.getFieldErrors()來得到,這裏也能夠看到Spring Validation在參數校驗時不會在第一次碰到參數錯誤時就返回,而是會校驗完成全部的參數。
若是不想手動編程去校驗,那麼這裏能夠只讀取一個隨機的FieldError,返回它的錯誤消息便可。
3.6 更加細緻的返回碼和消息
其實還有一種比較典型的自定義返回,就是錯誤碼(code)和消息(message)是一一對應的,好比:
這種狀況比較特殊,通常當參數錯誤的時候,會返回一個總體的參數錯誤的錯誤碼,而後攜帶參數的錯誤信息。但有時,業務上就要不一樣的參數錯誤,既要錯誤碼不一樣,錯誤信息也要不一樣。我想了下,有兩種思路。
其實在實際的工做中,確定還有更復雜的校驗邏輯,可是不必定非要都用框架去實現,框架裏面的實現(好比註解)應該是一個比較簡單通用的校驗,可以達到複用,減小重複的勞動。而更加複雜的邏輯校驗,必定是存在具體業務當中的,最好是在業務代碼裏面實現。
還有一點須要注意,Spring Validation的isValid方法,若是返回false,那麼Controller再也不會被調用,而是直接返回。若是你在Controller上面加了AOP進行接口調用統計的話,可能會漏掉。這個時候,咱們不該該讓Controller不調用,建議這種狀況在AOP裏面對Controller的參數切面進行校驗後,拋出統一的業務異常。