天天看點

SpringMVC資料驗證(AOP處理Errors和方法驗證)

什麼是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 對信用卡号進行一個大緻的驗證
@Email 被注釋的元素必須是電子郵箱位址
@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

  • @Valid

    是使用Hibernate Validation的時候使用。

    Java的JSR303聲明了這類接口,然後hibernate-validator對其進行了實作。

  • @Validated

    是隻用Spring Validator校驗機制使用。

方法參數驗證

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

現在我遇到一個新的需求,我需要前端給我傳遞一個對象數組,于是我使用一個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的思想可以大大提高我們的開發效率,減少重複代碼。

參考文檔:

  1. springmvc參數校驗-JSR303(Bean Validation)
  2. Java Bean Validation 最佳實踐