我们可以传递方法参数的值和注释吗

时间:2019-12-03 16:06:21

标签: java reflection rest-assured

要管理庞大的文档,我对调用API的方法使用了自定义注释

@SwagRef(method = POST, url = "/my/api/{pathParam1}")
  public Response callMyAPI(
      @MyParam(name = "pathParam1", required = true, in = PATH) String p1,
      @MyParam(name = "param2", required = false, in = QUERY) String p2) {
    return given()
            .pathParam("pathParam1", p1)
            .queryParam("param2", p2)
            .get();
  }

有另一段代码可以验证Swagger / api / docs与注释。 但是,我想知道是否有可能在批注中使用所有这些已经呈现的数据,并拥有一个通用代码,可以在其中传递方法引用或参数引用,并可以使用批注构建RequestSpecification。

我尝试了反射,但是我无法使用方法中的反射来获取参数的值

我只能推断方法类型和API,因为它使用methodName和stackTrace保持不变

    private SwagRef defineSwaggerInfo() {
        List<StackTraceElement> stackTrace = asList(currentThread().getStackTrace());
        return stackTrace.stream()
            .map(tryOrNull(element -> Pair.with(element.getMethodName(), forName(element.getClassName()))))
            .filter(Objects::nonNull)
            .filter(pair -> MyAPI.class.isAssignableFrom(pair.getValue1()))
            .map(pair -> with(pair.getValue0(), asList(pair.getValue1().getDeclaredMethods())))
            .flatMap(
                tryOrNull(
                    pair ->
                        pair.getValue1().stream()
                            .filter(method -> Objects.equals(method.getName(), pair.getValue0()))
                            .peek(method -> method.setAccessible(true))
                            .map(method -> method.getAnnotation(SwagRef.class))))
            .filter(Objects::nonNull)
            .findFirst()
            .orElseThrow();
}

但是我无法使用泛型函数来使用方法参数来构建请求规范

我尝试查看AspectJ,但无法正确嵌入

1 个答案:

答案 0 :(得分:2)

无法通过反射从堆栈中获取实际参数值。实际上,甚至没有保证在那一点上正在进行的调用的参数值仍在堆栈中。

执行自动参数处理最接近的方法是在接口中声明方法并生成proxy

interface FrontEnd {
    public static FrontEnd get() {
        return (FrontEnd)Proxy.newProxyInstance(FrontEnd.class.getClassLoader(),
            new Class<?>[]{FrontEnd.class}, (proxy, method, args) -> {
                if(method.getDeclaringClass() == Object.class) {
                    switch(method.getName()) {
                        case "toString": return
                            FrontEnd.class.getName()+'@'+System.identityHashCode(proxy);
                        case "equals": return proxy == args[0];
                        case "hashCode": return System.identityHashCode(proxy);
                        default: throw new AssertionError();
                    }
                }
                SwagRef swagRef = method.getAnnotation(SwagRef.class);
                if(swagRef == null) throw new IncompatibleClassChangeError();
                MyParam[] p = Arrays.stream(method.getParameterAnnotations())
                    .map(pa -> Arrays.stream(pa)
                        .filter(a -> a.annotationType() == MyParam.class)
                        .findFirst().orElseThrow(
                            () -> new IllegalStateException("missing required @MyParam")))
                    .toArray(MyParam[]::new);
                Map<String,String> map = IntStream.range(0, args.length).boxed()
                    .filter(i -> p[i].required() || args[i] != null)
                    .collect(Collectors.toMap(i -> p[i].name(), i -> args[i].toString()));
                // do actual invocation logic here
                System.out.println(
                    "operation: "+swagRef.method()+' '+swagRef.url()+", "+map);
                return null;
        });
    }

    @SwagRef(method = POST, url = "/my/api/{pathParam1}")
    public Response callMyAPI(
        @MyParam(name = "pathParam1", required = true, in = PATH) String p1,
        @MyParam(name = "param2", required = false, in = QUERY) String p2);
}

您可以向该接口添加更多方法,以相同的方式处理,前提是它们都具有必要的注释。

从Java 9开始,您可以在private中使用interface方法,在这里我更喜欢。

interface FrontEnd {
    public static FrontEnd get() {
        return (FrontEnd)Proxy.newProxyInstance(FrontEnd.class.getClassLoader(),
            new Class<?>[]{FrontEnd.class}, FrontEnd::callImpl);
    }
    @SwagRef(method = POST, url = "/my/api/{pathParam1}")
    public Response callMyAPI(
        @MyParam(name = "pathParam1", required = true, in = PATH) String p1,
        @MyParam(name = "param2", required = false, in = QUERY) String p2);

    private static Object callImpl(Object proxy, Method method, Object[] args) {
        if(method.getDeclaringClass() == Object.class) {
            switch(method.getName()) {
                case "toString": return
                    FrontEnd.class.getName()+'@'+System.identityHashCode(proxy);
                case "equals": return proxy == args[0];
                case "hashCode": return System.identityHashCode(proxy);
                default: throw new AssertionError();
            }
        }
        SwagRef swagRef = method.getAnnotation(SwagRef.class);
        if(swagRef == null) throw new IncompatibleClassChangeError();
        MyParam[] p = Arrays.stream(method.getParameterAnnotations())
            .map(pa -> Arrays.stream(pa)
                .filter(a -> a.annotationType() == MyParam.class)
                .findFirst().orElseThrow(
                    () -> new IllegalStateException("missing required @MyParam")))
            .toArray(MyParam[]::new);
        Map<String,String> map = IntStream.range(0, args.length).boxed()
            .filter(i -> p[i].required() || args[i] != null)
            .collect(Collectors.toMap(i -> p[i].name(), i -> args[i].toString()));
        // do actual invocation logic here
        System.out.println("operation: "+swagRef.method()+' '+swagRef.url()+", "+map);
        return null;
    }
}

或者,您可以在接口和一个可能是非公共的帮助程序类之间拆分逻辑。