依赖包:
<!--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();
}
}