天天看点

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();
    }

   //......其他异常处理逻辑

}