Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ghevge opened this issue Aug 2, 2022 · 4 comments

Comments

@ghevge
Copy link

ghevge commented Aug 2, 2022

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

@brunorsch
Copy link

brunorsch commented Aug 4, 2022

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.

@ghevge
Copy link
Author

ghevge commented Aug 5, 2022

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.

@brunorsch
Copy link

brunorsch commented Aug 5, 2022

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 :)

@ghevge
Copy link
Author

ghevge commented Aug 5, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants