349

为什么在 Java 中做到这一点如此困难?如果您想拥有任何类型的模块系统,您需要能够动态加载 JAR 文件。有人告诉我有一种方法可以通过编写自己ClassLoader的 .

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

4

20 回答 20

292

难的原因是安全性。类加载器是不可变的;您不应该在运行时随意添加类。实际上,我对使用系统类加载器感到非常惊讶。以下是制作自己的子类加载器的方法:

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

痛苦,但它就在那里。

于 2008-09-13T19:04:16.613 回答
147

以下解决方案是骇人听闻的,因为它使用反射绕过封装,但它完美无缺:

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);
于 2008-09-13T18:50:36.023 回答
52

您应该看看OSGi,例如在Eclipse 平台中实现的。它正是这样做的。您可以安装、卸载、启动和停止所谓的包,这些包实际上是 JAR 文件。但它做得更多,因为它提供了例如可以在运行时在 JAR 文件中动态发现的服务。

或者查看Java 模块系统的规范。

于 2008-09-13T18:55:34.543 回答
44

JCL 类加载器框架怎么样?我不得不承认,我没有使用过它,但它看起来很有希望。

使用示例:

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");
于 2009-09-20T11:35:39.023 回答
40

虽然这里列出的大多数解决方案要么是难以配置的黑客(JDK 9 之前)(代理),要么就是不再工作(JDK 9 之后),但我发现没有人提到明确记录的方法真的很令人惊讶。

您可以创建一个自定义系统类加载器,然后您可以自由地做任何您想做的事情。不需要反射,所有类共享相同的类加载器。

启动 JVM 时添加此标志:

java -Djava.system.class.loader=com.example.MyCustomClassLoader

类加载器必须有一个接受类加载器的构造函数,该类加载器必须设置为其父级。JVM启动时会调用构造函数并传递真实系统类加载器,主类将由自定义加载器加载。

要添加罐子,只需调用ClassLoader.getSystemClassLoader()并将其转换为您的班级。

查看此实现以获取精心设计的类加载器。请注意,您可以将add()方法更改为公开。

于 2020-01-15T01:14:03.103 回答
19

这是一个未弃用的版本。我修改了原始版本以删除不推荐使用的功能。

/**************************************************************************************************
 * 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();
    }
}
于 2010-04-07T15:33:22.013 回答
17

使用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());
    }
}
于 2017-09-27T21:28:57.140 回答
10

以下是 Alllain 方法的快速解决方法,以使其与较新版本的 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 版本时,它也可能会中断,因此您需要牢记这一点。

于 2018-11-02T01:16:54.117 回答
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);
于 2008-09-15T15:22:42.293 回答
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");
于 2013-08-15T12:57:44.580 回答
5

我知道我迟到了,但我一直在使用pf4j,这是一个插件框架,而且效果很好。

PF4J 是一个微框架,其目的是保持核心简单但可扩展。

插件使用示例:

使用 ExtensionPoint 接口标记在应用程序/插件中定义扩展点:

public interface Greeting extends ExtensionPoint {

    String getGreeting();

}

@Extension使用注解创建扩展:

@Extension
public class WelcomeGreeting implements Greeting {

    public String getGreeting() {
        return "Welcome";
    }

}

然后您可以根据需要加载和卸载插件:

public static void main(String[] args) {

    // create the plugin manager
    PluginManager pluginManager = new JarPluginManager(); // or "new ZipPluginManager() / new DefaultPluginManager()"

    // start and load all plugins of application
    pluginManager.loadPlugins();
    pluginManager.startPlugins();

    // retrieve all extensions for "Greeting" extension point
    List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
    for (Greeting greeting : greetings) {
        System.out.println(">>> " + greeting.getGreeting());
    }

    // stop and unload all plugins
    pluginManager.stopPlugins();
    pluginManager.unloadPlugins();

}

有关更多详细信息,请参阅文档

于 2020-07-25T23:36:59.423 回答
4

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

分配当前线程

首先我们必须添加

Thread.currentThread().setContextClassLoader(classLoader);

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

不包括

你的 jars 到父类加载器中,否则你将无法理解谁在加载什么。

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

然而,OSGi 框架仍然是最好的方式。

于 2015-07-25T07:40:57.553 回答
4

Alllain 的另一个版本的 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 上使用 Alllain 解决方案的人的临时解决方案。

于 2018-10-02T21:50:37.620 回答
4

另一个使用 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,所以这篇文章有助于如何更改 manifest.mf

跑步

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

 java -jar <your jar>

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

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

在运行时添加新 Jar

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

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

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

于 2018-10-10T13:42:35.563 回答
4

如果将来有人搜索这个,这种方式对我来说适用于 OpenJDK 13.0.2。

我有许多需要在运行时动态实例化的类,每个类都可能具有不同的类路径。

在这段代码中,我已经有一个名为 pack 的对象,它包含一些关于我要加载的类的元数据。getObjectFile() 方法返回类的类文件的位置。getObjectRootPath() 方法返回包含类文件的 bin/ 目录的路径,这些类文件包括我要实例化的类。getLibPath() 方法返回包含 jar 文件的目录的路径,这些 jar 文件构成该类所属模块的类路径。

File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
    URLClassLoader classloader;

    List<URL> classpath = new ArrayList<>();
    classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
    for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
        classpath.add(jar.toURI().toURL());
    }
    classloader = new URLClassLoader(classpath.toArray(new URL[] {}));

    Class<?> clazz = classloader.loadClass(object.getName());
    packObject = clazz.getDeclaredConstructor().newInstance();

} catch (Exception e) {
    e.printStackTrace();
    throw e;
}
return packObject;

我以前使用 Maven 依赖项:org.xeustechnologies:jcl-core:2.8 来执行此操作,但在超过 JDK 1.8 之后,它有时会冻结并且永远不会返回在 Reference::waitForReferencePendingList() 处“等待引用”。

我还保留了类加载器的映射,以便如果我尝试实例化的类与我已经实例化的类在同一个模块中,它们可以被重用,我会推荐。

于 2020-03-12T23:04:57.150 回答
2

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

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

创建对象:

    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 支持工厂方法、调用静态函数和回调接口实现。我将在自述文件页面上发布更多示例。

于 2018-05-25T04:32:54.050 回答
2

这可能是一个较晚的响应,我可以使用 DataMelt 中的 jhplot.Web 类(http://jwork.org/dmelt )这样做(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 文件中的类。

于 2018-10-21T00:20:01.137 回答
1

我需要在运行时为 java 8 和 java 9+ 加载一个 jar 文件(以上注释对这两个版本都不起作用)。这是执行此操作的方法(如果可能相关,请使用 Spring Boot 1.5.2)。

public static synchronized void loadLibrary(java.io.File jar) {
    try {            
        java.net.URL url = jar.toURI().toURL();
        java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
        method.setAccessible(true); /*promote the method to public access*/
        method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
    } catch (Exception ex) {
        throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
    }
}
于 2020-02-18T12:55:29.713 回答
1

对于jar文件的动态上传,可以使用我对URLClassLoader的修改。这个修改在应用运行过程中改变jar文件没有问题,和标准的URLClassloader一样。所有加载的 jar 文件都加载到 RAM 中,因此独立于原始文件。

内存中的 jar 和 JDBC 类加载器

于 2020-07-08T18:55:39.587 回答
-3

我个人觉得java.util.ServiceLoader做得很好。你可以在这里得到一个例子。

于 2011-09-23T02:48:15.313 回答