前言:
  Oval参数校验框架确实小巧而强大, 他通过注解的方式配置类属性, 然后通过Oval本身自带的工具类, 快速便捷执行参数校验. 但是工具类的校验需要额外的代码编写, 同时Oval对函数参数级的校验, 默认情况下并不生效.
  本文将讲述借助Aspectj, 并结合Oval, 来是参数的自动校验.

举个例子:
  编写如下测试代码:

@Getter
@Setter
public class CCBReq { @NotNull(message = "message字段不能为空")
private String message; } // *) Oval校验工具类, 用于实体类的快速校验
public class OvalCheckHelper { public static void validate(Object obj) {
Validator validator = new Validator();
List<ConstraintViolation> cvs = validator.validate(obj);
if ( cvs != null && cvs.size() > 0 ) {
throw new ConstraintsViolatedException(cvs);
}
} }

  具体的服务类, 以及常规的校验方式代码:

@Component("ovalService")
public class OvalService { // *) 函数参数没法直接校验
public String echo1(@NotNull String msg) {
return msg;
} public String echo2(CCBReq req) {
// *) 需要通过工具类, 进行快速校验
OvalCheckHelper.validate(req);
return req.getMessage();
} }

  这边使用Oval框架进行校验, 就遇到两个常规的不完美的点.
  1. 函数参数没法直接校验(设置了Oval注解, 但无法利用)

public String echo1(@NotNull String msg) { } 

  这边的@NotNull完全变成了为了可读性而添加的注解, Oval框架也没法直接利用到它.
  2. 实体类的校验, 需要额外的代码

OvalCheckHelper.validate(req);

  这类代码会附带到各个具体的服务类的方法中, 一定程度上也是高度耦合进了业务代码中了.

Aspectj简介:
  Aspectj是面向切面的编程, 其定义了切入点(PointCut)以及基于切入点的(@Before, @After, @Around)这些切面操作.
  具体可以参阅如下文章: AspectJ基本用法, 我这边也不展开了.
  就多讲点一些关于切点(PointCut)里的call/execution/@annotation的区别:
  使用call指令, 扩展后的代码类似如下:

Call(Before)
Pointcut{
Pointcut Method
}
Call(After)

  而使用execution/@annotation指令, 扩展后的代码类似如下:

Pointcut{
execution(Before)
Pointcut Method
execution(After)
}

  

解决思路:
  让我们直接给一个解决方案吧.
  首先定义注解, 用于PointCut的切入点的确定.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OvalArgsCheck {
}

  然后定义一个具体的切面:

import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import net.sf.oval.exception.ConstraintsViolatedException;
import net.sf.oval.guard.Guard;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.stereotype.Component; import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; @Aspect
@Component
public class OvalArgsAdavice { @Pointcut("@annotation(com.test.oval.OvalArgsCheck)")
public void checkArgs() {
} @Before("checkArgs()")
public void joint(JoinPoint joinPoint) throws Exception { MethodInvocationProceedingJoinPoint mjp =
((MethodInvocationProceedingJoinPoint) joinPoint); // *) 获取methodInvocation对象
MethodInvocation mi = null;
try {
Field field = MethodInvocationProceedingJoinPoint
.class.getDeclaredField("methodInvocation");
field.setAccessible(true);
mi = (MethodInvocation)field.get(mjp);
} catch (Throwable e) {
} if ( mi != null ) {
// 获取Guard对象的validateMethodParameters方法
Guard guard = new Guard();
Method dm = Guard.class.getDeclaredMethod(
"validateMethodParameters",
Object.class,
Method.class,
Object[].class,
List.class
);
dm.setAccessible(true); // *) 对函数中标注Oval注解的参数, 直接进行校验, 用于解决第一类问题
List<ConstraintViolation> violations = new ArrayList<ConstraintViolation>();
dm.invoke(guard, mi.getThis(), mi.getMethod(), mi.getArguments(), violations);
if ( violations.size() > 0 ) {
throw new ConstraintsViolatedException(violations);
} // *) 以下是对函数中实体类(内部属性标记Oval注解)的参数, 进行校验, 用于解决第二类问题
Validator validator = new Validator();
for ( Object obj : mi.getArguments() ) {
if ( obj == null ) continue;
List<ConstraintViolation> cvs = validator.validate(obj);
if ( cvs != null && cvs.size() > 0 ) {
throw new ConstraintsViolatedException(cvs);
}
} } } }

  然后在之前的具体服务上的方法上, 添加注解:

@Component("ovalService")
public class OvalService { @OvalArgsCheck
public String echo1(@NotNull String msg) {
return msg;
} @OvalArgsCheck
public String echo2(CCBReq req) {
return req.getMessage();
} }

  

实战:
  为了激活Aspectj, 我们需要在spring的配置中, 需要如下配置.

<!-- 激活aspectj功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 指定spring容器bean自动扫描的范围 -->
<context:component-scan base-package="com.test.oval"/>

  然后编写测试代码:

@ContextConfiguration("classpath:spring-test-oval.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class OvalTest { @Resource
private OvalService ovalService; @Test(expected = ConstraintsViolatedException.class)
public void testCase1() {
ovalService.echo1(null);
} @Test(expected = ConstraintsViolatedException.class)
public void testCase2() {
CCBReq ccbReq = new CCBReq();
ovalService.echo2(ccbReq);
} }

  测试结果皆为pass.
  由此采用该方案, 函数参数中, 无论是直接Oval注解修饰, 还是实体类中属性修饰, 都可以无缝地实现自动参数校验.

