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

 尤天asoka 发布于 2023-02-10 13:59

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

例如,我有一个接口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目录中的文件夹结构.

1 个回答
  • 要从实现某个接口的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文件中的类,就需要加载每个类并检查它们是否实现了所需的接口,如下所示:

        /**
         * <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;
        }
    

    因为您现在可以收集某个接口的所有实现,您可以通过简单的初始化新实例

    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是接口.如果在Jar-File中找不到实现类,则jar文件将由类加载器加载,但不会发生对象的实例化.

    另请注意,提供父类加载器始终是一种好习惯 - 尤其是使用URLClassLoader.否则,可能会发生某些未包含在Jar-File中的类可能会丢失,因此您将ClassNotFoundException尝试访问它们.这是由于类加载器使用的委托机制,它首先询问其父级是否知道所需类的类定义.如果是,则该类将由父级加载; 如果没有,该类将由创建的URLClassLoader加载.

    请记住,使用不同的ClassLoader可以多次加载同一个类(peer-classloaders).但是虽然类的名称和字节可能相同,但由于使用了不同的类加载器实例,因此类不兼容 - 因此尝试将类加载器A加载的实例强制转换为类加载器B加载的类型将失败.

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

    2023-02-10 14:01 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有