基于Hibernate Validate的后端验证

基于AOP和Hibernate Validate框架的后台验证

整体思路
  • 对Controller层的方法做切面拦截
  • 验证入参是否使用@DoValid 注解修饰
  • 在切面类中调用Hibernate Validate框架依次对入参进行验证
  • 全部验证合法,执行原方法体,若验证不通过,动态判断返回类型,并构造返回结果
Demo
  • 新增
  • 查询
常用注解
Annotation Value Scope
@NotNull 不为null 字段或属性
@NotEmpty 不为null同时也不为空 字段或属性,String,Collection,Map,数组
@NotBlank 字符串不为null,并且不是空字符串(忽略前后空白字符) 字段或属性
@Pattern 是否匹配正则 String
... ... ...
自定义注解
  • 定义注解
  • 定义验证类
如何与系统整合
  1. pom.xml添加如下依赖:
<dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.3.3.Final</version>
</dependency>
  1. 在spirng.xml中添加如下配置:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
      <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>  
</bean> 

或者使用@Bean注解,注入该Bean

  1. 在pom.xml中引入vb依赖或者将如下文件拷贝到对应系统中
    DoValid.javaDoValidAspect.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);
     }
 }

如果需要指定分组的形式,请查看链接:分组验证,然后对上述代码略加修改即可。

  1. 不采用分组策略:建XXXCreateFormXXXEditFormXXXSearchForm类并在From的属性上加验证注解即可
    采用分组策略:建立分组接口,在BO的属性上加验证注解,并指定分组,详见:分组验证
  2. 在需要验证的入参中加上@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());
     }
 }
参考

Hibernate Validate 中文 API
Hibernate Validate 常用注解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 3,141评论 0 2
  • hibernate Validator 是 Bean Validation 的参考实现 。 Hibernate V...
    一路逆风i阅读 3,064评论 0 1
  • Java 8自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发...
    谁在烽烟彼岸阅读 886评论 0 4
  • 说谎有时候是为了保护别人、为了让对方喜欢自己、为了博取同情、为了逃避,可有的人一生爱说谎,就是喜欢说谎。开始了说谎...
    小样儿的小小样儿927阅读 1,536评论 0 0