后记:
  之前写过一篇文章: Dubbo的Filter实战--整合Oval校验框架. 本文算是对这篇文章的一些补充, 该方案是可以使用于dubbo接口中的参数自动校验中, 不过需要额外的Dubbo filter支持, 不过这不算太难.
  采用切面编程, 某种程度上, 大大减少了重复代码的编写, 简单易配置, 提高了编程效率.

利用Aspectj实现Oval的自动参数校验的更多相关文章

  1. Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation.本文会详细介绍Spring Validation各种场景下 ...

  2. 利用 Bean Validation 来简化接口请求参数校验

    团队新来了个校招实习生静静,相互交流后发现竟然是我母校同实验室的小学妹,小学妹很热情地认下了我这个失散多年的大湿哥,后来... 小学妹:大湿哥,咱们项目里的 Controller 怎么都看不到参数校验 ...

  3. WebFlux04 SpringBootWebFlux集成MongoDB之Windows版本、WebFlux实现CRUD、WebFlux实现JPA、参数校验

    1 下载并安装MongoDB 1.1 MongoDB官网 1.2 下载 solutions -> download center 1.3 安装 双击进入安装即可 1.3.1 安装时常见bug01 ...

  4. Spring Boot 2.x基础教程:JSR-303实现请求参数校验

    请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点的常见场景.比较常见的问题主要表现在以下几个方面: 仅依靠前端框架解决参数校验,缺失服务端的校验.这种情况常见于需要同时开发前后端的时候,虽然 ...

  5. 全栈之路-小程序API-SpringBoot项目中参数校验机制与LomBok工具集使用

    参数校验机制在web开发中是非常重要的,每当看到现在所在公司的校验代码,我都有头疼,每一个接口都是重新写参数的校验,有些复杂的接口,参数的校验甚至占了整个接口代码量的挺大一部分的,看着我都有些头疼,我 ...

  6. 补习系列(4)-springboot 参数校验详解

    目录 目标 一.PathVariable 校验 二.方法参数校验 三.表单对象校验 四.RequestBody 校验 五.自定义校验规则 六.异常拦截器 参考文档 目标 对于几种常见的入参方式,了解如 ...

  7. Spring基础系列-参数校验

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9953744.html Spring中使用参数校验 概述 ​ JSR 303中提出了Bea ...

  8. http get post 参数校验

    spring boot 常见http get ,post请求参数处理   在定义一个Rest接口时通常会利用GET.POST.PUT.DELETE来实现数据的增删改查:这几种方式有的需要传递参数,后台 ...

  9. springboot 参数校验详解

    https://www.jianshu.com/p/89a675b7c900 在日常开发写rest接口时,接口参数校验这一部分是必须的,但是如果全部用代码去做,显得十分麻烦,spring也提供了这部分 ...

随机推荐

  1. Dijkstra双栈算术表达式求值

    在看algs4的时候偶然发现了这个算法,又回顾了一遍当时数据结构课程里讲过的知识,当时很不在意.迟早是要还的,哎 用python实现了,比较麻烦的是我现在没有解决bash传参的问题,''(" ...

  2. 使用redis做分布式锁

    1.使用setnx命令.先看下官方文档http://redis.cn/commands/setnx.html 2.使用getset命令.先获取,再set 实现案例: * create 2018-12- ...

  3. JS设计模式(11)中介者模式

    什么是中介者模式? 中介者模式:对象和对象之间借助第三方中介者进行通信. 定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的 ...

  4. Codeforces 1045B Space Isaac - 数论 - Hash

    题目传送门 传送门I 传送门II 传送门III 题目大意 给定将$\left \{ 0, 1, \dots, m - 1\right \}$分成了不相交的两个非空集合$A$和$B$,给定$A$,问存在 ...

  5. Android创建自定义的布局和控件

    Android的自带布局有framelayout.linerlayout.relativelayout,外加两个百分比布局,但是这些无法灵活的满足我们的需要,所以我们要自己自定义并引入自己的布局.首先 ...

  6. eclipse的springboot插件

    eclipse的springboot插件官网下载地址还喜欢捉迷藏,正确的下载路径修改方法: 点击zip,然后复制出官网路径如下 http://download.springsource.com/rel ...

  7. Jquery实现轮播效果图

    <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8&quo ...

  8. 浅谈 cosos2d-x 的ImageView和Sprite的区别

    ImageView 1. ImageView是继承于Widget的,是cocos2d-x的基类 2. 实现了类似于按钮监听的事件,通过addTouchEventListener添加事件 var ima ...

  9. 【转】 C++析构函数的作用和用法

    转自:https://www.cnblogs.com/puyangsky/p/5319470.html 一.定义1. 作用:对象消亡时,自动被调用,用来释放对象占用的空间2.特点:   (1) 名字与 ...

  10. 不正常退出vim产生swp文件的解决方法

    vi -r README.txt恢复文件,这样上次意外退出没有保存的修改,就会覆盖文件. vi -r查看生成的交换文件 rm .README.txt.swp删除交换文件 搞定!下次打开文件就可以正常编 ...