【问题标题】:run a executable jar file within java program using class loaders使用类加载器在 java 程序中运行可执行 jar 文件
【发布时间】:2012-08-26 05:34:26
【问题描述】:

我听说您可以使用 Process 或 ClassLoaders 运行其他 java 文件。

我有一个可执行 jar 'test.jar',其中有一个名为 Test 的主类。

我找到了一种使用 Process 运行的方法。我需要知道如何使用 ClassLoaders。

Process p = Runtime.getRuntime().exec("java -jar test.jar");
BufferedInputStream bis = new BufferedInputStream(p.getInputStream());
synchronized (p) {
   p.waitFor();
}
int b=0;
while((b=bis.read()) >0){
   System.out.print((char)b);    
}

【问题讨论】:

  • 注意:您无法控制该代码,只能将其放在沙箱中以限制其资源使用。如果它决定进入一个繁忙的循环并忽略中断,您将无法停止它。

标签: java jar classloader executable-jar


【解决方案1】:

一些帮助您入门的代码,基本上就是 @Felipe 所说的。

请记住,这并不完全相同。你没有产生一个新进程,只是一个新线程;您可能会遇到一些巨大的类加载问题; Test.jar 文件中的 System.exit() 之类的内容也会导致容器程序终止,等等。

File f = new File("Test.jar");
ClassLoader cl = new URLClassLoader(new URL[] { f.toURI().toURL() }, null);
final Class<?> claxx = cl.loadClass("my.package.Test");
final Method main = claxx.getMethod("main", String[].class);

Thread th = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            main.invoke(claxx, new Object[] { new String[] { "Param1", "Param2" } });
        } catch (Throwable th) {
            // TODO
        }
    }
});

th.setContextClassLoader(cl);
th.start();

th.join();

【讨论】:

    【解决方案2】:

    基本方法是:

    • 创建类加载器,
    • 阅读清单以找到入口点类
    • 加载入口点类,
    • 使用反射获取其public static void main(String[]) 方法,并且
    • 从新线程调用main 方法,传递命令的命令行参数。

    在这种特殊情况下,问题在于设置 System.in 和 System.out 流,以便您可以与在同一 JVM 中运行的“子应用程序”通信。问题是“子应用程序”期望 System.out 是它写入输出的流,并且“父应用程序”将从中读取。但是“父应用程序”期望 System.out 成为控制台(或其他)的输出流。那是行不通的,因为 System.in/out/err 是类字段,不可避免地对两个“应用程序”具有相同的值。

    解决方案是将父应用程序类的代码更改为不使用 System.in/out/err 字段。在“父级”调用子级上的main 方法之前,它必须使用System.setOut(...) 将其设置为将写入缓冲区的自定义输出流实例。然后,您需要一个匹配的输入流实例,“父”可以从 ... 中读取,而不是代码中的 bis

    您需要解决的另一件事是“子”应用程序调用System.exit() 的可能性。这将产生拔掉父级插头的不幸效果。

    还有各种其他的复杂性,比如清理线程和其他由“孩子”创建的东西。其中一些基本上是无法解决的,除非您的 JVM 实现了 Isolates JSR(主流 JVM 都没有这样做,AFAIK)。

    简而言之,在一般情况下执行此操作非常困难,而且有些事情您根本无法完全正确。除非您可以消除/避免这些问题,否则实现替代入口点和替代通信方式要简单得多。

    【讨论】:

    • 是的,“去过那里,做到了”是显而易见的(而且相当低调,..只有一丝鼠尾草味)。 :)
    • @Stephen,你说System.in/out/err are class fields that inevitably have the same value for both "applications" 但是当我按照 Flavio 的指示尝试时,我没有得到输出。但没有运行时异常。
    • @John - 这是因为 Flavio 的代码没有尝试将一个“应用程序”的输出连接到另一个“应用程序”的输入。他只是真正处理问题的简单部分。
    • @Stephen 你能给出一个关于如何进行转发的示例代码吗?
    • @John - 不。这太复杂了。改用 Process 会更好。
    【解决方案3】:

    您可以使用java.net.URLClassLoader 类。 您必须实例化 URLClassLoader,传递所需 JAR 的 URL(可以是文件 URL)。然后你可以使用这个类加载器来加载带有 main 方法的类。 URLClasLoader.loadClass() 方法将返回代表已加载类的 Class 实例。这样,您可以通过反射实例化它并进一步调用它。您必须直接调用 main() 方法。

    【讨论】:

      【解决方案4】:

      此代码可能会对您有所帮助:

      import java.io.File;
      import java.lang.reflect.Method;
      import java.net.URL;
      import java.net.URLClassLoader;
      import java.util.ArrayList;
      import java.util.List;
      
      public class Bootstrap {
      
      public static void main(String[] args) throws Exception {
      
          /*
              Assume your application has a "home" directory
              with /classes and /lib beneath it, where you can put
              loose files and jars.
      
              Thus,
      
              /usr/local/src/APP
              /usr/local/src/APP/classes
              /usr/local/src/APP/lib
           */
      
          String HOME = "/usr/local/src/YOURAPP";
          String CLASSES = HOME + "/classes";
          String LIB = HOME + "/lib";
      
          // add the classes dir and each jar in lib to a List of URLs.
          List urls = new ArrayList();
          urls.add(new File(CLASSES).toURL());
          for (File f : new File(LIB).listFiles()) {
              urls.add(f.toURL());
          }
      
          // feed your URLs to a URLClassLoader!
          ClassLoader classloader =
                  new URLClassLoader(
                          urls.toArray(new URL[0]),
                          ClassLoader.getSystemClassLoader().getParent());
      
          // relative to that classloader, find the main class
          // you want to bootstrap, which is the first cmd line arg
          Class mainClass = classloader.loadClass(args[0]);
          Method main = mainClass.getMethod("main",
                  new Class[]{args.getClass()});
      
          // well-behaved Java packages work relative to the
          // context classloader.  Others don't (like commons-logging)
          Thread.currentThread().setContextClassLoader(classloader);
      
          // you want to prune the first arg because its your main class.
          // you want to pass the remaining args as the "real" args to your main
          String[] nextArgs = new String[args.length - 1];
          System.arraycopy(args, 1, nextArgs, 0, nextArgs.length);
          main.invoke(null, new Object[] { nextArgs });
      }
      
      }
      

      来自here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-23
        • 2014-11-10
        • 1970-01-01
        • 1970-01-01
        • 2012-05-04
        • 2020-09-24
        相关资源
        最近更新 更多