通过 Proxy、InvocationHandler 和自定义 Annotations 实现参数验证
Implement parameter validation through Proxy, InvocationHandler and custom 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 的行为,因为我想自己实现它,但我不太清楚如何去做。
注释本身没有任何意义(在大多数情况下):它们应该是 processed/handled somewhere else,并且,正如您正确指出的那样,可能 一个隐藏的 Proxy
。
I want to imitate the behavior of Spring because I want to implement this myself, but I don't quite know how to.
让我们介绍一些没有 Spring 的最小示例,仅使用纯 jdk 设施:
假设我们有一些 interface
(目前在 JDK 我们不能轻易地从 class 到 class,但是我们可以创建一个 class 实现一些 interface
s):
public interface PostService {
Post getPostById(@PostId long id);
}
以及一个简单的实现:
public class SimplePostService implements PostService {
@Override
public Post getPostById(@PostId long id) {
return new Post(id);
}
}
现在,让我们执行验证:如果通过的 id
为负数,我们将抛出 IllegalArgumentException
为此,我们可以使用 jdk 的 Dynamic Proxy Classes. The interesting part is the Proxy#newProxyInstance
方法:
- 它接受:
ClassLoader
- 目标 class 将
implement
的 interface
数组
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
)
- 创建代理 class,
implements PostService
- 拦截对代理的调用:
- 它遍历参数,如果它 无效 (在上面的例子中为负数),它抛出
IllegalArgumentException
- 否则,它调用目标,真正的服务
上面的代码应该演示 Spring 如何执行其 @Valid
/@Transactional
/@Cacheable
处理的基本原理。当然,它使用更复杂的方法:
- 它可以动态创建子classes(不仅创建 classes 的实例,
implement
一些接口`)
- 它提供了一个框架,可以更容易地实现这种逻辑,参见Spring AOP
我正在尝试模仿 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 的行为,因为我想自己实现它,但我不太清楚如何去做。
注释本身没有任何意义(在大多数情况下):它们应该是 processed/handled somewhere else,并且,正如您正确指出的那样,可能 一个隐藏的 Proxy
。
I want to imitate the behavior of Spring because I want to implement this myself, but I don't quite know how to.
让我们介绍一些没有 Spring 的最小示例,仅使用纯 jdk 设施:
假设我们有一些 interface
(目前在 JDK 我们不能轻易地从 class 到 class,但是我们可以创建一个 class 实现一些 interface
s):
public interface PostService {
Post getPostById(@PostId long id);
}
以及一个简单的实现:
public class SimplePostService implements PostService {
@Override
public Post getPostById(@PostId long id) {
return new Post(id);
}
}
现在,让我们执行验证:如果通过的 id
为负数,我们将抛出 IllegalArgumentException
为此,我们可以使用 jdk 的 Dynamic Proxy Classes. The interesting part is the Proxy#newProxyInstance
方法:
- 它接受:
ClassLoader
- 目标 class 将
implement
的 InvocationHandler
- 这是我们可以嵌入任何逻辑的地方
interface
数组
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
) - 创建代理 class,
implements PostService
- 拦截对代理的调用:
- 它遍历参数,如果它 无效 (在上面的例子中为负数),它抛出
IllegalArgumentException
- 否则,它调用目标,真正的服务
- 它遍历参数,如果它 无效 (在上面的例子中为负数),它抛出
上面的代码应该演示 Spring 如何执行其 @Valid
/@Transactional
/@Cacheable
处理的基本原理。当然,它使用更复杂的方法:
- 它可以动态创建子classes(不仅创建 classes 的实例,
implement
一些接口`) - 它提供了一个框架,可以更容易地实现这种逻辑,参见Spring AOP