如何在运行时从下载的jar文件中加载未知类? [英] How to load unknown class from downloaded jar file during runtime?

查看:109
本文介绍了如何在运行时从下载的jar文件中加载未知类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm building a client server application. During runtime the client application loads a jar file from the server application and stores it. I run both the client and the server application as jar files. I now want to load the class contained in this downloaded jar file.

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

For example I have an interface A and a class B implementing A. The client application does not know about the class B, its name or even its existence. After the start of the client application the client applications downloads a jar file containing a jar file with content: server/package/B.class where server and package are folders.

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

Now the client Application should load this class B from the downloaded jar file with the following code:

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目录中的文件夹结构。

But then I get a ClassNotFoundException in the last line. Do I first have to extract the jar file? the folder structure in the jar file is like the folder structure in the bin directory of the server application.

推荐答案

加载类动态来自实现某个接口的jar文件,但是您事先并不知道哪个类和jar文件本身没有指定任何默认的插件类,您可以遍历下载的jar并获取一个列表jar中包含的类如下:

To load a class dynamically from a jar file that implements a certain interface, but you do not know in advance which class that will be and the jar file itself does not specify any default "plugin" class, you can iterate through the downloaded jar and get a list of classes contained in the jar like this:

    /**
     * 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文件中,你需要加载每个类并检查它们是否实现了所需的界面:

once the classes are known which are included in the jar file, you need to load each class and check if they implement the desired interface like this:

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

因为你现在可以收集某个界面的所有实现,你可以简单地初始化一个新实例通过

as you can now collect all implementations of a certain interface you can simple initialize a new instance via

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文件将由类加载器加载,但不会发生对象的实例化。

Note that this example assumes that A is an interface. If no implementing class could be found within the Jar-File the jar file will be loaded by the classloader but no instantiation of an object will happen.

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

Note further that it is always good practice to provide a parent classloader - especially with URLClassLoader. Else it might happen that certain classes which are not contained in the Jar-File might be missing and therefore you will get a ClassNotFoundException on trying to access them. This is due to the delegation mechanism used by classloaders which first ask their parent if they know the class definition for the required class. If so, the class will be loaded by the parent; if not, the class will be loaded by the created URLClassLoader instead.

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

Keep in mind that loading the same class multiple times with different ClassLoaders is possible (peer-classloaders). But although the Name and bytes of the class might be the same, the classes are not compatible as different classloader instances are used - so trying to cast an instance loaded by classloder A to a type loaded by classloader B will fail.

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

@ modified the code to avoid null values from being returned, instead more-or-less appropriate exceptions are thrown. @ as I am not able to accept code review suggestions I edited the review directly into the post

这篇关于如何在运行时从下载的jar文件中加载未知类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