关于java:你能用反射找到包中的所有类吗?

Can you find all classes in a package using reflection?

在给定的包中是否可以找到所有类或接口?(很快看一眼,如Package,似乎没有。)


由于类加载器的动态特性,这是不可能的。类加载器不需要告诉VM它可以提供哪些类,相反,它们只是为类处理请求,必须返回类或抛出异常。

但是,如果您编写自己的类加载器,或者检查类路径和它的jar,就可以找到这些信息。这将通过文件系统操作实现,而不是反射。甚至可能有一些库可以帮助您做到这一点。

如果有类被生成或远程传递,您将无法发现这些类。

通常的方法是在文件中注册您需要访问的类,或者在其他类中引用它们。或者只是在命名时使用约定。

附录:反射库将允许您在当前类路径中查找类。它可以用于获取包中的所有类:

1
2
3
4
 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses =
     reflections.getSubTypesOf(Object.class);


您可能应该看看开放源码反射库。有了它,你可以很容易地实现你想要的。

首先,设置反射索引(由于默认情况下禁用搜索所有类,因此有点混乱):

1
2
3
4
5
6
7
8
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

然后可以查询给定包中的所有对象:

1
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);


google guava 14包含了一个新的类ClassPath,它有三种扫描顶级类的方法:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

有关更多信息,请参阅ClassPathjavadocs。


您可以使用使用ClassLoader的方法1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */

private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */

private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName +"." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

第二章1此方法最初取自http://snippets.dzone.com/posts/show/4831,该方法由互联网档案馆存档,链接到现在。该代码段也可以在https://dzone.com/articles/get all classes within package.


春天

这个例子是针对Spring4的,但是您也可以在早期版本中找到ClassPath扫描器。

1
2
3
4
5
6
7
8
9
10
11
12
13
// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

谷歌番石榴

注意:在版本14中,API仍然标记为@beta,所以在生产代码中要小心。

1
2
3
4
5
6
7
8
final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}


你好。我一直对上面的解决方案(以及其他站点)有一些问题。作为开发人员,我正在为一个API编程一个插件。API防止使用任何外部库或第三方工具。设置还包括JAR或zip文件中的代码和直接位于某些目录中的类文件的混合。所以我的代码必须能够在每个设置中运行。经过大量的研究,我想出了一种方法,至少能在95%的所有可能的设置中工作。

下面的代码基本上是可以一直工作的overkill方法。

代码:

此代码扫描给定包中包含的所有类。它只适用于当前ClassLoader中的所有类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
 * Private helper method
 *
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */

private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname +"." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 *
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */

private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 *
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */

public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                       "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        +" does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname +" ("
                            + url.getPath()
                            +") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                       "IOException was thrown when trying to get all resources for"
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        +" does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
               "IOException was thrown when trying to get all resources for"
                        + pckgname, ioex);
    }

    return classes;
}

这三种方法为您提供了在给定包中查找所有类的能力。您可以这样使用它:

1
getClassesForPackage("package.your.classes.are.in");

解释如下:

方法首先得到当前的ClassLoader。然后它获取包含所述包的所有资源并迭代这些URLs,然后创建URLConnection并确定我们拥有的URL类型。它可以是目录(FileURLConnection或JAR或zip文件(JarURLConnection中的目录)。根据连接的类型,我们将调用两种不同的方法。

首先,让我们看看如果它是一个FileURLConnection会发生什么。它首先检查传递的文件是否存在并且是一个目录。如果是这样的话,它会检查它是否是类文件。如果是这样,将创建一个Class对象并将其放入ArrayList中。如果它不是一个类文件,而是一个目录,那么我们只需迭代它并做同样的事情。将忽略所有其他案例/文件。

如果URLConnectionJarURLConnection的话,将调用另一个私有helper方法。此方法迭代zip/jar存档中的所有条目。如果一个条目是类文件并且在包中,那么将创建一个Class对象并将其存储在ArrayList中。

在分析完所有资源之后,它(主方法)返回包含给定包中所有类的ArrayList,当前ClassLoader知道这些类。

如果该过程在任何时候失败,将抛出一个ClassNotFoundException,其中包含关于确切原因的详细信息。


通常,类装入器不允许扫描类路径上的所有类。但通常唯一使用的类加载器是urlcllassloader,从中我们可以检索目录和JAR文件的列表(参见geturls),并逐个打开它们以列出可用的类。这种方法称为类路径扫描,在扫描和反射中实现。

1
2
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

另一种方法是使用Java可插入注释处理API来编写注释处理器,它将在编译时收集所有注释的类,并构建用于运行时使用的索引文件。此机制在ClassIndex库中实现:

1
2
3
4
5
6
// package-info.java
@IndexSubclasses
package my.package;

// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

注意,由于Java编译器自动发现在类路径上找到的任何处理器,所以扫描完全自动化是不需要额外设置的。


不使用任何额外的库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
    }
}


