Java 在运行时添加类路径

Java add Classpaths at runtime

这个问题在Whosebug里有很多答案吗? 但大多数人将 ClassLoader.getSystemClassLoader() 转换为 URLClassLoader 并且这不再起作用了。

系统类加载器必须找到 类。

还有其他解决方案吗?
- 无需重新启动 jar
- 无需创建自己的类加载器(在这种情况下,我必须用自己的类加载器替换系统类加载器)

现在必须只在启动时添加缺少的 classes/jars,我不想在 "Classpath".
的清单中添加这些 我用 premain-Method 找到了 Java Agent。这也可以很好地工作,但在这种情况下,我想在不调用 "java -javaagent:... -jar ..."

的情况下启动 premain 方法

目前我在开始时用缺少的类路径重新启动我的程序:

public class LibLoader {
    protected static List<File> files = new LinkedList<>();

    public static void add(File file) {
        files.add(file);
    }

    public static boolean containsLibraries() {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        String[] classpaths = runtimeMxBean.getClassPath().split(System.getProperty("path.separator"));

        List<File> classpathfiles = new LinkedList<>();
        for(String string : classpaths) classpathfiles.add(new File(string));

        for(File file : files) {
            if(!classpathfiles.contains(file)) return false;
        }

        return true;
    }

    public static String getNewClassPaths() {
        StringBuilder builder = new StringBuilder();

        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        builder.append(runtimeMxBean.getClassPath());

        for(File file : files) {
            if(builder.length() > 0) builder.append(System.getProperty("path.separator"));

            builder.append(file.getAbsolutePath());
        }

        return builder.toString();
    }

    public static boolean restartWithLibrary(Class<?> main, String[] args) throws IOException {
        if(containsLibraries()) return false;

        List<String> runc = new LinkedList<>();

        runc.add(System.getProperty("java.home") + "\bin\javaw.exe");

        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        List<String> arguments = runtimeMxBean.getInputArguments();
        runc.addAll(arguments);

        File me = new File(LibLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());

        String classpaths = getNewClassPaths();
        if(!classpaths.isEmpty()) {
            runc.add("-cp");
            runc.add(classpaths);
        }

        if(me.isFile()) {
            runc.add("-jar");
            runc.add(me.getAbsolutePath().replace("%20", " "));
        } else {
            runc.add(main.getName());
        }

        for(String arg : args) runc.add(arg);

        ProcessBuilder processBuilder = new ProcessBuilder(runc);
        processBuilder.directory(new File("."));
        processBuilder.redirectOutput(Redirect.INHERIT);
        processBuilder.redirectError(Redirect.INHERIT);
        processBuilder.redirectInput(Redirect.INHERIT);
        Process process = processBuilder.start();

        try {
            process.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
}

希望有人有更好的解决方案。

Problem is, the classes must be found my the system ClassLoader not by a new ClassLoader.

听起来您当前的重新启动 JVM 的解决方案是唯一干净的方法。

无法更改系统 ClassLoader,并且您无法在运行时向其添加额外的 JAR。

(如果您尝试使用反射来扰乱系统 classloader 的数据结构,最好的情况是它不可移植并且依赖于版本。最坏的情况是它要么容易出错......要么被 JVM 的运行时安全机制阻止。)

Johannes Kuhn 在评论中建议的解决方案无效。 java.system.class.loader 属性 在 JVM bootstrap 期间被查询。到您的应用程序 运行 时,对其进行更改将不会生效。我不相信他 Answer 中的方法也能奏效。


这是处理此问题的一种可能的替代方法...如果您能及早找出丢失的 JAR。

为自己编写一个启动器 class,执行以下操作:

  • 保存命令行参数
  • 找到应用程序 JAR 文件
  • 从 MANIFEST.MF.
  • 中提取 Main-Class 和 Class-Path 属性
  • 根据上述...和其他特定于应用程序的逻辑找出真正的class路径。
  • 使用正确的 class 路径创建一个新的 URLClassLoader,并将系统 classloader 作为其父级。
  • 用它加载主class。
  • 使用反射找到主要的classes main方法。
  • 通过保存命令行参数调用它。

这本质上是 Spring Bootstrap 和 OneJar(以及其他东西)用来处理 "jars in a jar" 问题等的方法。它避免启动 2 个虚拟机。