您可以使用反射找到包中的所有类吗?


527

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


2
仅供参考,解决方案Amit链接到了工程,尽管如果类路径中包含空格字符(也可能还有其他非字母数字字符),则存在错误。如果您在任何形式的生产代码中使用它,请参阅我对他的回答的评论以寻求解决方法。
基普

2
另请注意此帖子
barfuin 2014年

1
看到相关的答案:stackoverflow.com/a/30149061/4102160
Cfx

1
另请注意此帖子
sp00m

1
请参阅下面有关ClassGraph的答案,它是目前扫描类路径和模块路径的最可靠的方法。
卢克·哈奇森

Answers:


373

由于类装载机的动态特性,这是不可能的。类加载器不需要告诉VM它可以提供哪些类,而是它们只是处理类的请求,并且必须返回类或引发异常。

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

如果有一些生成或远程交付的类,您将无法发现这些类。

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

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

 Reflections reflections = new Reflections("my.project.prefix");

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

12
无法查询类名称已经困扰了我很长时间。当然,这很困难,并且性能可能有很大差异,对于某些类加载器,该列表是未定义的或未限制的,但是可以通过一些方法解决此问题。
Shiny先生和新安宇

16
请注意,此解决方案在默认情况下将不起作用,因为getSubTypesOf不会返回Object的子类型。有关如何配置SubTypeScanner的信息,请参阅AleksanderBlomskøld的解决方案。
亚历克斯·斯普林

17
思考需要番石榴。番石榴很大。版本14.0.1是2.1MB。
mike jones 2013年

3
没有为我工作。Mac OSX-反射依赖版本0.9.9-RC1(Maven)-JDK 1.7。重新考虑接受的答案。@AleksanderBlomskøld的答案是一个。
康斯坦丁诺斯·玛格丽特斯

68
如果返回的是一个空列表,请按以下方式初始化Reflections对象:Reflections Reflections = new Reflections(“ your.package.here”,new SubTypesScanner(false));
若奥·罗查·达席尔瓦

186

您可能应该看看开源的Reflections库。有了它,您可以轻松实现您想要的。

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

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

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

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

6
嗯,我们去了:code.google.com/p/reflections/issues/detail?id=122。默认情况下不包括对象,但是您可以重新设置对象。感谢您将我指向该库,太好了!
mtrc

1
我在Mac上使用此代码(与本机库有关)遇到了问题,但是使用.addUrls(ClasspathHelper.forJavaClassPath())代替上面的代码为我解决了这些问题。更少的代码!
大卫·派森(DavidPärsson),

3
如果有人想知道获取默认软件包的最简单方法是将前缀设为空String->“”。
JBA 2014年

2
“ Reflections”库具有棘手的许可证:github.com/ronmamo/reflections/blob/master/COPYING.txt。诀窍在于,许可证仅允许许可证本身免费使用。因此,要真正使用该库(而不是许可),每个人都必须联系作者并协商使用条款。
Serge Rogatch 2015年

3
Serge,我认为您误解了WTFPL:wtfpl.net我认为WTFPL意味着您可以自由地做任何想做的事,不仅可以使用许可证,还可以使用代码
Richo 2015年

122

Google Guava 14包含一个新类,ClassPath其中包含三种扫描顶级类的方法:

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

有关更多信息,请参见ClassPathjavadocs


这对我来说是反射无法做到的(没有共同的直接祖先,没有共同的注释)
Riccardo Cossu 2013年

1
正如我所提到下面留言ClassPath被打上@Beta,所以可能不适合某些个好主意......
基督教

1
如果说在不执行反射的情况下可以工作会有些奇怪,那么该解决方案无疑是使用反射(和类加载器)功能实现的。
Maarten Bodewes,2016年

5
我认为他的意思是其他答案中提到的反射库。
Christoph Leiter

如果使用番石榴28.1-jre版本,则可以在Java 11下工作。
gorjanz

111

您可以使用此方法1使用了ClassLoader