我写了FastClassPathScanner来解决这个问题。它处理许多不同类型的类路径扫描任务,有一个简单的API,可与许多不同的类加载器和类路径环境一起工作,经过仔细的并行处理,并针对高速和低内存消耗进行了高度优化。它甚至可以生成类图的graphviz可视化,显示类是如何相互连接的。

对于在给定包中查找所有类或接口的原始问题,可以执行以下操作:

1
2
List<String> classNames = new FastClasspathScanner("com.mypackage").scan()
    .getNamesOfAllClasses();

这有许多可能的变体——有关完整信息,请参阅文档(链接在上面)。


我就是这样做的。我扫描所有子文件夹(子包),但不尝试加载匿名类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   *
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */

  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(),"UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname +" does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname +" does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for" + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);            
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname +" (" + directoryFile.getPath() +") does not appear to be a valid package");
          }
      }
      return classes;
  }

我整合了一个简单的Github项目来解决这个问题:

https://github.com/ddopson/java-class-enumerator

它既适用于基于文件的类路径,也适用于JAR文件。

如果您在签出项目后运行"make",它将打印出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 Cleaning...
rm -rf build/
 Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
 Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ .
jar cf build/ClassEnumerator.jar -C build/classes/ pro
 Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg'  =>  class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar  test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/'  =>  class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class'  =>  class 'null'
ClassDiscovery: JarEntry 'test/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Tests Passed.

另见我的其他答案


是的,使用一些API可以,以下是我喜欢的方式,面对这个问题,我使用Hibernate核心,必须找到用特定注释注释的类。

将这些设置为自定义注释,使用它可以标记要提取的类。

1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {

}

然后用它来标记你的班级

1
2
3
4
@EntityToBeScanned
public MyClass{

}

使此实用程序类具有以下方法

1
2
3
4
5
6
7
8
9
public class ClassScanner {

    public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
        Reflections reflections = new Reflections(".*");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
        return annotated;
    }

}

调用AllFoundClassesAnnotatedWithTitleToBescanned()方法以获取找到的一组类。

你需要以下给出的libs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    reflections</artifactId>
    <version>0.9.10</version>
</dependency>

我一直在尝试使用反射库,但在使用它时遇到了一些问题,我应该包括太多的jar,只是为了简单地获取包上的类。

我将发布在这个重复问题中找到的解决方案:如何获取包中的所有类名?

答案是由SP00M编写的;我添加了一些更正以使其生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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;
    }

}

要使用它,只需调用find方法,如本例中提到的sp00n:如果需要,我已经添加了类实例的创建。

1
2
3
4
5
6
7
8
9
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();
    //...
}

您需要查找类路径中的每个类加载器条目:

1
2
3
4
5
6
7
8
    String pkg ="org/apache/commons/lang";
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL[] urls = ((URLClassLoader) cl).getURLs();
    for (URL url : urls) {
        System.out.println(url.getFile());
        File jar = new File(url.getFile());
        // ....
    }

如果条目是目录,只需在右子目录中查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (jar.isDirectory()) {
    File subdir = new File(jar, pkg);
    if (!subdir.exists())
        continue;
    File[] files = subdir.listFiles();
    for (File file : files) {
        if (!file.isFile())
            continue;
        if (file.getName().endsWith(".class"))
            System.out.println("Found class:"
                    + file.getName().substring(0,
                            file.getName().length() - 6));
    }
}

如果条目是文件,并且是JAR,请检查它的zip条目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
else {
    // try to open as ZIP
    try {
        ZipFile zip = new ZipFile(jar);
        for (Enumeration<? extends ZipEntry> entries = zip
                .entries(); entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (!name.startsWith(pkg))
                continue;
            name = name.substring(pkg.length() + 1);
            if (name.indexOf('/') < 0 && name.endsWith(".class"))
                System.out.println("Found class:"
                        + name.substring(0, name.length() - 6));
        }
    } catch (ZipException e) {
        System.out.println("Not a ZIP:" + e.getMessage());
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

现在,一旦所有类名都包含在包中,就可以尝试用反射加载它们,并分析它们是类还是接口等。


几乎所有的答案要么使用Reflections,要么从文件系统读取类文件。如果您试图从文件系统中读取类,那么当您将应用程序打包为JAR或其他应用程序时可能会出错。此外,您可能不想为此目的使用单独的库。

这里是另一种方法,它是纯Java而不依赖于文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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不是必须的。您可以使用for循环而不是流。你可以这样测试它

1
2
3
4
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);
}


