依賴包:
<!--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>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
<mvc:annotation-driven validator="validator"/>
驗證對象:
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
@Data
public class BBB {
@NotNull
//只有@Validated 指定了group爲RegisterCheckGroup.class時才校驗
@Min(value = 3, message = "不能小於3", groups = RegisterCheckGroup.class)
private Integer age;
@NotBlank(groups = {Default.class, LoginCheckGroup.class, RegisterCheckGroup.class})
private String name;
@IDCardValid
private String idCard;
@Valid //級聯(遞歸)驗證,驗證CCC對象的字段
@NotNull
private CCC ccc;
/**
* 登錄驗證校驗組
*/
interface LoginCheckGroup {//extends javax.validation.groups.Default {
}
/**
* 註冊驗證校驗組
*/
interface RegisterCheckGroup {
}
}
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
@Data
public class CCC {
@NotNull
@Range(min = 0, max = 2)
private Integer sex;
}
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull;
@RestController
public class AAA {
//基本類型驗證
@RequestMapping("/hi")
public String hi(@NotBlank String name, @NotNull(message = "不能爲null") Integer age) {
System.out.println(name + ":" + age);
return "hi " + name + ":" + age;
}
//組合驗證
@RequestMapping("/hi2")
public String hi2(@NotBlank String name, @NotNull(message = "不能爲null") Integer age, @Validated CCC ccc) {
System.out.println(name + ":" + age + ":" + ccc.toString());
return "hi2 " + name + ":" + age + ":" + ccc.toString();
}
//封裝類型驗證
@RequestMapping("/ha")
public String ha(@Validated BBB bbb) {
System.out.println(bbb);
return bbb.toString() + " ha";
}
//登錄驗證組
@RequestMapping("/login")
public String login(@Validated(BBB.LoginCheckGroup.class) BBB bbb) {
System.out.println(bbb);
return bbb.toString() + " ha";
}
//註冊驗證組
@RequestMapping("/register")
public String register(@Validated(BBB.RegisterCheckGroup.class) BBB bbb) {
System.out.println(bbb);
return bbb.toString() + " ha";
}
}
自定義註解驗證:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IDCardConstraintValidator.class)
public @interface IDCardValid {
String message() default "IDCard格式不正確";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IDCardConstraintValidator implements ConstraintValidator<IDCardValid, Object> {
@Override
public void initialize(IDCardValid constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return IDCardUtil.isIDCard(value);
}
}
參考@NotNull:
package javax.validation.constraints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* The annotated element must not be {@code null}.
* Accepts any type.
*
* @author Emmanuel Bernard
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@link NotNull} annotations on the same element.
*
* @see javax.validation.constraints.NotNull
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
全局異常處理器
package com.base.exception;
import com.base.utils.ResponseUtil;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Component
public class GlobalExceptionHandler extends DefaultHandlerExceptionResolver {
@Override
public int getOrder() {
return super.getOrder() + 1;
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
//前端參數(解析或效檢)異常
//補充-默認異常處理器沒有返回以下異常的錯誤信息
if (ex instanceof ControllerArgsCheckException) {
return this.handleBadRequestException(ex, request, response, handler);
}
} catch (Exception e) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", e);
}
}
return super.doResolveException(request, response, handler, ex);
}
protected ModelAndView handleBadRequestException(Exception ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
return handleBadRequestException(ex, request, response, handler, ex.getMessage());
}
protected ModelAndView handleBadRequestException(Exception ex, HttpServletRequest request, HttpServletResponse response, Object handler, String message) throws IOException {
return handleHttpMessageException(ex, request, response, handler, message, HttpServletResponse.SC_BAD_REQUEST);
}
protected ModelAndView handleHttpMessageException(Exception ex, HttpServletRequest request, HttpServletResponse response, Object handler, String message, Integer status) throws IOException {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Failed to handle HTTP message: " + ex);
}
return ResponseUtil.write(request, response, status, message);
}
@Override
protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
return handleBadRequestException(ex, request, response, handler);
}
@Override
protected ModelAndView handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
return handleBadRequestException(ex, request, response, handler);
}
@Override
protected ModelAndView handleTypeMismatch(TypeMismatchException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String message;
if (ex instanceof MethodArgumentTypeMismatchException) {
message = jointArgErrorTip(((MethodArgumentTypeMismatchException) ex).getName(), ex.getValue(), ex.getMessage());
return handleBadRequestException(ex, request, response, handler, message);
}
return handleBadRequestException(ex, request, response, handler);
}
@Override
protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
return handleBadRequestException(ex, request, response, handler);
}
@Override
protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
return handleBadRequestException(ex, request, response, handler);
}
@Override
protected ModelAndView handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String message = jointArgErrorTip(ex.getBindingResult().getFieldErrors());
return handleBadRequestException(ex, request, response, handler, message);
}
@Override
protected ModelAndView handleBindException(BindException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String message = jointArgErrorTip(ex.getFieldErrors());
return handleBadRequestException(ex, request, response, handler, message);
}
/**
* 拼接參數錯誤提示
*/
public static String jointArgErrorTip(List<FieldError> fieldErrorList) {
// 只返回第一個參數錯誤提示
FieldError fieldError = fieldErrorList.get(0);
return jointArgErrorTip(fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage());
}
/**
* 拼接參數錯誤提示
*/
public static String jointArgErrorTip(String name, Object value, String message) {
return jointTip("參數校驗錯誤: ", name, value, message);
}
/**
* 拼接錯誤提示
*/
public static String jointTip(String prefix, String name, Object value, String message) {
return prefix + name + " = [" + value + "], " + message + "; ";
}
}
擴展參數校驗:
import com.wopuwulian.common.enums.ValueMsgEnum;
import lombok.Getter;
@Getter
public class ControllerArgsCheckException extends RuntimeException {
private String code;
public ControllerArgsCheckException(String message) {
super(message);
}
public ControllerArgsCheckException(String code, String message) {
super(message);
this.code = code;
}
public ControllerArgsCheckException(ValueMsgEnum valueMsgEnum) {
this(String.valueOf(valueMsgEnum.getValue()), valueMsgEnum.getMsg());
}
}
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
import com.base.exception.ControllerArgsCheckException;
import com.base.exception.GlobalExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.Set;
/**
* controller層參數校驗
*
* @Description
* @Author zhongxing
* @Date 2020/4/20 17:29
* @Version 1.0
*/
@Slf4j
@Aspect
@Component
public class ArgumentValidAspect {
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private final ExecutableValidator validator = factory.getValidator().forExecutables();
private <T> Set<ConstraintViolation<T>> validMethodParams(T obj, Method method, Object[] params) {
return validator.validateParameters(obj, method, params);
}
@Pointcut("execution(public * org.xxx.ac.*.controller.*.*(..)) || execution(public * com.xxx.wechat.controller.*.*(..))")
public void valid() {
}
@Around("valid()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//無參數不校驗
Object[] objects = pjp.getArgs();
if (objects.length == 0) {
return pjp.proceed();
}
// **************************校驗基本類型參數*************************/
// 獲得切入目標對象
Object target = pjp.getThis();
// 獲得切入的方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
// 執行校驗,獲得校驗結果
Set<ConstraintViolation<Object>> validResult = validMethodParams(target, method, objects);
//如果有校驗不通過的
if (!validResult.isEmpty()) {
// 獲得方法的參數名稱
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
// 只返回第一個參數錯誤提示
ConstraintViolation<Object> constraintViolation = validResult.iterator().next();
// 獲得校驗的參數路徑信息
PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
// 獲得校驗的參數位置
int paramIndex = pathImpl.getLeafNode().getParameterIndex();
// 獲得校驗的參數名稱
String paramName = parameterNames[paramIndex];
String errMsg = GlobalExceptionHandler.jointArgErrorTip(paramName, objects[paramIndex], constraintViolation.getMessage());
log.error(errMsg);
throw new ControllerArgsCheckException(errMsg);
}
//**************************校驗封裝類型javabean參數**********************/
// 方法帶BindingResult參數(只能出現在封裝類型參數後),默認不要帶,觸發BindException,由GlobalExceptionHandler處理
for (Object object : objects) {
if (object instanceof BeanPropertyBindingResult) {
BeanPropertyBindingResult result = (BeanPropertyBindingResult) object;
if (result.hasErrors()) {
String errMsg = GlobalExceptionHandler.jointArgErrorTip(result.getFieldErrors());
log.error(errMsg);
throw new ControllerArgsCheckException(errMsg);
}
}
}
return pjp.proceed();
}
}
import com.base.exception.ControllerArgsCheckException;
import com.base.exception.GlobalExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.validator.internal.engine.path.NodeImpl;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.Set;
/**
* service接口層(dubbo調用方)參數校驗
*
* @Description
* @Author zhongxing
* @Date 2020/4/20 17:29
* @Version 1.0
*/
@Slf4j
@Aspect
//@Component 未啓用
public class ArgumentValidAspectService {
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private final ExecutableValidator validator = factory.getValidator().forExecutables();
private <T> Set<ConstraintViolation<T>> validMethodParams(T obj, Method method, Object[] params) {
return validator.validateParameters(obj, method, params);
}
@Pointcut("execution(public * org.xxx.ac.*.service.*Service.*(..))")
public void valid() {
}
@Around("valid()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//無參數不校驗
Object[] objects = pjp.getArgs();
if (objects.length == 0) {
return pjp.proceed();
}
// **************************校驗基本類型參數*************************/
// 獲得切入目標對象
Object target = pjp.getThis();
// 獲得切入的方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
// 執行校驗,獲得校驗結果
Set<ConstraintViolation<Object>> validResult = validMethodParams(target, method, objects);
//如果有校驗不通過的
if (!validResult.isEmpty()) {
// 只返回第一個參數錯誤提示
ConstraintViolation<Object> constraintViolation = validResult.iterator().next();
// 獲得校驗的參數路徑信息
PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
NodeImpl leafNode = pathImpl.getLeafNode();
String paramName = leafNode.getName();
Object value = leafNode.getValue();
// 基本參數類型-參數名前綴
if (paramName.startsWith("arg")) {
// 獲得方法的參數名稱
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
// 獲得校驗的參數位置
int paramIndex = pathImpl.getLeafNode().getParameterIndex();
// 獲得校驗的參數名稱
if (parameterNames != null) {
paramName = parameterNames[paramIndex];
}
}
String errMsg = GlobalExceptionHandler.jointArgErrorTip(paramName, value, constraintViolation.getMessage());
log.error(errMsg);
throw new ControllerArgsCheckException(errMsg);
}
//**************************校驗封裝類型javabean參數**********************/
// 方法帶BindingResult參數(只能出現在封裝類型參數後),默認不要帶,觸發BindException,由GlobalExceptionHandler處理
for (Object object : objects) {
if (object instanceof BeanPropertyBindingResult) {
BeanPropertyBindingResult result = (BeanPropertyBindingResult) object;
if (result.hasErrors()) {
String errMsg = GlobalExceptionHandler.jointArgErrorTip(result.getFieldErrors());
log.error(errMsg);
throw new ControllerArgsCheckException(errMsg);
}
}
}
return pjp.proceed();
}
}