java - 通过 Proxy、InvocationHandler 和自定义 Annotations 实现参数验证
问题描述
我正在尝试模仿 Spings 验证和自定义约束注释,以便在参数到达我的服务器上的端点时验证参数。我了解了 Proxy 和 InvocationHandler 的基础知识,但我被困在单个注释如何执行某些代码上。举个例子:
我想要用注释的参数
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface PostId {}
在后台自动验证。因此,理想情况下,对于任何带有参数和此注释的方法,我都可以创建验证测试。
public class Main {
public void doSomething(@PostId String id) {
// id should be evaluated in the background and not explicitly
}
public static void main(String[] args) {
String test = "test";
doSomething(test);
}
}
编辑:我明确表示我想模仿 Spring 的行为,因为我想自己实现它,但我不太知道如何去做。
解决方案
注释本身并不意味着任何东西(在大多数情况下):它们应该在其他地方处理/处理 ,并且,正如您正确指出的那样,可能有一个隐藏的.Proxy
我想模仿 Spring 的行为,因为我想自己实现它,但我不太知道如何实现。
让我们介绍一些没有 Spring 的最小示例,仅使用纯 jdk 工具:
想象一下我们有一些interface
(目前在 JDK 中我们不能轻易子类化一个类,但我们可以创建一个实现 someinterface
的类):
public interface PostService {
Post getPostById(@PostId long id);
}
还有一个简单的实现:
public class SimplePostService implements PostService {
@Override
public Post getPostById(@PostId long id) {
return new Post(id);
}
}
IllegalArgumentException
现在,让我们执行验证:如果id
传递的是负数,我们将抛出
为此,我们使用 can jdk 的Dynamic Proxy Classes。有趣的部分是Proxy#newProxyInstance
方法:
- 它接受:
ClassLoader
interface
目标类将的 s 数组implement
InvocationHandler
- 这是我们可以嵌入任何逻辑的地方
public class Main {
public static void main(String[] args) {
// Target, real service
SimplePostService targetPostService = new SimplePostService();
PostService proxiedService = (PostService) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{PostService.class}, (proxy, method, methodArguments) -> {
Parameter[] parameters = method.getParameters();
// iterate over all arguments
for (int i = 0; i < methodArguments.length; i++) {
PostId annotation = parameters[i].getAnnotation(PostId.class);
// if annotation is present
if (annotation != null) {
long id = ((long) methodArguments[i]);
// And argument is not valid
if (id < 0) {
// then throw
throw new IllegalArgumentException("negative id = " + id);
}
}
}
// else invoke target service
return method.invoke(targetPostService, methodArguments);
});
System.out.println(proxiedService.getPostById(-1));
}
}
上面的代码:
- 创建一个目标,真正的服务 (
SimplePostService
) - 创建一个代理类,即
implements PostService
- 拦截对代理的调用:
- 它迭代参数,如果它无效(在上面的例子中为负),它会抛出
IllegalArgumentException
- 否则,它调用目标,真正的服务
- 它迭代参数,如果它无效(在上面的例子中为负),它会抛出
@Valid
上面的代码应该演示了 Spring 如何执行其//处理的基本原理@Transactional
。@Cacheable
当然,它使用更复杂的方法:
- 它可以动态创建子类(不仅可以创建
implement
某些接口的类的实例) - 它提供了一个框架来更容易实现这种逻辑,请参阅Spring AOP
推荐阅读
- r - NA 按行移动平均填充
- c - __asm 和 PCLint 9.0L 错误 14:先前定义的符号 'TS_IntDisableAsm(void)'
- android - 我的带有 viewmodel 的 CameraX 无法在配置更改中幸存
- javascript - Bootstrap v3.3.5:下拉高度不可编辑
- r - 在 Slurm 中使用作业数组并行化 Rscript
- laravel - 将默认值放入 url
- huawei-mobile-services - 有没有办法通过华为开发者控制台控制暂停或宽限期功能?
- flutter - 在 Flutter 中为菜单创建点前导
- python - wx python神秘地向GUI输出添加换行符
- awk - awk 中 NF 的后减量