天天看點

Spring AOP面向切面程式設計之基于@Aspect或者@RestControllerAdvice實作的全局參數校驗器ParamValidateAspect

Spring framework架構為我們提供了很多有用的工具,比如Validation類用于處于HTTP請求的參數校驗。

import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
           

我們可以在Controller層擷取Error來判斷是否校驗失敗,但是這樣每一個Controller層的方法都有重複的代碼,我們可以實作統一的切面Aspect去為所有的方法都添加 校驗邏輯。

@Aspect是Spring面向切面程式設計最重要的注解~可以實作在方法執行的時候添加額外的行為,
包括方法執行前Before、方法執行後After、方法抛出異常Exception、方法環繞Around等等時機。      
import com.nova.felixchat.infrastructure.commons.BusinessException;
import com.nova.felixchat.infrastructure.commons.enums.BizErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;

import java.util.List;

/**
 * @author linzihao
 */
@Slf4j
@Aspect
@Component
public class ParamValidateAspect {

    @Pointcut("execution(* com.nova.felixchat..*(..))")
    public void aspect() {
    }

    /*
     * 配置前置通知,使用在方法aspect()上注冊的切入點
     * 同時接受JoinPoint切入點對象,可以沒有該參數
     */
    @Before("aspect()")
    public void before(JoinPoint joinPoint) {
        Object[] params = joinPoint.getArgs();
        for (Object param : params) {
            if (param instanceof Errors) {
                Errors errors = (Errors) param;
                if (errors.hasErrors()) {
                    List<ObjectError> errorList = errors.getAllErrors();
                    StringBuilder sb = new StringBuilder();
                    FieldError fieldError = (FieldError) errorList.get(0);
                    sb.append(fieldError.getDefaultMessage());
                    log.warn("======參數校驗異常======" + sb.toString().trim());
                    throw new BusinessException(BizErrorCode.REQUEST_PARAM_ILLEGAL);
                }
            }
        }
    }
}
           

當然,我們完全可以使用@ControllerAdvice或者@RestControllerAdvice來實作統一參數校驗。

import com.ligeit.supply.rules.infrastructure.commons.BizErrorCode;
import com.ligeit.supply.rules.infrastructure.commons.BusinessException;
import com.ligeit.supply.rules.infrastructure.commons.ResultData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;

import java.io.IOException;
import java.util.Comparator;
import java.util.stream.Collectors;


/**
 * 統一異常處理類
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {


    /**
     * 參數驗證統一處理
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        String msg = bindingResult.getFieldErrors().stream().sorted(Comparator.comparing(FieldError::getField))
                .map(e1 -> e1.getDefaultMessage()).collect(Collectors.joining(","));
        log.error("參數驗證失敗: {},", e.getMessage());
        return ResultData.builder().code(BizErrorCode.REQUEST_PARAM_INVALID.getCode()).msg(msg).build();
    }

   //......其他異常處理邏輯

}