简体   繁体   中英

run a executable jar file within java program using class loaders

I have heard that you could run other java files using Process or using ClassLoaders.

I have a executable jar 'test.jar' with a main class called Test .

I found a way to run using Process. I need to know how to do it using 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);    
}

Some code to get you started, which basically does what @Felipe said.

Keep in mind that this is not exactly the same thing. You are not spawning a new process, only a new thread; you risk having some huge class loading headaches; things like System.exit() inside the Test.jar file will cause also the container program to terminate, and so on.

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

The basic approach is to:

  • create the classloader,
  • read the manifest to find the entry point class
  • load the entry point class,
  • use reflection to get its public static void main(String[]) method, and
  • call the main method from a new thread, passing the command line arguments for the command.

The problem in this particular case is going to be in setting up the System.in and System.out streams so that you can talk to the "child application" running in the same JVM. The problem is that the "child application" expects System.out to be the stream that it writes it output to and that the "parent application" will read from. But the "parent application" expects System.out to be the output stream for the console (or whatever). And that won't work, because System.in/out/err are class fields that inevitably have the same value for both "applications".

The solution is to change the code of the parent application classes to not use the System.in/out/err fields. Before the "parent" calls the main method on the child, it must use System.setOut(...) to set it to a custom output stream instance that will write to a buffer. Then you need a matching input stream instance that the "parent" can read from ... in place of bis in your code.

Another thing you need to address is the possibility that the "child" application will call System.exit() . That will have the unfortunate effect of pulling the plug on the parent.

And there are all sorts of other complexities, like cleaning up threads and other stuff created by the "child". Some of these are basically unsolvable unless your JVM implements the Isolates JSR (which no mainstream JVMs do, AFAIK).

In short, doing this in the general case is really hard, and there are some things that you simply cannot get completely right. Unless you can eliminate / avoid these problems, it is much simpler to implement an alternative entrypoint, and alternative ways of communicating.

You can use the class java.net.URLClassLoader . You'd have to instantiate the URLClassLoader passing the URLs (which can be file URLs) of the desired JARs. Then you can use this class loader to load the class with the main method. The URLClasLoader.loadClass() methods will return you the Class instance representing the loaded class. With that, you can instantiate it by reflecton and further call it. You'll have to directly call the main() method.

This code might help you :

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

}

Gotten from here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM