Search code examples
javaspringreflectionannotationsaop

Stream through methods to find Rest mapping information


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();

Solution

  • 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);