【问题标题】:Small class to allow a java program to auto-update itself允许java程序自动更新自身的小类
【发布时间】:2012-11-03 16:33:35
【问题描述】:

目标是让一个简单的 Java 类启动一个 jar 主类。当主类完成时,可以查询它是否要重新加载。通过这种方式,它可以自行热更新并重新运行。

Launcher 是通过 URLClassloader 加载 jar,然后卸载/重新加载更改后的 jar。可以通过修改 Launcher 或通过提供的 dos/unix 脚本来更改 jar,该脚本被调用以将新 jar 替换为旧 jar。

整个程序如下。经过测试,它似乎可以顺利运行。

java Launcher -jar [path_to_jar] -run [yourbatchfile] [-runonce]?可选]

1) Launcher 会在您的 jar 文件中查找“Main-Class”属性,这样就不需要为它指定实际的类来运行。

2) 它将调用 'public static void main(String[] args)' 并将您在命令行中提供的剩余参数传递给它

3) 当您的程序完成时,Launcher 将调用您的程序方法“public static boolean reload()”,如果结果为“true”,则会触发重新加载。

4) 如果你指定了 -runonce,那么程序将永远不会重新加载。

5) 如果您指定了 -run [batchfile],那么批处理文件将在重新加载之前运行。

我希望这对某些人有所帮助。

编码愉快!

import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.Attributes;
import java.util.jar.Manifest;


public class Launcher {

    public static void main(String[] args) {
        new Launcher().run(new ArrayList<>(Arrays.asList(args)));
    }

    private void run(List<String> list) {
        final String jar = removeArgPairOrNull("-jar", list);
        final boolean runonce = removeArgSingle("-runonce", list);
        final String batchfile = removeArgPairOrNull("-run", list);

        if (jar == null) {
            System.out.println("Please add -jar [jarfile]");
            System.out.println("All other arguments will be passed to the jar main class.");
            System.out.println("To prevent reloading, add the argument to -runonce");
            System.out.println("To provide another program that runs before a reload, add -run [file]");
        }

        boolean reload;

        do {
            reload = launch(list.toArray(new String[0]), new String(jar), new String(batchfile), new Boolean(runonce));
            System.out.println("Launcher: reload is: " + reload);

            gc();

            if (reload && batchfile != null) {
                try {
                    System.err.println("Launcher: will attempt to reload jar: " + jar);
                    runBatchFile(batchfile);
                } catch (IOException | InterruptedException ex) {
                    ex.printStackTrace(System.err);
                    System.err.println("Launcher: reload batchfile had exception:" + ex);
                    reload = false;
                }
            }

        } while (reload);

    }

    private boolean launch(String[] args, String jar, String batchfile, boolean runonce) {

        Class<?> clazz = null;
        URLClassLoader urlClassLoader = null;
        boolean reload = false;

        try {
            urlClassLoader = new URLClassLoader(new URL[]{new File(jar).toURI().toURL()});

            String mainClass = findMainClass(urlClassLoader);
            clazz = Class.forName(mainClass, true, urlClassLoader);

            Method main = clazz.getMethod("main", String[].class);
            System.err.println("Launcher: have method: " + main);

            Method reloadMethod;

            if (runonce) {
                // invoke main method using reflection.
                main.invoke(null, (Object) args);
            } else {
                // find main and reload methods and invoke using reflection.
                reloadMethod = clazz.getMethod("reload");

                main.invoke(null, (Object) args);
                System.err.println("Launcher: invoked: " + main);

                reload = (Boolean) reloadMethod.invoke(null, new Object[0]);
            }
        } catch (final Exception ex) {
            ex.printStackTrace(System.err);
            System.err.println("Launcher: can not launch and reload this class:" + ex);
            System.err.println("> " + clazz);
            reload = false;
        } finally {
            if (urlClassLoader != null) {
                try {
                    urlClassLoader.close();
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                    System.err.println("Launcher: error closing classloader: " + ex);
                }
            }
        }

        return reload ? true : false;
    }

    private static String findMainClass(URLClassLoader urlClassLoader) throws IOException {
        URL url = urlClassLoader.findResource("META-INF/MANIFEST.MF");
        Manifest manifest = new Manifest(url.openStream());
        Attributes attr = manifest.getMainAttributes();
        return attr.getValue("Main-Class");
    }

    private static void runBatchFile(String batchfile) throws IOException, InterruptedException {
        System.out.println("Launcher: executng batchfile: " + batchfile);
        ProcessBuilder pb = new ProcessBuilder("cmd", "/C", batchfile);
        pb.redirectErrorStream(true);
        pb.redirectInput(Redirect.INHERIT);
        pb.redirectOutput(Redirect.INHERIT);
        Process p = pb.start();
        p.waitFor();
    }

    private static String removeArgPairOrNull(String arg, List<String> list) {
        if (list.contains(arg)) {
            int index = list.indexOf(arg);
            list.remove(index);
            return list.remove(index);
        }
        return null;
    }

    private static boolean removeArgSingle(String arg, List<String> list) {
        if (list.contains(arg)) {
            list.remove(list.indexOf(arg));
            return true;
        }
        return false;
    }

    private void gc() {
        for (int i = 0; i < 10; i++) {
            byte[] bytes = new byte[1024];
            Arrays.fill(bytes, (byte) 1);
            bytes = null;
            System.gc();
            System.runFinalization();
        }
    }

}

【问题讨论】:

  • 哪个代码在抱怨清单?确切的错误消息是什么样的? findMainClass 是做什么的?
  • findMainClass 只是查看 jar 清单以找到主类。我将重新发布整个程序。
  • 在重新加载 urlclassloader 并显示找不到方法(No SuchMethod)的消息后失败: clazz.getMethod("reload") 也许您可以尝试一下,看看错误在哪里,或者也许这是处理和重新加载 urlclassloader 的一些限制?/
  • 实际异常消息是什么?
  • 没有什么能阻止你在同一个 jvm 中用同一个 jar 加载许多 URLClassloader,所以问题出在其他地方。你是在程序运行时更换 jar 吗?

标签: java jar reload urlclassloader automatic-updates


【解决方案1】:

经过调试,我确定最初的困难是Launcher没有隔离UrlClassloader。

通过将启动类加载器的程序部分放在单独的方法中,我能够让所有系统确定不再存在引用。

修改代码后,现在一切正常:)

【讨论】:

    猜你喜欢
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 2015-05-13
    • 2015-09-09
    • 1970-01-01
    • 2016-07-05
    • 1970-01-01
    • 2011-05-18
    相关资源
    最近更新 更多