如何在运行时动态加载JAR文件?

问题描述 投票:292回答:15

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

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

java jar classloader
15个回答
239
投票

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

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

痛苦,但确实有。


4
投票

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

分配当前线程

首先我们必须添加

Thread.currentThread().setContextClassLoader(classLoader);

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

不包括

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

另请参阅Problem reloading a jar using URLClassLoader

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


3
投票

Allain的另一种hackish解决方案版本,也可在JDK 11上使用:

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解决方案的人的临时解决方案。


3
投票

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

创建代理类

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

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

向代理添加引用:

Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent

我实际上使用的是Netbeans,因此this post帮助更改清单文件。mf

正在运行

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

 java -jar <your jar>

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

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

在运行时添加新的Jar

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

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

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


2
投票

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

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文件中的类。


1
投票

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


-2
投票

[我个人发现java.util.ServiceLoader做得很好。您可以举一个例子here


136
投票

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

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

49
投票

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

或参见Java Module System的规格。


40
投票

JCL class loader framework如何?我不得不承认,我没有使用过,但是看起来很有希望。

用法示例:

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");

21
投票

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

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

14
投票

[使用Java 9,现在用URLClassLoader给出的答案给出了类似的错误:

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

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

创建代理类:

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文件中:

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

运行代理:

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

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

9
投票

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

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

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

然后使用类加载器,只需执行:

classLoader.loadClass(name);

6
投票

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

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

5
投票

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

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版本时,它有时也可能会中断,因此请记住这一点。

© www.soinside.com 2019 - 2024. All rights reserved.