java—通过代理、invocationhandler和自定义注解实现参数验证

v2g6jxz6  于 2021-06-29  发布在  Java
关注(0)|答案(2)|浏览(347)

我尝试模拟spings验证和自定义约束注解,以便在参数到达服务器上的端点时进行验证。我已经掌握了代理和调用处理程序的基本知识,但是我还停留在一个注解如何执行一些代码上。举个例子:
我要用

@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);
    }
}

edit:我明确地说我想模仿spring的行为,因为我想自己实现它,但是我不太知道怎么做。

vsaztqbk

vsaztqbk1#

注解本身并不意味着什么(在大多数情况下):它们应该在其他地方进行处理/处理,而且,正如您正确指出的,可能存在一个隐藏的注解 Proxy .
我想模仿spring的行为,因为我想自己实现它,但是我不太知道怎么做。
让我们介绍一些没有spring的最小示例,只使用纯jdk工具:
想象一下我们有一些 interface (目前在jdk中,我们不能很容易地对一个类进行子类化,但是我们可以创建一个类来实现一些 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);
    }
}

现在,让我们执行验证:我们将 IllegalArgumentException 如果 id 通过是否定的
为此,我们可以使用jdk的动态代理类。有趣的是 Proxy#newProxyInstance 方法:
它接受: ClassLoader 数组 interface 目标类将
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 否则,它将调用目标、真正的服务
上面的代码应该演示spring如何执行其功能的基本原理 @Valid / @Transactional / @Cacheable 处理。当然,它使用了更复杂的方法:
它可以动态地创建子类(不仅创建 implement 一些接口`)
它提供了一个框架,使得实现这种逻辑更加容易,请参见springaop

5vf7fwbs

5vf7fwbs2#

您应该编写bean来处理自定义注解,它将实现javax.validation.constraintvalidator接口。例如:

@Component
public class PostId implements ConstraintValidator<PostId, String> {
    @Override
    public boolean isValid(final String value, final ConstraintValidatorContext context) {
    //your validation
  }
}

第一个generic postid是注解类型,第二个generic是参数类型(例如string)。您应该重写isvalid方法并将validate的逻辑放入其中。

相关问题