首页 > 解决方案 > 通过 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 的行为,因为我想自己实现它,但我不太知道如何去做。

标签: javaspringreflectionproxy

解决方案


注释本身并不意味着任何东西(在大多数情况下):它们应该在其他地方处理/处理 ,并且,正如您正确指出的那样,可能有一个隐藏的.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

推荐阅读