4

所以这是我的问题。我正在创建一个类似于 DOS 命令行的软件。我希望人们能够通过简单地将包含扩展基Command类的类的 jar 文件拖放到文件夹中来添加其他命令。我没有尝试过任何工作。

这是我尝试过的:

  1. 使用 Reflections 库添加在 jar 文件中搜索扩展 Command 类的类
    这引发了许多错误,只是没有在我的 jar 中找到大多数类。我认为这与正在发生的所有SomeClass$1.class事情有关。
  2. 遍历 jar 中的每个文件并将其添加到类路径中,
    我找不到一种方法来实现这一点,因为我根本无法将迭代ZipEntry转换为可以添加到类路径中的任何内容,例如 URL。
  3. 将整个 jar 添加到类路径
    这也不起作用,因为程序不知道这些类的名称,因此,它不能将它们转换为命令。

我想在这里得到一些帮助。任何关于如何使我的程序更具可扩展性的建议都将非常受欢迎。包括代码的+1;)
如果您需要任何进一步的信息,请告诉我。

编辑:
新代码:

URLClassLoader ucl = (URLClassLoader) ClassLoader.getSystemClassLoader();
    for(File file : FileHelper.getProtectedFile("/packages/").listFiles()) {
        if(file.getName().endsWith(".jar")) {
            Set<Class<?>> set = new HashSet<Class<?>>();
            JarFile jar = null;
            try {
                jar = new JarFile(file);
                Enumeration<JarEntry> entries = jar.entries();
                while(entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();

                    if(!entry.getName().endsWith(".class"))
                        continue;

                    Class<?> clazz;
                    try {
                        clazz = ucl.loadClass(entry.getName().replace("/", ".").replace(".class", "")); // THIS IS LINE 71
                    } catch(ClassNotFoundException e) {
                        e.printStackTrace();
                        continue;
                    }

                    if(entry.getName().contains("$1"))
                        continue;

                    if(clazz.getName().startsWith("CMD_"))
                        set.add(clazz);

                    jar.close();
                }
            } catch(Exception e) {
                //Ignore file
                try {
                    jar.close();
                } catch (IOException e1) {/* Don't worry about it too much... */}
                OutputHelper.log("An error occurred whilst adding package " + OutputStyle.ITALIC + file.getName() + OutputStyle.DEFAULT + ". " + e.getMessage() + ". Deleting package...", error);
                e.printStackTrace();
                file.delete();
            }

抛出的错误:

java.lang.ClassNotFoundException: com.example.MyCommands.CMD_eggtimer
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at com.MyWebsite.MyApplication.Commands.CommandList.update(CommandList.java:71)

上面的代码中已经标记了第 71 行。

4

4 回答 4

1

当我过去需要这样做时,我结合使用了您的第 2 点和第 3 点,即我将 jar 文件添加到类路径中,然后遍历它列出它包含的文件以计算出类的名称,并使用 Class.forName(...) 加载它们,以便我可以检查它们是否是我需要对其执行处理的类。

另一种选择是要求在 jar 文件的清单中或通过其他一些元数据机制来标识类。

于 2013-03-20T20:54:35.207 回答
1

感谢大家的帮助,但我自己想通了。

首先,您需要您要操作的文件的位置。使用它来创建 jar 的 File 和 JarFile。

File file = new File("/location-to/file.jar");  
JarFile jar = new JarFile(file);  

如果需要,您可以通过遍历文件夹中的内容来做到这一点。

然后您需要URLClassLoader为该文件创建一个:

URLClassLoader ucl = new URLClassLoader(new URL[] {file.toURI().toURL()});  

然后遍历 Jar 文件中的所有类。

Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

if(!entry.getName().endsWith(".class"))
    continue;

Class<?> clazz;
try {
    clazz = ucl.loadClass(entry.getName().replace("/", ".").replace(".class", ""));
} catch(ClassNotFoundException e) {
    e.printStackTrace();
    continue;
}

if(entry.getName().contains("$1"))
    continue;

if(clazz.getSimpleName().startsWith("CMD_"))
    set.add(clazz); //Sort the classes how you like. Here I added ones beginning with 'CMD_' to a HashSet for later manipulation

不要忘记关闭东西:

jar.close();
ucl.close();

所以,让我们回顾一下发生了什么:

  1. 我们得到了文件
  2. 我们使用新的将它添加到类路径中URLClassLoader
  3. 我们遍历了 jar 中的条目,找到了我们想要的类
  4. 我们关闭了URLClassLoaderandJarFile以防止内存泄漏

嘘!

于 2013-03-24T16:36:58.657 回答
0

扫描类路径以查找实现给定接口的所有类似乎是一件相当麻烦的事情,但似乎有一些方法可以做到这一点 - 那里的答案建议使用反射库(我假设我实际上从未使用过它)

一个更明智的方法是使用ServiceLoader机制,即使它意味着一些元数据编辑。

编辑:我错过了您帖子中的反射参考,抱歉......仍然,关于 ServiceLoader 的建议仍然适用。

于 2013-03-20T20:44:20.700 回答
0

如果您使用类路径中的 jar 启动程序,则可以通过获取系统属性java.class.path并创建JarFile对象来获取该 jar 路径。然后,您可以遍历该对象的条目。每个条目都有一个路径(相对于 jar)。您可以解析此路径以获取包和类名称为com.package.Class. 然后,您可以使用 aClassLoader加载该类并使用它做任何您想做的事情。(您可能想使用另一个 ClassLoader,但它适用于下面的那个)

public class Main {
    static ClassLoader clazzLoader = Main.class.getClassLoader();

    public static void main(String [] args) {

        try {
            List<Class<?>> classes = new LinkedList<>();

            // do whatever transformation you need to get the jar file
            String classpath = System.getProperty("java.class.path").split(";")[1];

            // I'm just checking if it's the right jar file
            System.out.println(classpath);

            // get the file and make a JarFile out of it
            File bin = new File(classpath);
            JarFile jar = new JarFile(bin);

            // get all entries in the jar file
            Enumeration<JarEntry> entries = jar.entries();

            // iterate over jarfile entries
            while(entries.hasMoreElements()) {
                try {
                    JarEntry entry = entries.nextElement();

                    // skip other files in the jar
                    if (!entry.getName().endsWith(".class"))
                        continue;

                    // extract the class name from its URL style name
                    String className = entry.getName().replace("/", ".").replace(".class", "");

                    // load the class
                    Class<?> clazz = clazzLoader.loadClass(className);

                    // use your Command class or any other class you want to match
                    if (Comparable.class.isAssignableFrom(clazz))
                        classes.add(clazz);
                } catch (ClassNotFoundException e) {
                    //ignore
                }
            }

            for (Class<?> clazz : classes) {
                System.out.println(clazz);
                clazz.newInstance(); // or whatever
            }   

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我已经用内部类尝试过这个并且它也可以工作,我不知道Reflections库做了什么样的解析。

此外,你不需要一个完整的 Jar 来完成这样的工作。如果你有一个包含.class文件的 bin 文件夹,你可以递归地浏览它的文件/文件夹,将类文件解析为正确的格式,然后像上面一样加载它们。

编辑使用您的新代码,一些事情

  1. 迭代时不要关闭jar.close()循环中的 jar。
  2. if(clazz.getName().startsWith("CMD_"))不是你想要的。该getName()方法返回类的完整路径名,包括包。利用getSimpleName()
  3. 如果你得到一个ClassNotFoundException,那是因为那个类不在类路径上。这告诉我您没有在类路径上运行带有 jar 文件的程序。加载类时,仅打开一个 zip/jar 文件并“加载”类文件是不够的。你必须像这样运行你的程序

    java -classpath <your_jars> com.your.class.Main

    或者使用 Eclipse(或其他 IDE),将 jars 添加到构建路径中。

看一下这个关于使用类路径条目运行 java 的更多解释。

于 2013-03-20T21:26:46.853 回答