简体   繁体   中英

Can we pass value and annotation of method parameters

To manage swagger documentations I am using custom annotations for the methods which call the 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();
  }

There is a separate piece of code which validates the Swagger/api/docs vs the annotations. However I'm wondering is it possible to somehow use all this already presented data in the annotations and have a common code where I can pass the method reference or the parameter reference and the RequestSpecification can be built using the annotations.

I tried with reflection, but I'm unable to fetch the value of parameters using reflection from method

I was only able to deduce the method type and API since it's constant using the methodName and 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();
}

But I'm not able to come up with a generic function for Building the request spec using method parameters

I tried looking at AspectJ but wasn't able to embed it properly

There is no way to get the actual parameter values from the stack via Reflection. In fact, there's not even a guaranty that the parameter values of an ongoing invocation are still on the stack at that point.

The closest you can get to perform automated parameter processing, is to declare the methods in an interface and generate a 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);
}

You may add more methods to that interface, to be handled the same way, assuming that they all have the necessary annotations.

Starting with Java 9, you can use a private method in the interface , which I would prefer here.

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

Alternatively, you may split up the logic between the interface and a, possibly non-public, helper class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM