繁体   English   中英

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

[英]Java: Adding classes from external source to classpath

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

这是我尝试过的:

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

我希望在这里有所帮助。 任何有关如何使我的程序更具扩展性的建议都将受到欢迎。 +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行。

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

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

谢谢大家的帮助,但我自己解决了。

首先,您需要要处理的文件的位置。 使用它来创建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以防止内存泄漏

晕!

扫描类路径以查找实现给定接口的所有类似乎很麻烦,但是似乎有一些方法可以这样做-那里的答案建议使用Reflections库(前提是我实际上从未使用过)

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

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

如果您使用类路径中的jar启动程序,则可以通过获取系统属性java.class.path并创建一个JarFile对象来获取该jar路径。 然后您可以遍历该对象的entry 每个条目都有一个路径(相对于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即可执行此操作。 如果您的bin文件夹中带有.class文件,则可以递归地浏览其文件/文件夹,将类文件解析为正确的格式,然后像上面一样加载它们。

编辑您的新代码,几件事

  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添加到您的构建路径中。

查看此内容,以获取有关使用类路径条目运行Java的更多说明。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM