利用Aspectj实现Oval的自动参数校验

 

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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);
        }
    }
 
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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注解, 但无法利用)

1
public String echo1(@NotNull String msg) { }

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

1
OvalCheckHelper.validate(req);

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

 

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

1
2
3
4
5
Call(Before)
Pointcut{
    Pointcut Method
}
Call(After)

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

1
2
3
4
5
Pointcut{
    execution(Before)
    Pointcut Method
    execution(After)
}

  

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

1
2
3
4
5
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OvalArgsCheck {
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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);
                }
            }
 
        }
 
    }
 
 
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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的配置中, 需要如下配置.

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

  然后编写测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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支持, 不过这不算太难.
  采用切面编程, 某种程度上, 大大减少了重复代码的编写, 简单易配置, 提高了编程效率.

 

posted on   mumuxinfei  阅读(1143)  评论(0编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2015-07-18 H5版俄罗斯方块(5)---需求演进和产品迭代

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示