Java:将类从外部源添加到类路径

时间:2013-03-20 20:32:53

标签: java plugins

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

以下是我的尝试:

  1. 使用Reflections库为jar文件添加搜索扩展Command类的类
    这引发了许多错误,并且在我的jar中找不到大多数类。我认为这与所有SomeClass$1.class内容有关。
  2. 遍历jar中的每个文件并将其添加到类路径
    我无法找到一种工作方式,因为我无法将ZipEntry的迭代转换为可添加到类路径中的任何内容,例如URL。
  3. 将整个jar添加到类路径
    这不起作用,因为程序不知道这些类的名称,因此,它不能将它们变成命令。
  4. 我会在这里得到一些帮助。关于如何使我的程序更具可扩展性的任何建议都将非常受欢迎。包括代码在内的+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 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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. 将其添加到类路径中
  4. 我们遍历jar中的条目并找到了我们想要的类
  5. 我们关闭了URLClassLoaderJarFile以防止内存泄漏
  6. 好哇!

答案 2 :(得分:0)

扫描类路径以查找实现给定接口的所有类似乎是一个相当混乱的事情但似乎有some way这样做 - 那里的答案建议使用Reflections库(i前提)我实际上从未使用过那个)

一种更明智的方式,即使它暗示了一些元数据编辑,也就是使用ServiceLoader机制。

修改:我错过了帖子中的思考参考,对不起......仍然,有关ServiceLoader的建议仍然适用。

答案 3 :(得分:0)

如果在类路径中使用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();
        }
    }
}

我已经尝试过使用Inner类,但它也有效,我不知道Reflections库的解析是什么类型。

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

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

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

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

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

  4. 有关使用类路径条目运行java的更多解释,请查看this