关于Java:如何在运行时动态加载Jars?

关于Java:如何在运行时动态加载Jars?

How should I load Jars dynamically at runtime?

为什么用Java这么难? 如果要使用任何类型的模块系统,则需要能够动态加载jar。 有人告诉我有一种方法可以通过编写自己的ClassLoader来完成,但是对于(至少在我看来)应该像调用带有jar文件作为其参数的方法一样容易的事情来说,这是很多工作。

对执行此操作的简单代码有什么建议吗?


很难的原因是安全性。类加载器是不可变的。您不应在运行时随意向其添加类。实际上,我很惊讶能与系统类加载器一起使用。这是制作自己的子类加载器的方法:

1
2
3
4
5
6
7
8
URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

很痛苦,但确实如此。


以下解决方案有些骇人,因为它使用反射来绕过封装,但是可以完美地工作:

1
2
3
4
5
6
7
File file = ...
URL url = file.toURI().toURL();

URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);


您应该看一下OSGi,例如在Eclipse平台中实现。它确实做到了。您可以安装,卸载,启动和停止所谓的捆绑软件,这些捆绑软件实际上是JAR文件。但是它提供了更多功能,例如可以在运行时在JAR文件中动态发现的服务。

或参见Java模块系统的规范。


JCL类加载器框架如何?我不得不承认,我没有使用过,但是看起来很有希望。

用法示例:

1
2
3
4
5
6
7
8
9
10
JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file  
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder  
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)

JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class  
Object obj = factory.create(jcl,"mypackage.MyClass");

这是不推荐使用的版本。我修改了原始文件以删除不推荐使用的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**************************************************************************************************
 * Copyright (c) 2004, Federal University of So Carlos                                           *
 *                                                                                                *
 * All rights reserved.                                                                           *
 *                                                                                                *
 * Redistribution and use in source and binary forms, with or without modification, are permitted *
 * provided that the following conditions are met:                                                *
 *                                                                                                *
 *     * Redistributions of source code must retain the above copyright notice, this list of      *
 *       conditions and the following disclaimer.                                                 *
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
 *     * conditions and the following disclaimer in the documentation and/or other materials      *
 *     * provided with the distribution.                                                          *
 *     * Neither the name of the Federal University of So Carlos nor the names of its            *
 *     * contributors may be used to endorse or promote products derived from this software       *
 *     * without specific prior written permission.                                               *
 *                                                                                                *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
 *"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 **************************************************************************************************/

/*
 * Created on Oct 6, 2004
 */

package tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Useful class for dynamically changing the classpath, adding classes during runtime.
 */

public class ClasspathHacker {
    /**
     * Parameters of the method to add an URL to the System classes.
     */

    private static final Class< ? >[] parameters = new Class[]{URL.class};

    /**
     * Adds a file to the classpath.
     * @param s a String pointing to the file
     * @throws IOException
     */

    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }

    /**
     * Adds a file to the classpath
     * @param f the file to be added
     * @throws IOException
     */

    public static void addFile(File f) throws IOException {
        addURL(f.toURI().toURL());
    }

    /**
     * Adds the content pointed by the URL to the classpath.
     * @param u the URL pointing to the content to be added
     * @throws IOException
     */

    public static void addURL(URL u) throws IOException {
        URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        Class< ? > sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL",parameters);
            method.setAccessible(true);
            method.invoke(sysloader,new Object[]{ u });
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }        
    }

    public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        addFile("C:\\dynamicloading.jar");
        Constructor< ? > cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
        DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
        instance.test();
    }
}

在Java 9中,URLClassLoader的答案现在会出现如下错误:

1
java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

这是因为使用的类加载器已更改。相反,要添加到系统类加载器,可以通过代理使用Instrumentation API。

创建一个代理类:

1
2
3
4
5
6
7
8
9
10
11
package ClassPathAgent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class ClassPathAgent {
    public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
        instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
    }
}

添加META-INF / MANIFEST.MF并将其放在具有代理类的JAR文件中:

1
2
Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent

运行代理:

