reflections icon indicating copy to clipboard operation
reflections copied to clipboard

reflections doesn't return the list of classes annotated with RequestMapping

Open ghevge opened this issue 3 years ago • 4 comments

I'm trying to use reflections 0.10.2 in an open JDK 17 spring boot project to get the list of controller classes, but for some reasons I always get an empty list of classes.

This is how my code looks like:

 Reflections reflections = new Reflections(new ConfigurationBuilder().forPackage("com.kp.mw.controllers")
                    .filterInputsBy(new FilterBuilder().includePackage("com.kp.mw.controllers"))
                    .setScanners(TypesAnnotated));
 Set<Class<?>> annotated = reflections.get(SubTypes.of(TypesAnnotated.with(RequestMapping.class)).asClass());

A controller class looks like this:

package com.kp.mw.controllers;

import java.util.UUID;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.kp.mw.dtos.ProductDTO;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/product")
public class ProductController {
    @PostMapping("/")
    public void createProduct(ProductDTO product) {
        // create product
    }

    @PutMapping("/{id}")
    public void updateProduct(ProductDTO product, @PathVariable UUID id) {
        // update product
    }
}

All my controllers are under com.kp.mw.controllers and are annotated with @RequestMapping.

Any idea what is wrong here?

Thanks

ghevge avatar Aug 02 '22 18:08 ghevge

I was having a similar issue, Reflections wasn't able to find classes when running from docker. I bet it's caused due to the Java modules feature introduced in JDK 9, since Reflection scans with package names are working very well in our JDK8 projects.

Using the following snippet, I was able to fix it, hope it helps:

 new Reflections(ConfigurationBuilder.build()
  // Here we SET the URLs (no adding, to remove any eventually pre-setted but unnecessary URLs) 
  // using ClasspathHelper to get the URL for the given package
  // For some reason, without this, scanning takes up to 20 seconds to finish
  // and it scans a lot of classes (around 16k)
  .setUrls(ClasspathHelper.forPackage(mainPackage))
  .addScanners(TypesAnnotated)
  // As people said in other issues, you need to explicitly set expandSuperTypes to false 
  // if you don't need it, or it may increase scanning time
  .setExpandSuperTypes(false))
  .get(TypesAnnotated.with(YourAnnotation.class).asClass())

Many thanks to @vlborkunov for his reply on #373, providing this solution.

brunorsch avatar Aug 04 '22 22:08 brunorsch

Thanks for the reply! I'm running in a container too. Anyhow, in the end I've decided to ditch the use of the Reflections library as it seems is not proactively maintained. Even the latest version being flagged as having some security issues.

I've manage to get the same result with spring functionality.

ghevge avatar Aug 05 '22 11:08 ghevge

I've managed to get the same result with spring functionality.

Amazing! Could I ask you to provide an example of how you did it? Just to serve as a source if someone comes across a similar problem in the future :)

brunorsch avatar Aug 05 '22 20:08 brunorsch

This is what I've done:

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;

private static final String CONTROLLERS_PACKAGE = "com.test.controllers";
private static List<MethodWrapper> deleteApis = new ArrayList<>();
private static List<MethodWrapper> getApis = new ArrayList<>();
private static List<MethodWrapper> postApis = new ArrayList<>();
private static List<MethodWrapper> putApis = new ArrayList<>();


public void initializeApis() {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(RequestMapping.class));
        for (BeanDefinition bd : scanner.findCandidateComponents(CONTROLLERS_PACKAGE)) {
            if (bd instanceof AnnotatedBeanDefinition) {
                AnnotationMetadata meta = ((AnnotatedBeanDefinition) bd).getMetadata();
                MultiValueMap<String, Object> beanAnnotations = meta
                        .getAllAnnotationAttributes(RequestMapping.class.getName());
                String beanPath = ((String[]) beanAnnotations.get(PATH).get(0))[0];
                populateAPIsList(putApis, meta, PutMapping.class.getName(), beanPath);
                populateAPIsList(getApis, meta, GetMapping.class.getName(), beanPath);
                populateAPIsList(deleteApis, meta, DeleteMapping.class.getName(), beanPath);
                populateAPIsList(postApis, meta, PostMapping.class.getName(), beanPath);
            }
        }
    }


    private void populateAPIsList(List<MethodWrapper> list, AnnotationMetadata meta, String annotationName,
            String beanPath) {
        Set<MethodMetadata> methodsMeta = meta.getAnnotatedMethods(annotationName);
        for (MethodMetadata methodMeta : methodsMeta) {
            MultiValueMap<String, Object> methodAnnotationAttributes = methodMeta
                    .getAllAnnotationAttributes(annotationName);
            String methodPath = ((String[]) methodAnnotationAttributes.get(PATH).get(0))[0];
            boolean requiresLogin = methodMeta.isAnnotated(RequiresLogin.class.getName());
            MultiValueMap<String, Object> permissionAnnotationAttributes = methodMeta
                    .getAllAnnotationAttributes(RequiresPermission.class.getName());
            String permission = null;
            if (permissionAnnotationAttributes != null) {
                permission = ((String) permissionAnnotationAttributes.get(VALUE).get(0));
            }
            list.add(new MethodWrapper(methodMeta.getMethodName(), getPathPattern(beanPath + methodPath), requiresLogin,
                    permission));
        }
    }

ghevge avatar Aug 05 '22 20:08 ghevge