【问题标题】:Java: Adding classes from external source to classpathJava:将类从外部源添加到类路径
【发布时间】:2013-03-10 04:06:39
【问题描述】:

所以这是我的问题。我正在创建一个类似于 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 行。

【问题讨论】:

  • 查看java.util.jar.JarFilestackoverflow.com/questions/9796814/… 中的用法,您将了解如何从jar 文件中获取类列表
  • 但是你可以对 ZipFile 做同样的事情,这也会使用更少的内存......但问题不是找到类,而是将它们添加到类路径并保留它们的实例我可以在运行命令时运行...

标签: java plugins


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    首先你需要你想要操作的文件的位置。使用它来创建 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. 我们关闭了 URLClassLoaderJarFile 以防止内存泄漏

    嘘!

    【讨论】:

      【解决方案3】:

      扫描类路径以查找实现给定接口的所有类似乎是一件相当麻烦的事情,但似乎有 some way 这样做 - 那里的答案建议使用 Reflections 库(我假设我没有实际上曾经使用过)

      更明智的方法是使用ServiceLoader 机制,尽管这意味着要进行一些元数据编辑。

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

      【讨论】:

        【解决方案4】:

        如果您使用类路径中的 jar 启动程序,则可以通过获取系统属性 java.class.path 并创建 JarFile 对象来获取该 jar 路径。然后你可以iterate over that object's entries。每个条目都有一个路径(相对于 jar)。您可以解析此路径以获取包和类名称为com.package.Class。然后,您使用ClassLoader 加载该类并使用它做任何您想做的事情。 (您可能想使用另一个 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 jar.close(),同时进行迭代。
        2. 这个if(clazz.getName().startsWith("CMD_")) 没有做你想做的事。 getName() 方法返回类的完整路径名,包括包。使用getSimpleName()
        3. 如果您得到ClassNotFoundException,那是因为该类不在类路径中。这告诉我您没有在类路径上运行带有 jar 文件的程序。加载类时,仅打开一个 zip/jar 文件并“加载”类文件是不够的。你必须像

          一样运行你的程序

          java -classpath &lt;your_jars&gt; com.your.class.Main

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

        查看this 以获取有关使用类路径条目运行 java 的更多说明。

        【讨论】:

        • 只要涉及到任何SomeClass$1.class 文件,它就会抛出一个错误...
        • @Kezz101 哪个错误?我刚刚尝试了一个匿名内部课程,它奏效了。小心clazz.newInstance(); 行,你不能实例化interface,所以你的程序可能在那里失败。将其更改为您需要做的任何事情。
        • 是的,它不是一个界面。错误消息只是位置(例如 com.example.package.SomeClass$1.class)。它似乎只发生在它遇到的第一个 1 美元课程上。
        • 也是ClassNotFoundException 在线clazz = ucl.loadClass(entry.getName().replace("/", ".").replace(".class", ""))。此外,所有类都会发生这种情况,无论它们是否是内部类......
        • @Kezz101 用你的新代码更新你的问题,让我看看。
        猜你喜欢
        • 2014-06-02
        • 2013-07-16
        • 2012-03-14
        • 2016-04-03
        • 2012-09-05
        • 2019-09-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多