什麼是JSR303?
JSR 303 – Bean Validation 是一個資料驗證的規範,2009 年 11 月确定最終方案。
Hibernate Validator 是 Bean Validation 的最佳實踐。
為什麼使用JSR,松耦合,讓業務代碼的職責更加清晰。
松耦合就是職責更加清晰,每個人都有自己的職責,如果你的代碼進行改動,我不用改動或者僅僅少量改動就可以釋出和部署。
準備工作
maven 配置
<!-- JSR 303 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- Hibernate validator-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.0.Final</version>
</dependency>
SpringMVC 配置
<mvc:annotation-driven validator="validator">
</mvc:annotation-driven>
<!-- 配置校驗器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 校驗器,使用Hibernate校驗器 -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<!-- 指定校驗使用的資源檔案,在檔案中配置校驗錯誤資訊,如果不指定則預設使用classpath下面的ValidationMessages.properties檔案, -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<!-- 校驗錯誤資訊配置檔案,也可以不配置,直接使用注解中的message即可 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 資源檔案名 -->
<property name="basenames">
<list>
<value>classpath:messageSource</value>
</list>
</property>
<!-- 資源檔案編碼格式 -->
<property name="fileEncodings" value="utf-8"/>
<!-- 對資源檔案内容緩存時間,機關秒 -->
<property name="cacheSeconds" value="120"/>
</bean>
常用校驗注解
注解 | 運作時檢查 |
---|---|
@AssertFalse | 被注解的元素必須為false |
@AssertTrue | 被注解的元素必須為true |
@DecimalMax(value) | 被注解的元素必須為一個數字,其值必須小于等于指定的最大值 |
@DecimalMin(Value) | 被注解的元素必須為一個數字,其值必須大于等于指定的最小值 |
@Digits(integer=, fraction=) | 被注解的元素必須為一個數字,其值必須在可接受的範圍内 |
@Future | 被注解的元素必須是日期,檢查給定的日期是否比現在晚 |
@Max(value) | |
@Min(value) | |
@NotNull | 被注解的元素必須不為null |
@Null | 被注解的元素必須為null |
@Past(java.util.Date/Calendar) | 被注解的元素必須過去的日期,檢查标注對象中的值表示的日期比目前早 |
@Pattern(regex=, flag=) | 被注解的元素必須符合正規表達式,檢查該字元串是否能夠在match指定的情況下被regex定義的正規表達式比對 |
@Size(min=, max=) | 被注解的元素必須在制定的範圍(資料類型:String, Collection, Map and arrays) |
@Valid | 遞歸的對關聯對象進行校驗, 如果關聯對象是個集合或者數組, 那麼對其中的元素進行遞歸校驗,如果是一個map,則對其中的值部分進行校驗 |
@CreditCardNumber | 對信用卡号進行一個大緻的驗證 |
被注釋的元素必須是電子郵箱位址 | |
@Length(min=, max=) | 被注解的對象必須是字元串的大小必須在制定的範圍内 |
@NotBlank | 被注解的對象必須為字元串,不能為空,檢查時會将空格忽略 |
@NotEmpty | 被注釋的對象必須不為空(資料:String,Collection,Map,arrays) |
@Range(min=, max=) | 被注釋的元素必須在合适的範圍内 (資料:BigDecimal, BigInteger, String, byte, short, int, long and 原始類型的包裝類 ) |
@URL(protocol=, host=, port=, regexp=, flags=) | 被注解的對象必須是字元串,檢查是否是一個有效的URL,如果提供了protocol,host等,則該URL還需滿足提供的條件 |
Bean驗證
首先向我們的bean中添加注解。
public class User {
@NotEmpty(message = "使用者名不為空")
private String username;
@NotEmpty(message = "密碼不為空")
private String password;
// getter 和 setter
}
Controller
中配置:
@RestController
public class UserAction {
@PostMapping("/login")
public Result login(@Validated User user, Errors errors) {
System.out.println(user);
if (errors.hasErrors()) {
return ResultUtil.messageResult(errors);
}
return ResultUtil.SUCCESS_RESULT;
}
}
我們隻需要在要校驗的bean前面添加
@Validated
,在需要校驗的bean後面添加
Errors
對象來接收校驗出錯資訊即可,然後根據錯誤資訊進行判斷和傳回錯誤資訊給前端。
注意:
@Validated
和
Errors errors
是成對出現的,并且形參順序是固定的(一前一後)。也就是所每一個
@Validated
後面必須跟一個
Errors
,需要驗證多個bean,後面就跟多個
Errors
。
AOP處理Errors
如果我們通過JSR來驗證bean對象,那麼在每個需要驗證的方法中都需要處理Error對象,很容易想到可以通過AOP的方式來統一處理錯誤對象,并且組織錯誤資訊,傳回給前端。
通過一個環繞通知對所有的action方法盡心攔截,如果發現有Errors對象存在,就擷取所有的錯誤資訊,封裝為一個list傳回前端。
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import java.util.ArrayList;
import java.util.List;
/**
* @author 李文浩
* @version 2017/10/8.
*/
public class ValidationAdvice {
/**
* 切點處理
*
* @param pjp
* @return
* @throws Throwable
*/
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
Errors errors = null;
Object[] args = pjp.getArgs();
if (null != args && args.length != 0) {
for (Object object : args) {
if (object instanceof BindingResult) {
errors = (BindingResult) object;
break;
}
}
}
if (errors != null && errors.hasErrors()) {
List<ObjectError> allErrors = errors.getAllErrors();
List<String> messages = new ArrayList<String>();
for (ObjectError error : allErrors) {
messages.add(error.getDefaultMessage());
}
return ResultUtil.messageResult(messages);
}
return pjp.proceed();
}
}
Spring配置:
<bean id="validationAdvice" class="studio.jikewang.util.ValidationAdvice" />
<aop:config>
<aop:pointcut id="validation1" expression="execution(public * studio.jikewang.action.*.*(..))" />
<aop:aspect id="validationAspect" ref="validationAdvice">
<aop:around method="aroundMethod" pointcut-ref="validation1" />
</aop:aspect>
</aop:config>
@Validated
@Valid
@Validated
@Valid
-
@Valid
是使用Hibernate Validation的時候使用。
Java的JSR303聲明了這類接口,然後hibernate-validator對其進行了實作。
-
是隻用Spring Validator校驗機制使用。@Validated
方法參數驗證
Spring提供了
MethodValidationPostProcessor
類,用于對方法的校驗。
Controller
@RestController
@Validated
public class UserAction {
@PostMapping("/login")
public Result login(@NotEmpty(message = "使用者名不為空") String username,
@NotEmpty(message = "密碼不為空") String password) {
return ResultUtil.SUCCESS_RESULT;
}
}
xml配置(最好是配置在
<mvc:annotation-driven validator="validator" />
上面,不然會有未知錯誤)如下:
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
</bean>
在校驗遇到非法的參數時會抛出
ConstraintViolationException
,可以通過
getConstraintViolations
獲得所有沒有通過的校驗
ConstraintViolation
集合,可以通過它們來獲得對應的消息。
我們同樣使用
@ExceptionHandler
捕捉
ConstraintViolationException
異常處理全局異常資訊。
然後将所有的錯誤資訊封裝好傳回給前端。
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public Result handleConstraintViolationException(ConstraintViolationException e) {
List<String> list = new ArrayList<String>();
for (ConstraintViolation<?> s : e.getConstraintViolations()) {
System.out.println(s.getInvalidValue() + ": " + s.getMessage());
list.add(s.getMessage());
}
Result result = new Result();
result.setStatus("0");
result.setMessage(list);
return result;
}
}
使用 @Validated
驗證list
@Validated
現在我遇到一個新的需求,我需要前端給我傳遞一個對象數組,于是我使用一個list去接收,但是無法獲得驗證資訊。
于是将list重新包裝一下。
public class ValidList<E> {
@Valid
private List<E> list;
public List<E> getList() {
return list;
}
public void setList(List<E> list) {
this.list = list;
}
}
Controller
@RestController
public class UserAction {
@PostMapping("/login")
public Result login(@Validated ValidList<User> users, Errors errors) {
System.out.println(users.getList());
if (errors.hasErrors()) {
return ResultUtil.messageResult(errors);
}
return ResultUtil.SUCCESS_RESULT;
}
}
然後為了隻傳回第一個驗證失敗的資訊(如果不更改,就會将所有的出錯資訊傳回給前端),更改
ValidationAdvice
如下:
public class ValidationAdvice {
/**
* 切點處理
*
* @param pjp
* @return
* @throws Throwable
*/
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
boolean isValidList = false;
Errors errors = null;
if (null != args && args.length != 0) {
for (Object object : args) {
if (object instanceof ValidList) {
isValidList = true;
}
if (object instanceof BindingResult) {
errors = (BindingResult) object;
break;
}
}
}
if (errors != null && errors.hasErrors()) {
List<ObjectError> allErrors = errors.getAllErrors();
List<String> messages = new ArrayList<String>();
for (ObjectError error : allErrors) {
if (isValidList) {
messages.add(error.getDefaultMessage());
break;
} else {
messages.add(error.getDefaultMessage());
}
}
return ResultUtil.messageResult(messages);
}
return pjp.proceed();
}
}
這樣即可驗證
list
總結
AOP的思想是貫穿我們的開發的,使用AOP的思想可以大大提高我們的開發效率,減少重複代碼。
參考文檔:
- springmvc參數校驗-JSR303(Bean Validation)
- Java Bean Validation 最佳實踐