基于AOP和Hibernate Validate框架的后台验证
整体思路
- 对Controller层的方法做切面拦截
- 验证入参是否使用
@DoValid
注解修饰 - 在切面类中调用Hibernate Validate框架依次对入参进行验证
- 全部验证合法,执行原方法体,若验证不通过,动态判断返回类型,并构造返回结果
Demo
- 新增
- 查询
常用注解
Annotation | Value | Scope |
---|---|---|
@NotNull | 不为null | 字段或属性 |
@NotEmpty | 不为null同时也不为空 | 字段或属性,String,Collection,Map,数组 |
@NotBlank | 字符串不为null,并且不是空字符串(忽略前后空白字符) | 字段或属性 |
@Pattern | 是否匹配正则 | String |
... | ... | ... |
自定义注解
- 定义注解
- 定义验证类
如何与系统整合
- pom.xml添加如下依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.3.Final</version>
</dependency>
- 在spirng.xml中添加如下配置:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
或者使用@Bean
注解,注入该Bean
- 在pom.xml中引入vb依赖或者将如下文件拷贝到对应系统中
DoValid.java
和DoValidAspect.java
/*
* Copyright (c) 2016, www.vnetoo.com. All rights reserved.
*/
package com.vnetoo.vcomponent.validate;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 参数验证注解
* @date 2016年11月25日 下午4:48:53
* @author zhaoj
* @since V2.0.0
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DoValid {
//如需要引入分组,加入如下这句即可,代码中使用@DoValid({GroupA.class})
//Class<?>[] value() default {};
}
/*
* Copyright (c) 2016, www.vnetoo.com. All rights reserved.
*/
package com.vnetoo.vcomponent.validate;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Set;
import javax.validation.ConstraintViolation;
import org.apache.commons.lang.StringUtils;
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.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.vnetoo.common.AppContext;
import com.vnetoo.common.enums.Result;
import com.vnetoo.common.vo.AjaxResponse;
import com.vnetoo.common.vo.CommonResponse;
/**
* 表单后台验证切面
*
* @date 2016年11月25日 上午10:12:21
* @author zhaoj
* @since V2.0.0
*/
@Aspect
@Component
public class DoValidAspect {
/**
* 验证类的结尾
*
* @date 2016年11月25日 下午4:51:41
* @author zhaoj
* @since V2.0.0
*/
public static final String VALIDATOR_SUFFIX = "Validator";
/**
* 定义切面
*
* @date 2016年11月25日 上午10:12:02
* @author zhaoj
* @since V2.0.0
*/
@Pointcut("execution(public * com.vnetoo..*Controller.*(..))")
public void validate() {
}
/**
* 切面方法体环绕
*
* @date 2016年11月25日 上午10:11:33
* @author zhaoj
* @since V2.0.0
* @param pjp
* @return
* @throws Throwable
*/
@Around(value = "validate()")
private Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
/*
* 算法: 1.取得方法体中的参数注解二维数组 2.逐一遍历参数中的注解,并对带有带有 @Valid 注解的参数做验证处理
*
* 验证处理: 1.调用当前Controller中的validate()
* 2.如果返回的CommonResponse.Result!=Result.Success,验证返回类型
* 2.1 JSON(主要用于添加、编辑):判断Controller是否有@RestController 或者 方法上加了@ResponseBody,返回带错误信息的JSON
* 2.2 View(主要用于查询): 判断Controller是否返回值为ModelAndView,返回带错误信息的错误页面
*
* 其中,错误信息在CommonResponse.msg中
* 3.若所有验证都通过了,则可以继续执行方法
*/
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
//[参数][注解]二维数组
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations != null && parameterAnnotations.length != 0) {
for (int i = 0; i != parameterAnnotations.length; ++i) {
Annotation[] param = parameterAnnotations[i];
for (int j = 0; j != param.length; ++j) {
Annotation annotation = param[j];
if (annotation instanceof DoValid) {
// 参数中有验证注解
Object bo = pjp.getArgs()[i];
/*******************单个入参验证开始*******************/
CommonResponse validator = validate(bo);
//分组支持
//CommonResponse validator = validate(bo,((DoValid) annotation).value());
if (validator.getResult() != Result.SUCCESS) {
//验证不通过,验证返回方式
if(isJsonResponse(pjp)){
//返回JSON
return new AjaxResponse(validator .getResult(), AppContext.token(), validator .getMsg());
}else{
//返回View
return new ModelAndView("errorMsg").addObject("errMsg", validator .getMsg());
}
}
/*******************单个入参验证结束*******************/
}
}
}
}
//所有入参验证完毕、已做字段过滤,且均合法
//代码若能执行到此处,则说明验证通过,则执行Controller层中的方法体
return pjp.proceed();
}
/**
* 判断是否是返回JSON的请求
* @date 2016年12月1日 下午4:45:50
* @author zhaoj
* @since V2.0.0
* @param pjp
* @return
*/
public static boolean isJsonResponse(ProceedingJoinPoint pjp){
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Annotation[] methodAnnotations = method.getAnnotations();
for(Annotation tmp : methodAnnotations){
if(tmp instanceof ResponseBody){
return true;
}
}
Annotation[] classAnnotations = method.getClass().getAnnotations();
for(Annotation tmp : classAnnotations){
if(tmp instanceof RestController){
return true;
}
}
return false;
}
/**
* 参数验证
* @date 2016年12月1日 下午4:46:00
* @author zhaoj
* @since V2.0.0
* @param bo
* @return
*/
public static CommonResponse validate(Object bo) {
Set<ConstraintViolation<Object>> set = ((LocalValidatorFactoryBean) AppContext.getBean("validator")).getValidator().validate(bo);
//分组验证支持
//Set<ConstraintViolation<Object>> set2 = ((LocalValidatorFactoryBean) AppContext.getBean("validator")).getValidator().validate(bo, ((DoValid) annotation).value());
if (set != null && !set.isEmpty()) {
String msg = "";
Iterator<ConstraintViolation<Object>> iterator = set.iterator();
while (iterator.hasNext()) {
String singleMsg = iterator.next().getMessage();
if (StringUtils.isNotEmpty(singleMsg)) {
msg += singleMsg + ",";
}
}
if (StringUtils.isNotEmpty(msg.toString())) {
msg = msg.substring(0, msg.toString().length() - 1);
}
return new CommonResponse(Result.ERROR, msg);
}
return new CommonResponse(Result.SUCCESS, null);
}
}
如果需要指定分组的形式,请查看链接:分组验证,然后对上述代码略加修改即可。
- 不采用分组策略:建
XXXCreateForm
、XXXEditForm
、XXXSearchForm
类并在From的属性上加验证注解即可
采用分组策略:建立分组接口,在BO的属性上加验证注解,并指定分组,详见:分组验证 - 在需要验证的入参中加上
@DoValid
注解修饰即可,如:
/**
* 添加保存
* @date 2016-12-09 15:19:51
* @author zhaoj
* @since V1.0.3
* @param bo
* @return
*/
@CheckToken
@ResponseBody
@Authorization
@LogMark(memo="添加保存")
@RequestMapping(value = "/insert")
public AjaxResponse insert(@DoValid ExamUserCreateForm form){
try {
ExamUser bo = new ExamUser();
PropertyUtils.copyProperties(bo, form);
getService().insert(bo);
return new AjaxResponse();
} catch (Exception e) {
return new AjaxResponse(Result.ERROR, AppContext.token(), e.getMessage());
}
}