/**
 * 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,该文件由Internet Archive存档,现已链接。该代码段也可以从https://dzone.com/articles/get-all-classes-within-package获取


6
如果我的路径包含空格,我对此有疑问。URL类将空格转义为%20,但new File()构造函数将其视为文字百分号2零。我通过更改以下dirs.add(...)行来解决了这一问题: dirs.add(new File(resource.toURI())); 这也意味着我必须在URISyntaxExceptiongetClasses
Kip 2010年

19
您刚刚从dzone.com/articles/get-all-classes-within-package复制了 !请参考源下一次
RS

19
+1,因为此解决方案不需要外部库...从来没有,实际上,永远不要将您的代码与库随机耦合,只是为了实现这样的小目标。您是否知道您正在为攻击者增加潜在的攻击面?2015年11月的阿帕奇百科全书的问题发现,导致远程命令执行只是通过在应用程序部署在JBoss / Weblogic的[classpath中有Apache的百科全书foxglovesecurity.com/2015/11/06/...
sc0p

@Qix正确指出此代码不支持jar。为了支持jar和目录。如下所述更改了代码:
user1523177 '16

1
好的解决方案,但是如果将'Class.forName(String className)'替换为'Class.forName(String className,boolean initialize,ClassLoader loader)'似乎更好,这里'initialize = false;' 为了不创建类实例。
Andrei_N

99

弹簧

该示例适用于Spring 4,但是您也可以在早期版本中找到类路径扫描器。

// 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,因此请注意生产代码。

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

5
极好的答案。这里有太多的解决方案,这些解决方案是冗长的,未经测试的,行不通的!这个很棒:它简洁且经过测试(来自Guava)。很好!它很有用,值得更多的赞扬。
JeanValjean 2014年

不幸的是,ClassPathGuava中的类也被标记为@Beta:“在类或方法级别用@Beta批注标记的API可能会发生更改。可以在任何主要版本中以任何方式修改甚至删除它们。如果您的代码是本身就是一个库(即在您自己的控件之外的用户的CLASSPATH上使用),除非您将它们重新打包,否则不应该使用Beta API ...“ code.google.com/p/guava-libraries/#Important_Warnings
基督徒

@Christian好点,我没有注意到!谢谢。我将使用Spring类路径扫描器添加另一个答案,这肯定不是beta版本。
voho 2015年

要使用番石榴解决方案查找嵌套的静态类,getAllClasses()可以使用方法。
v.ladynev '16

1
如果从可执行jar运行,则Spring解决方案是唯一可行的解​​决方案。
路加福音

38

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

以下代码基本上是永远可以使用的矫over过正的方法。

编码:

此代码在给定的程序包中扫描其中包含的所有类。它仅适用于当前版本中的所有类ClassLoader

/**
 * 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;
}

这三种方法使您能够查找给定程序包中的所有类。
您可以这样使用它:

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

说明:

该方法首先获取电流ClassLoader。然后,它将获取包含所述程序包的所有资源并对其进行迭代URL。然后,它创建一个URLConnection并确定我们拥有哪种类型的UR1。它可以是目录(FileURLConnection),也可以是jar或zip文件中的目录(JarURLConnection)。根据连接的类型,我们将调用两种不同的方法。

首先让我们看看如果它是FileURLConnection
它首先检查传递的文件是否存在并且是目录。如果是这样,它将检查它是否是一个类文件。如果是这样,Class将创建一个对象并将其放在中ArrayList。如果它不是类文件而是目录,我们只需对其进行迭代并执行相同的操作。其他所有案例/文件将被忽略。

如果URLConnectionJarURLConnection,则将调用另一个私有帮助器方法。此方法遍历zip / jar存档中的所有条目。如果一个条目是类文件,并且在包的内部,Class则将创建一个对象并将其存储在中ArrayList

解析完所有资源后,它(main方法)将返回当前ArrayList包含的所有给定包中的类ClassLoader

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


4
此示例似乎需要import sun.net.www.protocol.file.FileURLConnection,它会在编译时生成警告(“警告:sun.net.www.protocol.file.FileURLConnection是Sun专有的API,在将来的发行版中可能会删除”)。有没有使用该类的替代方法,或者可以通过注释抑制警告?
基督教徒

此方法不适用于引导程序类,例如java.lang,java.util等中的类。可以通过获取System.getProperty(“ sun.boot.class.path”)并用:或;拆分找到这些类。(取决于OS),然后运行上述checkDirectory和checkJarFile的稍作修改的版本。
coderforlife

1
您可以使用connection.getClass()。getCanonicalName()。equals(“ sun.net.www.protocol.file.FileURLConnection”)来避免出现警告/错误。如果确实需要,您可以创建一个URLConnection,您认为应该使用sun.net.www.protocol.file.FileURLConnection,并将连接类的名称与您创建的类的名称进行比较。如果它们相同,则可以将其视为sun.net.www.protocol.file.FileURLConnection的实例,而不是在类名称更改时失败。
威廉·迪恩斯

1
@Christian您可以避免FileURLConnection做类似的事情: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }这就是我在代码中搜索JPA注释类的过程
Zardoz89,2016年

15

不使用任何额外的库:

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

当我在JAR中运行此代码时,它upackagenull... :(
Christian

对于软件包“ com.mycompany.beans”,将“ test”替换为“ com / mycompany / beans”
James Jithin16

4
使用此代码时,我得到一个空值。似乎仅在您的jar是可执行文件时才起作用
Alao

如果您从获得了软件包名称String pack = getPackage().getName();,则必须添加pack = pack.replaceAll("[.]", "/");
user2682877

13

通常,类加载器不允许扫描类路径中的所有类。但是通常唯一使用的类加载器是UrlClassLoader,我们可以从中检索目录和jar文件的列表(请参阅getURLs),并一一打开它们以列出可用的类。这种方法称为类路径扫描,是在Scannotation and Reflections中实现的

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

另一种方法是使用Java Pluggable Annotation Processing API编写注释处理器,该处理器将在编译时收集所有带注释的类并构建索引文件以供运行时使用。此机制在ClassIndex库中实现:

// package-info.java
@IndexSubclasses
package my.package;

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

请注意,由于Java编译器自动发现了在类路径上找到的任何处理器,因此扫描是完全自动化的,因此不需要其他设置。


会发现打包在Jar中的类吗?它似乎对我不起作用。
ass 2013年

您尝试使用哪种工具?
Sławek

我正在使用Reflections库。但是,在遵循@AleksanderBlomskøld针对该库的最新版本提到的解决方法之后,我开始工作了。
ass 2013年

嗨,我使用的是eclipse,无法使其正常运行,ClassIndex.getPackageClasses(“ my.package”)返回一个空的地图
Juan

11

当前列出给定包中所有类的最健壮的机制是ClassGraph,因为它可以处理最广泛的类路径规范机制,包括新的JPMS模块系统。(我是作者。)

List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames.addAll(scanResult.getAllClasses().getNames());
}

5

这是我的方法。我扫描所有子文件夹(子软件包),并且我不尝试加载匿名类:

   /**
   * 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;
  }  

4

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

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

它应同时适用于基于文件的类路径和jar文件。

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

 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. 

另请参阅我的其他答案


4

是的,您可以使用几个API,这是我喜欢的方式,它遇到了这个问题,当时我正在使用休眠核心,并且不得不查找带有特定注释的类。

使这些成为自定义注释,使用该注释可以标记要拾取的类。

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 {

}

然后像这样标记您的班级

@EntityToBeScanned 
public MyClass{

}

制作具有以下方法的实用程序类

public class ClassScanner {

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

}

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

您将需要以下给出的库

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>

3

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

    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());
        // ....
    }   

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

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条目:

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

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


您要为Jar文件中的包装输入什么?
凯尔·布​​莱恩汀汀

本示例将不涉及子包。也许这是某些人感兴趣的... @ mr-tea只需指定您要寻找的软件包。我将其放在一个项目中,在该项目中指定一个测试包,对其进行编译和打包,然后从JAR的main方法中调用该示例。像魅力一样工作。:)
基督教徒

3

我一直在尝试使用Reflections库,但是在使用它时遇到了一些问题,仅为了简单地获取包中的类,我应该包含太多jar。

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

答案写由sp00m ; 我添加了一些更正使其生效:

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

}

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

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();
    //...
}

3

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

IteratePackageUtil.java:

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

3

@RunWith(Parameterized.class)使用Maven时,AleksanderBlomskøld的解决方案不适用于参数化测试。测试的名称正确,也可以找到但未执行:

-------------------------------------------------------
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时,AleksanderBlomskøld的解决方案找不到任何类。

我确实做到了以下几点,它的灵感来自DavidPärsson对AleksanderBlomskøld的回答的评论:

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

3

那这个呢:

public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
    final String pkgPath = pkgName.replace('.', '/');
    final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
    final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();

    Path root;
    if (pkg.toString().startsWith("jar:")) {
        try {
            root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
        } catch (final FileSystemNotFoundException e) {
            root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
        }
    } else {
        root = Paths.get(pkg);
    }

    final String extension = ".class";
    try (final Stream<Path> allPaths = Files.walk(root)) {
        allPaths.filter(Files::isRegularFile).forEach(file -> {
            try {
                final String path = file.toString().replace('/', '.');
                final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
                allClasses.add(Class.forName(name));
            } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
            }
        });
    }
    return allClasses;
}

然后可以重载该函数:

public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
    return getClassesForPackage(pkg.getName());
}

如果您需要测试:

public static void main(final String[] argv) throws IOException, URISyntaxException {
    for (final Class<?> cls : getClassesForPackage("my.package")) {
        System.out.println(cls);
    }
    for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
        System.out.println(cls);
    }
}

如果您的IDE没有导入助手:

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

有用:

  • 从您的IDE

  • 用于JAR文件

  • 没有外部依赖


2

几乎所有答案都使用Reflections或读取文件系统中的类文件。如果尝试从文件系统读取类,则将应用程序打包为JAR或其他格式时,可能会出错。同样,您可能不想为此使用单独的库。

这是另一种纯Java方式,不依赖于文件系统。

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循环代替流。你可以这样测试

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

1
它之所以不是很有用,是因为:需要使用JDKToolProvider.getSystemJavaCompiler(),此代码不会扫描嵌套的程序包。
v.ladynev '16

我无法使其与外部罐子包装一起使用
Enrico Giurin

1

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


1

值得一提

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

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

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


1

这是很有可能的,但是没有额外的库Reflections就很难了……
这很困难,因为您没有获得类名的完整工具。
而且,我采用了我ClassFinder班上的代码:

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

0

基于@Staale的答案,并尝试不依赖第三方库,我将通过检查以下内容来实现文件系统方法:

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

0

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

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

话虽如此,作为将类列表从文件系统中加载的一种替代方法,您只需在要加载的所有类中实现相同的接口即可,而不必考虑软件包,并使用Spring为您提供所有这些类的实例。这样,您可以加载(并实例化)所需的所有类,而不管它们位于哪个包中。

另一方面,如果您希望将它们全部包含在包中,则只需让该包中的所有类都实现给定的接口即可。


0

纯Java:FindAllClassesUsingPlainJavaReflectionTest.java

@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {

  private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
    log.error(throwable.getLocalizedMessage());
    return new RuntimeException(throwable);
  };

  private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {

    Locale locale = Locale.getDefault();
    Charset charset = StandardCharsets.UTF_8;
    val fileManager = ToolProvider.getSystemJavaCompiler()
                                  .getStandardFileManager(/* diagnosticListener */ null, locale, charset);

    StandardLocation location = StandardLocation.CLASS_PATH;
    JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
    Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
    val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
                             .getOrElseThrow(asRuntimeException);

    String pathToPackageAndClass = basePackageName.replace(".", File.separator);
    Function<String, String> mapToClassName = s -> {
      String prefix = Arrays.stream(s.split(pathToPackageAndClass))
                            .findFirst()
                            .orElse("");
      return s.replaceFirst(prefix, "")
              .replaceAll(File.separator, ".");
    };

    return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
                        .filter(javaFileObject -> javaFileObject.getKind().equals(kind))
                        .map(FileObject::getName)
                        .map(fileObjectName -> fileObjectName.replace(".class", ""))
                        .map(mapToClassName)
                        .map(className -> Try.of(() -> Class.forName(className))
                                             .getOrElseThrow(asRuntimeException))
                        .collect(Collectors.toList());
  };

  @Test
  @DisplayName("should get classes recursively in given package")
  void test() {
    Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
    assertThat(classes).hasSizeGreaterThan(4);
    classes.stream().map(String::valueOf).forEach(log::info);
  }
}

PS:为简化处理错误等的样板,我在这里vavrlombok库中使用

其他实现可以在我的GitHub daggerok / java-reflection-find-annotated-classes-or-methods回购中找到


0

我找不到因如此简单的事情而干掉的短暂工作。就是这样,我在拧了一段时间之后自己做了:

    Reflections reflections =
        new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                .setUrls(ClasspathHelper.forPackage(packagePath))
                .setScanners(new SubTypesScanner(false)));

    Set<String> typeList = reflections.getAllTypes(); 

0

如果您在春季,可以使用PathMatchingResourcePatternResolver;

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");

    Arrays.asList(resources).forEach(r->{
        ...
    });

-4

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.