java - 如何在运行时从下载的 jar 文件加载未知类?

标签 java classloader dynamic-class-loaders

我正在构建客户端服务器应用程序。在运行时,客户端应用程序从服务器应用程序加载一个 jar 文件并存储它。我将客户端和服务器应用程序都作为 jar 文件运行。我现在想加载这个下载的 jar 文件中包含的类。

例如,我有一个接口(interface) A 和一个实现 A 的类 B。客户端应用程序不知道类 B、它的名称甚至它的存在。客户端应用程序启动后,客户端应用程序下载一个 jar 文件,其中包含一个包含以下内容的 jar 文件: server/package/B.class 其中 server 和 package 是文件夹。

现在,客户端应用程序应使用以下代码从下载的 jar 文件中加载此类 B:

URL downloadURL = downloadFolder.toURI().toURL();
URL[] downloadURLs = new URL[] { ruleSetFolderURL };
URLClassLoader loader =
    new URLClassLoader(downloadURLs);
Class tmp = loadClass(server.package.B);

但是我在最后一行得到了一个 ClassNotFoundException。我是否必须先提取 jar 文件? jar 文件中的文件夹结构类似于服务器应用程序的 bin 目录中的文件夹结构。

最佳答案

要从实现特定接口(interface)的 jar 文件中动态加载一个类,但您事先不知道将是哪个类,并且 jar 文件本身没有指定任何默认的“插件”类,您可以遍历下载 jar 并获取 jar 中包含的类列表,如下所示:

    /**
     * Scans a JAR file for .class-files and returns a {@link List} containing
     * the full name of found classes (in the following form:
     * packageName.className)
     *
     * @param file
     * JAR-file which should be searched for .class-files
     * @return Returns all found class-files with their full-name as a List of
     *         Strings
     * @throws IOException If during processing of the Jar-file an error occurred
     * @throws IllegalArgumentException If either the provided file is null, does 
     *                                  not exist or is no Jar file 
     */
    public List<String> scanJarFileForClasses(File file) throws IOException, IllegalArgumentException
    {
            if (file == null || !file.exists())
                    throw new IllegalArgumentException("Invalid jar-file to scan provided");
            if (file.getName().endsWith(".jar"))
            {
                    List<String> foundClasses = new ArrayList<String>();
                    try (JarFile jarFile = new JarFile(file))
                    {
                            Enumeration<JarEntry> entries = jarFile.entries();
                            while (entries.hasMoreElements())
                            {
                                    JarEntry entry = entries.nextElement();
                                    if (entry.getName().endsWith(".class"))
                                    {
                                            String name = entry.getName();
                                            name = name.substring(0,name.lastIndexOf(".class"));
                                            if (name.indexOf("/")!= -1)
                                                    name = name.replaceAll("/", ".");
                                            if (name.indexOf("\\")!= -1)
                                                    name = name.replaceAll("\\", ".");
                                            foundClasses.add(name);
                                    }
                            }
                    }
                    return foundClasses;
            }
            throw new IllegalArgumentException("No jar-file provided");
    }

一旦知道包含在 jar 文件中的类,您需要加载每个类并检查它们是否实现了所需的接口(interface),如下所示:

    /**
     * <p>
     * Looks inside a jar file and looks for implementing classes of the provided interface.
     * </p>
     *
     * @param file
     * The Jar-File containing the classes to scan for implementation of the given interface
     * @param iface
     * The interface classes have to implement
     * @param loader
     * The class loader the implementing classes got loaded with
     * @return A {@link List} of implementing classes for the provided interface
     * inside jar files of the <em>ClassFinder</em>s class path
     *
     * @throws Exception If during processing of the Jar-file an error occurred
     */
    public List<Class<?>> findImplementingClassesInJarFile(File file, Class<?> iface, ClassLoader loader) throws Exception
    {
        List<Class<?>> implementingClasses = new ArrayList<Class<?>>();
        // scan the jar file for all included classes
        for (String classFile : scanJarFileForClasses(file))
        {
            Class<?> clazz;
            try
            {
                // now try to load the class
                if (loader == null)
                    clazz = Class.forName(classFile);
                else
                    clazz = Class.forName(classFile, true, loader);

                // and check if the class implements the provided interface
                if (iface.isAssignableFrom(clazz) && !clazz.equals(iface))
                    implementingClasses.add(clazz);
            }
            catch (ClassNotFoundException e)
            {
                e.printStackTrace();
            }
        }
        return implementingClasses;
    }

因为您现在可以收集某个接口(interface)的所有实现,您可以通过

简单地初始化一个新实例
public void executeImplementationsOfAInJarFile(File downloadedJarFile)
{
    If (downloadedJarFile == null || !downloadedJarFile.exists())
        throw new IllegalArgumentException("Invalid jar file provided");

    URL downloadURL = downloadedJarFile.toURI().toURL();
    URL[] downloadURLs = new URL[] { downloadURL };
    URLClassLoader loader = URLClassLoader.newInstance(downloadURLs, getClass().getClassLoader());
    try
    {
        List<Class<?>> implementingClasses = findImplementingClassesInJarFile(downloadedJarFile, A.class, loader);
        for (Class<?> clazz : implementingClasses)
        {
            // assume there is a public default constructor available
            A instance = clazz.newInstance();
            // ... do whatever you like here
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

请注意,此示例假定 A 是一个接口(interface)。如果在 Jar 文件中找不到实现类,类加载器将加载 jar 文件,但不会发生对象的实例化。

请进一步注意,提供父类加载器始终是一种很好的做法 - 特别是使用 URLClassLoader。否则,Jar 文件中未包含的某些类可能会丢失,因此您将在尝试访问它们时收到 ClassNotFoundException。这是由于类加载器使用的委托(delegate)机制,它首先询问它们的父类是否知道所需类的类定义。如果是这样,该类将由父类加载;如果没有,该类将由创建的 URLClassLoader 加载。

请记住,使用不同的类加载器多次加载同一个类是可能的(对等类加载器)。但是,尽管类的名称和字节可能相同,但类不兼容,因为使用了不同的类加载器实例 - 因此尝试将类加载器 A 加载的实例转换为类加载器 B 加载的类型将失败。

@Edit:修改代码以避免返回空值,而是抛出或多或少适当的异常。 @Edit2:因为我无法接受代码审查建议,所以我将审查直接编辑到帖子中

关于java - 如何在运行时从下载的 jar 文件加载未知类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20586067/

相关文章:

java - 尝试连接 Lettuce 连接工厂时出错

java - FileWriter 创建 csv 时在单个单元格中写入 html

java - java类在内存中驻留的时间是多少

java - 我尝试在网页上查看小程序时收到 "class not found"错误

java - ClassLoader问题导致ClassCastException的解决方法

java - 从 jar 加载自定义类时出现 ClassCastException

java - 以编程方式加载不同的 .class 文件运行 JUnit 测试

java - OpenJPA:级联删除抛出 PersistenceException

java - 如何使用 ClassLoader 或 Class 以编程方式加载类

java - xslt 函数 format-dateTime 在转换为 PDF 时不起作用