SpringMVC整合Hibernate Validator進行註解式的參數校正
——讓代碼更少、更加專註於商務邏輯
1 問題背景:
參數驗證是一個常見的問題,例如驗證使用者輸入的密碼是否為空白、郵箱是否合法等。但是無論是前端還是後台,都需對使用者輸入進行驗證,以此來保證系統資料的正確性。對於web來說,有些人可能理所當然的想在前端驗證就行了,但這樣是非常錯誤的做法,前台的驗證一般是通過Javascript,js代碼是可以被禁用和篡改的,所以相對後台檢驗而言,安全性會低一些。前端代碼對於使用者來說是透明的,稍微有點技術的人就可以繞過這個驗證,直接提交資料到後台。無論是前端網頁提交的介面,還是提供給外部的介面,參數驗證隨處可見,也是必不可少的。總之,一切使用者的輸入都是不可信的。
基於這樣的常識,在後端的代碼開發中,參數校正是一個永遠也繞不開的話題。然而編寫參數校正代碼的過程是一個技術含量不高,及其耗時,繁瑣的過程。比如極大多數的參數校正代碼就是:判斷一下使用者名稱是否已經存在、使用者名稱長度是否滿足要求、使用者填寫的郵箱是否合法。年齡參數是不是一個整數等等。為了減少重複代碼的開發,許多有技術積澱的公司,一般都會提供一套或多套特有的參數校正工具類。以此減少代碼量。這種做法在項目中非常普遍,有了這些工具類庫,程式員在編寫業務代碼的過程中,工作效率可以大大提升。
然而,即便聰明如上述做法,我們的商務邏輯中依然還是可以見到大量的參數校正邏輯,倘若項目架構的不夠合理,這些與參數校正邏輯有關的代碼量會更多,真是XXX的裹腳布,又臭又長。大量繁雜的參數校正代碼混雜在商務邏輯中,一來降低了代碼的可讀性;二是讓人對業務本身的理解難度加大,很多剛加入公司的人往往是通過讀碼來理解業務的,公司的一些業務培訓一般只能講個大概,然而在閱讀代碼的過程中稍有不慎便會被這些“額外”的代碼擾亂思路,對於新手,那更是異常噩夢,倘若老員工辭職了,撒手拋給新來的,這樣的項目能不能維護下去都是個問題了。 2 Hibernate Validator介紹:
不過對於java開發人員來說,解決上述難題越來越容易了。隨著大量的Bean Validation 架構的問世,和主流架構對這些校正架構的整合和支援。 我們是非常容易的能將商務邏輯和參數校正代碼相分離的,其實說是分離還不夠恰當,這裡即將要介紹的hibernate Validator可以以註解的方式對參數進行校正,商務邏輯中的極大多數參數校正代碼都可以省去,可以使代碼更簡潔,更加專註於商務邏輯。hibernate Validator是 Bean Validation 的參考實現,Hibernate Validator 提供了 JSR 303 規範中所有內建 constraint 的實現,除此之外還有一些附加的 constraint。為了滿足特定的需求,使用者還可以自實現更多的constraint以便滿足特定的需求。 3 整合 Hibernate Validator前後代碼對比:
為了能一目瞭然的看到整合Hibernate Validator的好處,我們來比較一下下述代碼。假設我們有這樣一個方法, 該方法是從某個SSM(Spring MVC + Spring + Mybatis)項目的controller層摘錄下來的。該方法的目的是查詢所有名字叫某某的男性或女性同胞。名字參數name不可為空值,性別參數gender必須為“F”或“M”,分別代表女性或男性。注意,這裡的範例程式碼將參數校正邏輯放在了controller層,校正通過後再進入service層。可能有的項目會將參數校正挪到service層,當然這不是重點。這個查詢方法的代碼如下:
@RequestMapping(value = "/all", method = RequestMethod.GET)@ResponseBodypublic List<String> getAllPeople(String name, String gender) { if (StringUtils.isEmpty(name)) { throw new BusinessException(FALL, "使用者名稱不可為空"); } if (gender == null || (!gender.equals("F") && !gender.equals("M"))) { throw new BusinessException(FALL, "性別不合法"); } return cityService.getAllCitys(name, gender);}
上述查詢方法是在沒有整合Hibernate Validator的項目中,參數校正的一般方式,為了輔助完成參數校正,我們還不得不封裝了一個工具類StringUtils用於判斷字串是否為空白。如果參數異常,則拋出業務異常,該異常會交給架構的異常處理機制進行處理。我們可以看到,僅僅兩個簡單的參數就不得不寫上多行代碼來完成要求。然而,當我們將上述SSM項目與Hibernate Validator整合之後,參數校正可以在方法簽名中完成,方法體內無需寫任何與參數校正相關的代碼邏輯。如果參數校正不通過,同樣也會拋出異常,該異常同樣會交給架構的異常處理機制進行處理。
@RequestMapping(value = "/all", method = RequestMethod.GET)@ResponseBodypublic List<String> getAllPeople( @NotBlank(message = "使用者名稱不可為空" ) String name, @Gender(message = "性別不合法") String gender) { return cityService.getAllCitys(name, gender);}
通過上述代碼的比較,可以很明顯的看到,後者代碼簡潔了很多,在參數數量很多,業務複雜的的方法中,代碼量的差異會更加明顯,同樣的,代碼的可讀性也會有了天壤之別。 4. Spring MVC整合Hibernate Validator步驟:
4.1 如果項目中使用Maven,就需要在pom.xml中添加如下一段,Hibernate需要Java EL運算式,因此需要添加EL的依賴項。
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.4.Final</version></dependency><dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>2.2.4</version></dependency><dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.el</artifactId> <version>2.2.4</version></dependency>
4.2 在springmvc的設定檔(此處使用預設名:spring-mvc.xml)中添加如下配置開啟校正功能
<mvc:annotation-driven validator="validator" /><!-- bean層級的校正 方法中的參數bean必須添加@Valid註解,後面緊跟著BindingResult result參數--> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator" /> </bean><!-- 方法層級的校正 要校正的方法所在類必須添加@Validated註解--> <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"> <!-- 可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的參考配置,如果不指定則系統使用預設的 --> <property name="validator" ref="validator" /> </bean>
4.3 在校正的方法所在類上添加@Validated註解可以開啟方法層級的校正,第3節代碼對比的列子中,參數校正註解直接添加在方法簽名上的方法參數前面,這就是所謂的方法層級的校正。所謂bean層級的校正,就是方法的參數是一個Java Bean,校正的註解則添加到Java Bean的相應屬性上。目前,在使用Spring MVC的項目中,bean層級的校正必須給bean參數添加@Valid註解。但在使用Jersey(一個REST FULL的標準實現架構)替代Spring MVC的項目中,則沒有此要求。所以代碼還會更方便。 5 Hibernate Validator 中內建的 constraint簡介
註解 作用
@Valid 被注釋的元素是一個對象,需要檢查此對象的所有欄位值
@Null 被注釋的元素必須為 null
@NotNull 被注釋的元素必須不為 null
@AssertTrue 被注釋的元素必須為 true
@AssertFalse 被注釋的元素必須為 false
@Min(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值
@Max(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值
@DecimalMin(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值
@DecimalMax(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值
@Size(max, min) 被注釋的元素的大小必須在指定的範圍內
@Digits (integer, fraction) 被注釋的元素必須是一個數字,其值必須在可接受的範圍內
@Past 被注釋的元素必須是一個過去的日期
@Future 被注釋的元素必須是一個將來的日期
@Pattern(value) 被注釋的元素必須符合指定的Regex 6 自訂校正註解樣本
6.1 在第3節中,我們使用到了@Gender註解表示男女的性別,這其實不是Hibernate Validator內建的,而是一個自訂的註解。由於HV中的標準註解可能滿足不了某些校正情境,因此自訂校正註解是有必要的。之所以寫本文,目的是推薦公司的項目也也可以嘗試使用這個優秀的bean validation架構,所以自訂註解的實現細節此處不進一步講述,只是給出兩個已經實現的列子,一個是之前使用到的註解@Gender,用於校正使用者的性別。另一個註解是@PhoneNumber, 用於限制上傳的參數必須是手機號。
6.2 @Gender註解類代碼如下:
@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = Gender.Validator.class)@SuppressWarnings("javadoc")public @interface Gender{ String message() default "invalid gender"; boolean allowBlank() default false; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default{}; public class Validator implements ConstraintValidator<Gender, String> { boolean allowBlank; @Override public void initialize(Gender gender) { allowBlank = gender.allowBlank(); } @Override public boolean isValid(String arg0, ConstraintValidatorContext arg1) { if (arg0 == null) { return allowBlank; } return arg0.equalsIgnoreCase("M") || arg0.equalsIgnoreCase("F"); } }}
6.2 @PhoneNumber註解如下:
@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = PhoneNumber.Validator.class)@SuppressWarnings("javadoc")public @interface PhoneNumber { String message() default "invalid phone number"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; public class Validator implements ConstraintValidator<PhoneNumber, String> { @Override public void initialize(PhoneNumber arg0) { } @Override public boolean isValid(String arg0, ConstraintValidatorContext arg1) { return StringUtil.isPhoneNumber(arg0); }}}
@PhoneNumber註解中用到的工具類StringUtil的isPhoneNumber方法如下:
public final String PHONE ="^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,5-8])|(18[0-9]))\\d{8}$";public static boolean isPhoneNumber(String phone) { if (StringUtil.isNullOrEmpty(phone)) { return false; } return match(RegExp.PHONE, phone); }
7 簡單總結
本文主要介紹了hibernate Validator這一優秀的Bean Validation架構。並介紹了Spring MVC中整合這個參數校正框如何將商務邏輯與參數校正邏輯相分離,從而保持代碼的精簡和優雅。易讀和易維護。本人之前所在公司的多重專案採用了這種方式,使得項目代碼無比清爽。本人也願意分享自己積累的一點微薄知識,如果將本文涉及的技術與上一篇篇經驗案列講到的內容聯合使用,更會使項目增色不少,但願在後續的項目中,本人能見證這一成熟技術的應用。