亚历山德·布洛姆斯克?在使用maven时,ld的解决方案对于参数化测试@RunWith(Parameterized.class)不起作用。测试名称正确,并且在找到但未执行的位置也正确:

1
2
3
4
5
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec

这里也报告了类似的问题。

在我的例子中,@Parameters正在一个包中创建每个类的实例。当在IDE中本地运行时,这些测试工作得很好。但是,在运行maven时,没有找到与aleksander blomsk一起使用的类?LD溶液

我真的用大卫·P的灵感创作了下面的剪贴画?rsson对aleksander blomsk的评论?LD的回答:

1
2
3
4
5
6
7
Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
            .addUrls(ClasspathHelper.forJavaClassPath())
            .filterInputsBy(new FilterBuilder()
            .include(FilterBuilder.prefix(basePackage))));

Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);

我刚写了一个util类,它包括测试方法,你可以检查一下~

iteratepackageUtil.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package eric.j2se.reflect;

import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * an util to iterate class in a package,
 *
 * @author eric
 * @date Dec 10, 2013 12:36:46 AM
 */

public class IteratePackageUtil {
    /**
     * <p>

     * Get set of all class in a specified package recursively. this only support lib
     *
</p>
     * <p>

     * class of sub package will be included, inner class will be included,
     *
</p>
     * <p>

     * could load class that use the same classloader of current class, can't load system packages,
     *
</p>
     *
     * @param pkg
     *            path of a package
     * @return
     */

    public static Set<Class<? extends Object>> getClazzSet(String pkg) {
        // prepare reflection, include direct subclass of Object.class
        Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
                .filterInputsBy(new FilterBuilder().includePackage(pkg)));

        return reflections.getSubTypesOf(Object.class);
    }

    public static void test() {
        String pkg ="org.apache.tomcat.util";

        Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
        for (Class<? extends Object> clazz : clazzSet) {
            System.out.println(clazz.getName());
        }
    }

    public static void main(String[] args) {
        test();
    }
}

值得一提

如果要在某个包下列出所有类,可以使用Reflection,方法如下:

1
2
3
4
5
6
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));
}

这将创建一个类列表,稍后您可以根据需要使用它们。


如果您没有使用任何动态类加载器,那么您可以搜索类路径,并为每个条目搜索目录或JAR文件。


这是很有可能的,但如果没有像Reflections这样的附加库,就很难……很难,因为你还没有完整的工具来获取类名。而且,我选择了我的ClassFinder类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package play.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by LINKOR on 26.05.2017 in 15:12.
 * Date: 2017.05.26
 */

public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
    try {
        file = new JarFile(filePath);
    } catch (IOException e) {
        trouble = true;
    }
}

public List<String> findClasses(String pkg) {
    ArrayList<String> classes = new ArrayList<>();
    Enumeration<JarEntry> entries = file.entries();
    while (entries.hasMoreElements()) {
        JarEntry cls = entries.nextElement();
        if (!cls.isDirectory()) {
            String fileName = cls.getName();
            String className = fileName.replaceAll("/",        ".").replaceAll(File.pathSeparator,".").substring(0, fileName.lastIndexOf('.'));
            if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
        }
    }
    return classes;
}
}

基于@staale的回答,为了不依赖第三方库,我将通过检查第一个包的物理位置来实现文件系统方法,方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();

new java.io.File(
    klass.getResource(
       "/" + curPackage.replace("." ,"/")
    ).getFile()
).listFiles(
    new java.io.FileFilter() {
        public boolean accept(java.io.File file) {
            final String classExtension =".class";

            if ( file.isFile()
                && file.getName().endsWith(classExtension)
                // avoid inner classes
                && ! file.getName().contains("$") )
            {
                try {
                    String className = file.getName();
                    className = className.substring(0, className.length() - classExtension.length());
                    foundClassesDyn.add( Class.forName( curPackage +"." + className ) );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(System.out);
                }
            }

            return false;
        }
    }
);

foundClasses = foundClassesDyn.toArray(foundClasses);

如果您只是想加载一组相关的类,那么Spring可以帮助您。

Spring可以在一行代码中实例化实现给定接口的所有类的列表或映射。列表或映射将包含实现该接口的所有类的实例。

也就是说,作为从文件系统中加载类列表的替代方法,您只需在所有想要加载的类中实现相同的接口,而不管包是什么,并使用Spring为您提供所有类的实例。这样,您就可以加载(并实例化)您想要的所有类,而不管它们在哪个包中。

另一方面,如果您希望所有类都包含在一个包中,那么只需让包中的所有类实现一个给定的接口。


这是不可能的,因为包中的所有类都可能没有被加载,而您总是知道一个类的包。