Can you find all classes in a package using reflection?

前端 未结 27 2211
不知归路
不知归路 2020-11-21 05:24

Is it possible to find all classes or interfaces in a given package? (Quickly looking at e.g. Package, it would seem like no.)

相关标签:
27条回答
  • 2020-11-21 05:56

    I've been trying to use the Reflections library, but had some problems using it, and there were too many jars I should include just to simply obtain the classes on a package.

    I'll post a solution I've found in this duplicate question: How to get all classes names in a package?

    The answer was written by sp00m; I've added some corrections to make it work:

    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.Enumeration;
    import java.util.LinkedList;
    import java.util.List;
    
    public final class ClassFinder {
    
        private final static char DOT = '.';
        private final static char SLASH = '/';
        private final static String CLASS_SUFFIX = ".class";
        private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";
    
        public final static List<Class<?>> find(final String scannedPackage) {
            final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            final String scannedPath = scannedPackage.replace(DOT, SLASH);
            final Enumeration<URL> resources;
            try {
                resources = classLoader.getResources(scannedPath);
            } catch (IOException e) {
                throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
            }
            final List<Class<?>> classes = new LinkedList<Class<?>>();
            while (resources.hasMoreElements()) {
                final File file = new File(resources.nextElement().getFile());
                classes.addAll(find(file, scannedPackage));
            }
            return classes;
        }
    
        private final static List<Class<?>> find(final File file, final String scannedPackage) {
            final List<Class<?>> classes = new LinkedList<Class<?>>();
            if (file.isDirectory()) {
                for (File nestedFile : file.listFiles()) {
                    classes.addAll(find(nestedFile, scannedPackage));
                }
            //File names with the $1, $2 holds the anonymous inner classes, we are not interested on them. 
            } else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {
    
                final int beginIndex = 0;
                final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
                final String className = file.getName().substring(beginIndex, endIndex);
                try {
                    final String resource = scannedPackage + DOT + className;
                    classes.add(Class.forName(resource));
                } catch (ClassNotFoundException ignore) {
                }
            }
            return classes;
        }
    
    }
    

    To use it just call the find method as sp00n mentioned in this example: I've added the creation of instances of the classes if needed.

    List<Class<?>> classes = ClassFinder.find("com.package");
    
    ExcelReporting excelReporting;
    for (Class<?> aClass : classes) {
        Constructor constructor = aClass.getConstructor();
        //Create an object of the class type
        constructor.newInstance();
        //...
    }
    
    0 讨论(0)
  • 2020-11-21 05:58

    If you're in Spring-land you can use PathMatchingResourcePatternResolver;

      PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
      Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
    
        Arrays.asList(resources).forEach(r->{
            ...
        });
    
    0 讨论(0)
  • 2020-11-21 06:00

    Almost all the answers either uses Reflections or reads class files from file system. If you try to read classes from file system, you may get errors when you package your application as JAR or other. Also you may not want to use a separate library for that purpose.

    Here is another approach which is pure java and not depends on file system.

    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.StandardLocation;
    import javax.tools.ToolProvider;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.regex.Pattern;
    import java.util.stream.Collectors;
    import java.util.stream.StreamSupport;
    
    public class PackageUtil {
    
        public static Collection<Class> getClasses(final String pack) throws Exception {
            final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
            return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
                    .map(javaFileObject -> {
                        try {
                            final String[] split = javaFileObject.getName()
                                    .replace(".class", "")
                                    .replace(")", "")
                                    .split(Pattern.quote(File.separator));
    
                            final String fullClassName = pack + "." + split[split.length - 1];
                            return Class.forName(fullClassName);
                        } catch (ClassNotFoundException e) {
                            throw new RuntimeException(e);
                        }
    
                    })
                    .collect(Collectors.toCollection(ArrayList::new));
        }
    }
    

    Java 8 is not a must. You can use for loops instead of streams. And you can test it like this

    public static void main(String[] args) throws Exception {
        final String pack = "java.nio.file"; // Or any other package
        PackageUtil.getClasses(pack).stream().forEach(System.out::println);
    }
    
    0 讨论(0)
  • 2020-11-21 06:01

    Without using any extra libraries:

    package test;
    
    import java.io.DataInputStream;
    import java.io.InputStream;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) throws Exception{
            List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
            for(Class c:classes){
                System.out.println("Class: "+c);
            }
        }
    
        public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{
    
            String dottedPackage = pack.replaceAll("[/]", ".");
            List<Class> classes = new ArrayList<Class>();
            URL upackage = cl.getResource(pack);
    
            DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
            String line = null;
            while ((line = dis.readLine()) != null) {
                if(line.endsWith(".class")) {
                   classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
                }
            }
            return classes;
        }
    }
    
    0 讨论(0)
  • 2020-11-21 06:04

    Worth mentioning

    If you want to have a list of all classes under some package, you can use Reflection the following way:

    List<Class> myTypes = new ArrayList<>();
    
    Reflections reflections = new Reflections("com.package");
    for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
        myTypes.add(Class.forName(s));
    }
    

    This will create a list of classes that later you can use them as you wish.

    0 讨论(0)
  • 2020-11-21 06:07

    It is not possible, since all classes in the package might not be loaded, while you always knows package of a class.

    0 讨论(0)
提交回复
热议问题