这使用byte-buddy-agent库将代理添加到正在运行的JVM:

1
2
3
4
5
6
7
8
9
10
11
import java.io.File;

import net.bytebuddy.agent.ByteBuddyAgent;

public class ClassPathUtil {
    private static File AGENT_JAR = new File("/path/to/agent.jar");

    public static void addJarToClassPath(File jarFile) {
        ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
    }
}


我发现的最好的是org.apache.xbean.classloader.JarFileClassLoader,它是XBean项目的一部分。

这是我过去使用的一种简短方法,可以从特定目录中的所有lib文件创建类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void initialize(String libDir) throws Exception {
    File dependencyDirectory = new File(libDir);
    File[] files = dependencyDirectory.listFiles();
    ArrayList<URL> urls = new ArrayList<URL>();
    for (int i = 0; i < files.length; i++) {
        if (files[i].getName().endsWith(".jar")) {
        urls.add(files[i].toURL());
        //urls.add(files[i].toURI().toURL());
        }
    }
    classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(),
        urls.toArray(new URL[urls.size()]),
        GFClassLoader.class.getClassLoader());
}

然后使用类加载器,只需执行以下操作:

1
classLoader.loadClass(name);

如果您使用的是Android,则以下代码适用:

1
2
3
String jarFile ="path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile,"/data/data/" + context.getPackageName() +"/", null, getClass().getClassLoader());
Class< ? > myClass = classLoader.loadClass("MyClass");

这是Allain方法使其与Java的较新版本兼容的快速解决方法:

1
2
3
4
5
6
7
8
9
10
11
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
    Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
    Method method = classLoader.getClass()
            .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    method.invoke(classLoader, jarPath);
}

请注意,它依赖于特定JVM内部实现的知识,因此它不是理想的,也不是通用的解决方案。但是,如果您知道将要使用标准的OpenJDK或Oracle JVM,则这是一个快速简便的解决方法。在将来发布新的JVM版本时,它有时也可能会中断,因此您需要牢记这一点。


jodonnell提出的解决方案很好,但是应该有所增强。我使用这篇文章成功地开发了我的应用程序。

分配当前线程

首先,我们必须添加

1
Thread.currentThread().setContextClassLoader(classLoader);

否则您将无法将存储的资源(例如spring / context.xml)加载到jar中。

不包括

您的jar放入父类加载器,否则您将无法理解谁正在加载什么。

另请参见使用URLClassLoader重新加载jar时出现问题

但是,OSGi框架仍然是最好的方法。


使用Instrumentation的另一个有效解决方案对我有效。它具有修改类加载器搜索的优势,避免了相关类的类可见性问题:

创建一个代理类

对于此示例,它必须位于命令行调用的同一jar中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package agent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class Agent {
   public static Instrumentation instrumentation;

   public static void premain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void agentmain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void appendJarFile(JarFile file) throws IOException {
      if (instrumentation != null) {
         instrumentation.appendToSystemClassLoaderSearch(file);
      }
   }
}

修改MANIFEST.MF

向代理添加引用:

1
2
3
Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent

我实际上使用的是Netbeans,因此本文有助于您更改manifest.mf。

跑步

Launcher-Agent-Class仅在JDK 9+上受支持,并且负责加载代理,而无需在命令行上明确定义它:

1
 java -jar <your jar>

在JDK 6+上工作的方式是定义-javaagent参数:

1
java -javaagent:<your jar> -jar <your jar>

在运行时添加新的Jar

然后,您可以根据需要使用以下命令添加jar:

1
Agent.appendJarFile(new JarFile(<your file>));

我在文档上没有发现任何问题。


来自Allain的骇客解决方案的另一个版本,也适用于JDK 11:

1
2
3
4
5
6
7
File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});

在JDK 11上,它给出了一些弃用警告,但作为在JDK 11上使用Allain解决方案的人的临时解决方案。


