简体   繁体   English

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

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

So here is my issue. 所以这是我的问题。 I am creating a piece of software that is similar to a DOS command line. 我正在创建一个类似于DOS命令行的软件。 I would like people to be able to add other commands by simply dropping a jar file containing classes extending the base Command class into a folder. 我希望人们能够通过简单地删除包含将基本Command类扩展到文件夹的类的jar文件来添加其他命令。 Nothing I have tried yet works. 我没有尝试过任何方法。

Here is what I have tried: 这是我尝试过的:

  1. Using the Reflections library to add search the jar file for classes extending the Command class 使用Reflections库添加在jar文件中搜索扩展Command类的类
    This threw many an error and just didn't find the majority of the classes in my jar. 这引发了许多错误,只是在我的jar中找不到大多数类。 I think this has something to do with all the SomeClass$1.class stuff that goes on. 我认为这与发生的所有SomeClass$1.class事情有关。
  2. Iterating through every file in the jar and adding it to the classpath 遍历jar中的每个文件并将其添加到类路径中
    I couldn't find a way for this to work because I simply couldn't turn the iterations of ZipEntry into anything that could be added to the classpath, eg URLs. 我找不到解决此问题的方法,因为我根本无法将ZipEntry的迭代转换为可以添加到类路径的任何内容,例如URL。
  3. Adding the whole jar to the classpath 将整个罐子添加到类路径
    This didn't work either because the program won't know the names of these classes, therefore, it can't turn them into commands. 这也不起作用,因为程序不知道这些类的名称,因此,它无法将它们转换为命令。

I would love some help here. 我希望在这里有所帮助。 Any suggestions on how I can make my program more extensible would be very much welcome. 任何有关如何使我的程序更具扩展性的建议都将受到欢迎。 +1 for those including code ;) +1包括代码;)
If you need any further information just let me know. 如果您需要更多信息,请告诉我。

EDIT: 编辑:
New code: 新代码:

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();
            }

Error thrown: 引发错误:

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)

Line 71 has been marked in the above code. 上面的代码中已标记第71行。

When I've needed to do this in the past, I used a combination of your points 2 and 3, ie I add the jar file to the classpath, and then iterate through it listing the files it contains to work out the names of classes, and use Class.forName(...) to load them so I can check if they are classes I need to perform processing on. 过去需要这样做时,我结合使用了第2点和第3点,即我将jar文件添加到类路径中,然后对其进行遍历,列出其中包含的文件以计算出类名,并使用Class.forName(...)加载它们,这样我就可以检查它们是否是需要对其进行处理的类。

Another option is to require the classes to be identified in the jar file's manifest, or via some other metadata mechanism. 另一个选择是要求在jar文件的清单中或通过其他一些元数据机制来标识这些类。

Thanks for the help everyone but I figured it out on my own. 谢谢大家的帮助,但我自己解决了。

Firstly you need the location of the file you want to manipulate. 首先,您需要要处理的文件的位置。 Use this to create a File and a JarFile of the jar. 使用它来创建jar的File和JarFile。

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

You can do this by iterating through stuff in a folder if you want. 如果需要,可以通过遍历文件夹中的内容来完成此操作。

Then you need to create a URLClassLoader for that file: 然后,您需要为该文件创建URLClassLoader

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

Then iterate through all the classes in the Jar file. 然后遍历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

Don't forget to close stuff: 不要忘记关闭内容:

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

So, let's recap over what's happened: 因此,让我们回顾一下发生的事情:

  1. We got the file 我们得到了文件
  2. We added it to the classpath using a new URLClassLoader 我们使用新的URLClassLoader将其添加到类路径中
  3. We iterated through entries in the jar and found the classes we wanted 我们遍历了jar中的条目,找到了我们想要的类
  4. We closed the URLClassLoader and the JarFile to prevent memory leaks 我们关闭了URLClassLoaderJarFile以防止内存泄漏

Huzzah! 晕!

Scanning the classpath to find all classes implementing a given interface seems a rather messy affair but there seem to be some way to do so - the answers there suggest the use of the Reflections library (i premise i haven't actually ever used that) 扫描类路径以查找实现给定接口的所有类似乎很麻烦,但是似乎有一些方法可以这样做-那里的答案建议使用Reflections库(前提是我实际上从未使用过)

A more sensible way, even though it implies some metadata editing, would be to use the ServiceLoader mechanism. 一种更明智的方法,即使它暗示了一些元数据编辑,也将使用ServiceLoader机制。

Edit : i missed the Reflections reference in your post, sorry... still, the suggestion about ServiceLoader still applies. 编辑 :我错过了您的帖子中的Reflections参考,对不起...仍然,关于ServiceLoader的建议仍然适用。

If you start your program with the jar in the classpath, then you can get that jar path by getting the system property java.class.path and creating a JarFile object. 如果您使用类路径中的jar启动程序,则可以通过获取系统属性java.class.path并创建一个JarFile对象来获取该jar路径。 You can then iterate over that object's entries . 然后您可以遍历该对象的entry Each entry has a path (relative to the jar). 每个条目都有一个路径(相对于jar)。 You can parse this path to get the package and class name as com.package.Class . 您可以解析此路径以将包和类名获取为com.package.Class You then use a ClassLoader to load the class and do whatever you want with it. 然后,您可以使用ClassLoader加载类并对其进行任何操作。 (You might want to use another ClassLoader, but it works with the one below) (您可能想使用另一个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();
        }
    }
}

I've tried this with Inner classes and it works as well, I don't know what kind of parsing the Reflections library does. 我已经使用Inner类进行了尝试,它也可以正常工作,但我不知道Reflections库的解析方式。

Also, you don't need a full Jar for something like this to work. 另外,您不需要一个完整的Jar即可执行此操作。 If you have a bin folder with .class files in it, you can recursively go through its files/folders, parse class file to the proper format, and load them like above. 如果您的bin文件夹中带有.class文件,则可以递归地浏览其文件/文件夹,将类文件解析为正确的格式,然后像上面一样加载它们。

EDIT With your new code, a few things 编辑您的新代码,几件事

  1. Don't close the jar jar.close() within your loop, while you're iterating. 进行迭代时,请勿在循环中关闭jar jar.close()
  2. This if(clazz.getName().startsWith("CMD_")) isn't doing what you want. if(clazz.getName().startsWith("CMD_"))没有做您想要的事情。 The getName() method returns the full pathname of your class, including packages. getName()方法返回类的完整路径名,包括包。 Use getSimpleName() 使用getSimpleName()
  3. If you are getting a ClassNotFoundException , it's because that class is not on the classpath. 如果您收到ClassNotFoundException ,那是因为该类不在类路径中。 This is telling me that you are not running the program with your jar files on the classpath. 这是告诉我您没有在类路径上运行带有jar文件的程序。 When loading classes, it's not enough to open a zip/jar file and "load" the class files. 加载类时,仅打开zip / jar文件并“加载”类文件是不够的。 You have to run your program like 你必须像这样运行程序

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

    Or with Eclipse (or other IDE), add the jars to your build path. 或使用Eclipse(或其他IDE),将jar添加到您的构建路径中。

Take a look at this for more explanations on running java with classpath entries. 查看此内容,以获取有关使用类路径条目运行Java的更多说明。

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

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