2012-08-31 76 views
1

我聽說您可以使用Process或使用ClassLoaders運行其他java文件。使用類加載器在Java程序中運行可執行jar文件

我有一個名爲Test的主類的可執行jar'test.jar'。

我找到了一種使用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);  
} 
+0

注意:您無法控制該代碼,只能將其放在沙盒中以限制其資源使用。如果它決定進入繁忙循環並忽略中斷,則無法停止它。 –

回答

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(沒有主流的JVMs,AFAIK),否則其中一些基本上是無法解決的。

總之,在一般情況下這樣做是非常困難的,有些事情你根本無法完全正確。除非您可以消除/避免這些問題,否則實施替代入口點以及其他交流方式要簡單得多。

+0

有趣的I/O點。 –

+1

@AndrewThompson - 我學到了很多這種難以置信的東西...... –

+0

是的,'那裏,做到了'顯然(並且非常低調,只帶着一絲聖人)。 :) –

1

您可以使用類java.net.URLClassLoader。 您必須實例化傳遞所需JAR的URL(可以是文件URL)的URLClassLoader。然後你可以使用這個類加載器用main方法加載類。 URLClasLoader.loadClass()方法將返回表示已加載類的Class實例。有了它,你可以通過反射來實例化它,並進一步調用它。你必須直接調用main()方法。

5

一些代碼讓你開始,基本上做@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(); 
0

此代碼可以幫助你:

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得到。

相關問題