这可能是一个较晚的响应,我可以使用DataMelt(http://jwork.org/dmelt)的jhplot.Web类来做到这一点(fastutil-8.2.2.jar的简单示例)

1
2
import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library

根据文档,此文件将在" lib / user"中下载,然后动态加载,因此您可以在同一程序中立即使用此jar文件中的类开始。


请看一下我开始的这个项目:proxy-object lib

该库将从文件系统或任何其他位置加载jar。它将为jar专用一个类加载器,以确保没有库冲突。用户将能够从加载的jar创建任何对象,并在其上调用任何方法。
该库旨在从支持Java 7的代码库中加载用Java 8编译的jar。

创建对象:

1
2
3
4
5
6
7
8
9
10
    File libDir = new File("path/to/jar");

    ProxyCallerInterface caller = ObjectBuilder.builder()
            .setClassName("net.proxy.lib.test.LibClass")
            .setArtifact(DirArtifact.builder()
                    .withClazz(ObjectBuilderTest.class)
                    .withVersionInfo(newVersionInfo(libDir))
                    .build())
            .build();
    String version = caller.call("getLibVersion").asString();

ObjectBuilder支持工厂方法,调用静态函数和回调接口实现。
我将在自述页面上发布更多示例。


我个人发现java.util.ServiceLoader做得很好。您可以在此处获得示例。


推荐阅读

    linux还原系统命令?

    linux还原系统命令?,系统,数据,设备,工具,电脑,一致,命令,硬盘,文件,备份,lin

    linux查u盘系统命令?

    linux查u盘系统命令?,系统,设备,电脑,信息,管理,定期,软件,密码,生产,百分比

    linux系统关键命令?

    linux系统关键命令?,地址,工作,系统,信息,命令,目录,检测,环境,工具,设备,Lin

    linux系统内核命令?

    linux系统内核命令?,信息,系统,工作,工具,电脑,软件,管理,设备,内核,发展,如

    linux下并行运行命令?

    linux下并行运行命令?,系统,服务,工作,命令,环境,网络,暂停,文件,脚本,参数,l

    linux命令行装系统?

    linux命令行装系统?,系统,软件,电脑,信息,工具,环境,管理,代码,材料,检测,lin

    linux系统命令终端?

    linux系统命令终端?,系统,首页,终端,设备,电脑,情况,信息,命令,界面,用户,lin

    linux系统vi命令?

    linux系统vi命令?,档案,状态,系统,命令,正规,数字,模式,编辑,文件,光标,linux

    linux系统中文版命令?

    linux系统中文版命令?,系统,工作,信息,网络,地址,设备,目录,命令,功能,操作,l

    linux系统编译命令?

    linux系统编译命令?,系统,代码,百度,暂停,电脑,工具,命令,终端,内核,程序,Lin

    linux系统命令调用?

    linux系统命令调用?,系统,单位,工具,工作,管理,地址,权威,密码,电脑,信息,怎

    linux麒麟系统命令行?

    linux麒麟系统命令行?,系统,银河,电脑,设备,公司,信息,手机,密码,平台,麒麟,

    linux系统man命令?

    linux系统man命令?,信息,地址,系统,工作,命令,数据,管理,单位,目录,文件,linu

    虚拟机linux系统命令?

    虚拟机linux系统命令?,系统,工具,软件,名字,时间,命令,工作,首次,环境,名称,L

    linux系统查端口命令?

    linux系统查端口命令?,系统,状态,地址,检测,工具,网络,信息,灵活,服务,端口,l

    linux系统分屏命令?

    linux系统分屏命令?,系统,工具,地址,工作,命令,基础知识,信息,时间,情况,技

    linux系统高级命令?

    linux系统高级命令?,系统,工作,地址,信息,管理,命令,地方,目录,功能,用户,请

    linux重启系统命令?

    linux重启系统命令?,系统,工作,命令,设备,状态,标准,灵活,管理,用户,级别,Lin

    纯命令行linux系统?

    纯命令行linux系统?,系统,工作,信息,密码,地址,设备,终端,设计,网上,状态,怎

    linux系统输出命令?

    linux系统输出命令?,系统,工作,地址,信息,命令,工具,目录,设备,基础,发行,lin