为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。

对实现这一功能的简单代码有什么建议吗?


当前回答

我知道我来晚了,但我一直在使用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();

}

欲了解更多细节,请参阅文档

其他回答

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

如果你在Android上工作,下面的代码可以工作:

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

在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

运行代理:

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

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

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

另一个来自Allain的黑客解决方案版本,也适用于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解决方案的人来说,它只是一个临时解决方案。