1

I have a simple, but incredibly ugly looking method. The issue I am having is that I feel this can be done a million times more elegantly. In addition, I would also like to scan a method for more than one annotation, and not just the Rest endpoint declarations.

I feel this can be done through a stream of Annotations[] (method.getDeclaredAnnotations()) and filtered by a List<Annotation> restEndpointAnnotationsAndOtherAnnotations, but I cannot seem to get it to work.

Any help would be greatly appreciated. I think it's probably a fairly fun challenge for some people. The primary issue I am getting (I think) is trying to convert Class<? extends Annotation> to Annotation, but perhaps I am missing the mark.

public RestEndpoint mapToRestEndpoint(Method method) {

        String url = null;

        if (method.isAnnotationPresent(GetMapping.class)) {
            url = method.getAnnotation(GetMapping.class).value()[0];
        } else
        if (method.isAnnotationPresent(PutMapping.class)) {
            url = method.getAnnotation(PutMapping.class).value()[0];
        } else
        if (method.isAnnotationPresent(PostMapping.class)) {
            url = method.getAnnotation(PostMapping.class).value()[0];
        } else
        if (method.isAnnotationPresent(PatchMapping.class)) {
            url = method.getAnnotation(PatchMapping.class).value()[0];
        } else
        if (method.isAnnotationPresent(DeleteMapping.class)) {
            url = method.getAnnotation(DeleteMapping.class).value()[0];
        } else
        if (method.isAnnotationPresent(RequestMapping.class)) {
            url = method.getAnnotation(RequestMapping.class).value()[0];
        } else return null;

        return new RestEndpoint(url, true);
    }

Where RestEndpoint is a simple POJO

@Value
public class RestEndpoint {

    @NonNull String endpoint;
    boolean isPublic;
}

I can actually find where it matches the Rest mapping using streams, but I cannot then apply the .value() method to it (since it doesn't know what annotation it is, and would be just as tedious to then cast to multiple annotation types)

EDIT: This is a pretty handy way of getting the information on methods if anyone is interested.

ApplicationContext context = ((ContextRefreshedEvent) event).getApplicationContext();  
context.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Richard
  • 121
  • 8
  • What is the point of this? Spring already internally has all this information why do this again? – M. Deinum Apr 23 '20 at 08:01
  • The point is to scan through all annotations in RestController classes and return a list of endpoints. I'm trying to attach an annotation that declares endpoints as publicly available or not (see https://stackoverflow.com/questions/61350383/managing-endpoint-security-and-testing-approaches?noredirect=1#comment108582675_61350383). Your response got me to look a bit further and I found this which will definitely help a lot (https://stackoverflow.com/questions/43541080/how-to-get-all-endpoints-list-after-startup-spring-boot) – Richard Apr 23 '20 at 10:07

1 Answers1

1

Problem is in getAnnotation as it need concrete annotation class to know that it has somethings like value(). You can create helper method that try to invoke value() on given object and do other parsing.

private String getUrl(Method method, Class<? extends Annotation> annotationClass){
    Annotation annotation = method.getAnnotation(annotationClass);
    String[] value;
    try {
        value = (String[])annotationClass.getMethod("value").invoke(annotation);
    } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
        return null;
    }
    return value[0];
}

Then use it like this:

String url = Stream.of(
    GetMapping.class, PutMapping.class, PostMapping.class, PatchMapping.class, DeleteMapping.class, RequestMapping.class)
    .filter(clazz -> method.isAnnotationPresent(clazz))
    .map(clazz -> getUrl(method, clazz))
    .findFirst().orElse(null);
lczapski
  • 4,026
  • 3
  • 16
  • 32