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

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


当前回答

以防将来有人搜索这个,这种方法对我来说适用于OpenJDK 13.0.2。

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

在这段代码中,我已经有了一个名为pack的对象,它保存了一些关于我试图加载的类的元数据。getObjectFile()方法返回类的类文件的位置。getObjectRootPath()方法返回bin/目录的路径,其中包含我试图实例化的类的类文件。getLibPath()方法返回一个目录的路径,该目录包含构成该类所在模块类路径的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()中卡住“等待引用”。

我还保留了类加载器的映射,以便在我试图实例化的类与我已经实例化的类在同一个模块中时可以重用它们,我建议这样做。

其他回答

对于jar文件的动态上传,您可以使用我对URLClassLoader的修改。这种修改对于在应用程序操作期间更改jar文件没有问题,就像标准的URLClassloader一样。所有加载的jar文件都加载到RAM中,因此独立于原始文件。

内存中的jar和JDBC类装入器

虽然这里列出的大多数解决方案要么是hack(在JDK 9之前)难以配置(代理),要么就是不再工作(在JDK 9之后),但我发现没有人提到一个清晰的文档化方法,这真的很令人惊讶。

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

当启动JVM时添加这个标志:

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

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

要添加jar,只需调用ClassLoader.getSystemClassLoader()并将其转换为您的类。

请查看此实现,以获得精心制作的类装入器。请注意,您可以将add()方法更改为public。

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

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

下面的解决方案有点笨拙,因为它使用反射来绕过封装,但它工作得完美无缺:

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

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

}

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