Java包内省

5

如何获取一个包中的所有类?


还请注意此帖子 - barfuin
5个回答

10

你不能这样做。类可以通过许多不同的类加载器来引入,包括远程加载器。


我的库FastClasspathScanner处理了绝大多数常见的类加载器和类路径规范机制。 - Luke Hutchison

7
这是一种更完整的解决jars相关问题的方法,基于JG发布的想法。
/**
 * Scans all classloaders for the current thread for loaded jars, and then scans
 * each jar for the package name in question, listing all classes directly under
 * the package name in question. Assumes directory structure in jar file and class
 * package naming follow java conventions (i.e. com.example.test.MyTest would be in
 * /com/example/test/MyTest.class)
 */
public Collection<Class> getClassesForPackage(String packageName) throws Exception {
  String packagePath = packageName.replace(".", "/");
  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  Set<URL> jarUrls = new HashSet<URL>();

  while (classLoader != null) {
    if (classLoader instanceof URLClassLoader)
      for (URL url : ((URLClassLoader) classLoader).getURLs())
        if (url.getFile().endsWith(".jar")  // may want better way to detect jar files
          jarUrls.add(url);

    classLoader = classLoader.getParent();
  }

  Set<Class> classes = new HashSet<Class>();

  for (URL url : jarUrls) {
    JarInputStream stream = new JarInputStream(url.openStream()); // may want better way to open url connections
    JarEntry entry = stream.getNextJarEntry();

    while (entry != null) {
      String name = entry.getName();
      int i = name.lastIndexOf("/");

      if (i > 0 && name.endsWith(".class") && name.substring(0, i).equals(packagePath)) 
        classes.add(Class.forName(name.substring(0, name.length() - 6).replace("/", ".")));

      entry = stream.getNextJarEntry();
    }

    stream.close();
  }

  return classes;
}

-1 没有任何文档或提示说明它的作用,+2 因为它看起来扫描了类路径并包含了 jar 包!所以你净得到了 +1 :) - Bill K
好的,关于它的功能已经有了一些提示,我撤回之前的话。 - Bill K
添加了Javadoc头以获得更好的说明。 :) - toluju
@toluju 很棒!只需要将.equals(packagePath)更改为.startsWith(packagePath)就可以满足我的需求了。谢谢。 - Jesse Chisholm

4

这里有一个代码片段(链接在此),它可以做到您想要的,前提是类可以在本地找到:

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

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

这个能用于打包成jar的类吗? - Thorbjørn Ravn Andersen
据我所知,如果您有一个 JAR(-1),这将失败,但是对于查找 .class 文件以进行发现的常规操作而言是可以的(+1),因此您不会损失 :) - Bill K
以上代码不仅假定给定包中的类是本地的,而且它们都已被同一个类加载器加载,并且它们没有被打包。 - ChssPly76
这对于像Eclipse这样的系统而言仍然是一个有用的开始,其中一个JAR文件被放置在目录中,并在下一次启动时被发现。这是一个合理的解决方案,但非常有限(并且由于它当前不支持JAR文件,因此使用起来没那么有用)。 - Bill K

1

没有全局的方法来做到这一点。话虽如此,如果您知道您的类来自哪里,您可以遍历jar文件或文件系统的目录。


1

Java没有发现功能。

大多数具有添加(发现)新类的能力的产品都有一个描述“程序扩展”的文本文件或特定目录,您可以在其中放置使用@JG所描述的技巧的类或jar。(这就是Eclipse所做的,并建议在用户可能手动添加新模块的任何解决方案中使用)


1
不要忘记插件的SPI API。这些为插件框架提供了相当优雅的解决方案。 - jsight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接