利用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 2018-07-18 14:13 mumuxinfei 阅读(1143) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .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)---需求演进和产品迭代