天天看点

hibernate validator使用扩展

依赖包:

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

}

           

继续阅读