【问题标题】:Java, Get all classes available to a URLClassLoader that implement a specific interfaceJava,获取实现特定接口的 URLClassLoader 可用的所有类
【发布时间】:2011-02-17 00:03:39
【问题描述】:

我正在开发一个命令行应用程序,它在运行时加载用户指定的文本翻译器(通过命令行 arg 提供的类文件/jar 的路径)。基本上,我采用该论点并使用它来创建 URLClassLoader。然后我需要找到实现 Transable 接口的 URLClassloader 可用的所有类。

现在我只允许这个命令行 arg 是一个包含类文件的目录。使解决方案相当简单(下面的代码)。但老实说,我不喜欢这个解决方案,因为它会分解 jar 文件、jar 文件的目录等......此外,这显然会分解任何具有已定义包的类,因为 loadClass 需要包含包的全名。谁有更好的方法?

    File d = new File(path);
    if(d.isDirectory()) {
        URL url = d.toURI().toURL();
        ClassLoader cl = new URLClassLoader(new URL[]{url});

        FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".class");
            }
        };

        for(File f : d.listFiles(filter)) {
            String name = f.getName().substring(0, f.getName().indexOf("."));
            String key = "";
            if(name.endsWith("Translator")) {
                key = name.substring(0, name.indexOf("Translator"));
            }
            else if(name.endsWith("translator")) {
                key = name.substring(0, name.indexOf("translator"));
            }
            else
                key = name;

            Class c = cl.loadClass(name);
            if(Transable.class.isAssignableFrom(c)) {
                Transable t = (Transable)c.newInstance();
                env.registerTranslator(key, t);
            }
            else {
                System.out.println("[ClassLoader] "+c.getCanonicalName()+" will not be loaded. It is not a translator class");
            }
        }
    }
    else {
        throw new Error("NOT IMPLEMENTED");
    }

【问题讨论】:

  • 只需进入 jar 文件并检查所有条目。

标签: java reflection classloader serviceloader


【解决方案1】:

如果你继续沿着这条路走下去,你可能只需要蛮力。据我所知,默认的类加载器甚至不会将一个类加载到 JVM 中,除非它以某种方式被引用。这意味着,除非您知道要加载它们的完全限定类名,否则您的某些类将基本上不可见。

您可能需要重新考虑您的要求。因为加载一组已为其指定类名的翻译器会容易得多。

【讨论】:

  • 我正在考虑将其中的一些内容移到需要明确名称的配置文件中,或者至少需要包含翻译器类的包的名称。这是一种更好的方法,我只是想确保不能首先干净地实现另一个选项。
  • @jdc0589 有针对 jar 和本地文件的解决方案,但 AFAIK 不是任何类加载器的通用解决方案。
  • 想详细说明一下?我只关心本地文件和本地 jars
  • 更改了我的输入文件的格式,要求明确定义翻译器类。绝对是最好的选择。
【解决方案2】:

您应该使用 Java 的服务提供者接口 (SPI) 和 ServiceLoader 类(在 Java 6 中引入),而不是显式搜索实现者。 SPI 几乎是一种标准方式来执行您在 Java 中描述的操作。

请参阅official Java tutorial,了解如何创建服务提供者以及如何在运行时通过 ServiceLoader 使用它。

【讨论】:

  • 听起来很棒,会尽快看看。谢谢
  • 这里有点困惑。也许我还没有找到它..但这似乎只返回我的 META-INF/services/org.jextend.Transable 文件中定义的默认实现?至少这就是我现在能做的所有事情,即使我使用可以访问其他实现的类加载器参数构造了 ServiceLoader。
  • 在您发布的链接中找到此文本:“此外,当前的 ServiceLoader 类无法告诉您的应用程序何时有新的提供程序在运行时可用。”怎么了?
  • 在不同的根目录中可能有多个 META-INF/services/org.jextend.Transable 文件或您的类加载器找到的 jar 文件。还有一个工具可以帮助您在构建期间创建这些文件:code.google.com/p/spi/wiki/GettingStarted
  • 在运行时不发现新提供者的处理是 ServiceLoader 将在启动期间查找并解析服务描述文件,因此当您在程序中将新文件和服务实现类放在类路径中时正在运行,它不会立即被拾取,但只有在你重新启动程序后才会被拾取。
【解决方案3】:

这可能符合要求。如果没有,您应该能够查看他们的来源,以了解什么对您有用。 http://code.google.com/p/reflections/

【讨论】:

  • 看起来很有趣..但是你能说依赖地狱吗?
  • 我刚刚在当天早些时候看到了对它的引用,然后偶然发现了你的问题,所以我想我会把它传递下去。我从来没有使用过它,但我想如果没有别的,你可以查看源代码,看看他们在做什么。
【解决方案4】:

原则上这不适用于任意类加载器,因为它们可以使用任何可以想象的方式来实际加载类,并且根本没有任何“目录监听”功能。

每当loadClass 出现时,类加载器甚至可以即时生成类(即类的字节码),并想象一个 TransableClassloader,其中每个自动定义的类都会实现您的接口 - 您的程序永远不会结束。


也就是说,对于URLClassloader,您可以使用getURLs(),对于jar:file: URL,您可以使用JarFileFile api 来获取文件名列表(因此类名)来尝试。由于您在 URL 中给出了包层次结构的根,因此找到正确的包名称也不困难。

(如果您需要更多详细信息,请说出来。)


编辑: 对于包名,它们对应于层次结构中的目录名。因此,当您有一个对应于(例如)dir/classes 的基本 URL,并找到一个名为 dir/classes/com/company/gui/SimpleTranslator.class 的类文件时,它对应于类 com.company.gui.SimpleTranslator

因此,删除基本前缀并将/ 替换为.(并删除.class)。 (在 JarFile 中,您不必删除前缀。)

实际上,如果您使用递归方法来遍历您的文件层次结构,您可以使用相同的方法构建您的包名,只需附加字符串(将它们作为参数提供给下一个递归调用):

public void searchClassesInDir(File dir, String packagePrefix) {
    if(dir.isDirectory()) {
        String prefix = packagePrefix + dir.getName() + ".";
        for(File f : dir.listFiles()) {
            searchClasses(f, prefix);
        }
    }
    else {
       String fileName = dir.getName();
       if(! fileName.endsWith(".class"))
           return;
       String className = packagePrefix + fileName.substring(0, fileName.length()-".class".length());
       // now do the rest of your processing
    }
}

searchClasses(new File(url.toURI()), "");

【讨论】:

  • 我想这就是我要走的路。除了确定包名称之外,我知道如何做所有事情。我能想到的唯一方法是只获取 jar 文件中类条目的路径,将文件分隔符替换为“。”并在结尾处砍掉“.class”。还有其他方法吗?
  • 我在这里添加了一个示例。但是砍掉'.class'并将'/'转换为'.'也可以。
  • 您也可以将过滤器添加到listFiles() 调用中,但要确保它也捕获子目录(或在第二个循环中)。对于一个类似的 jar 文件,我认为,你只需要遍历 JarEntrys (并且不需要递归,而是替换)。
【解决方案5】:

这会有帮助吗?

由于类 c = cl.loadClass(name);

类方法 getInterfaces() 返回一个类数组

检查每个类名是否与翻译类名匹配。

【讨论】:

  • 这不是真正的问题。不过谢谢
猜你喜欢
  • 2016-05-13
  • 1970-01-01
  • 2011-10-24
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 2013-12-24
  • 2015-11-22
相关资源
最近更新 更多