通过 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 实现一些 interfaces):

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