3

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);    
}
John Eipe
  • 10,922
  • 24
  • 72
  • 114
  • note: You cannot _control_ that code, only put it in a sandbox to limit its resource use. If it decides to go in a busy loop and ignore interrupts you cannot stop it. – Thorbjørn Ravn Andersen Aug 31 '12 at 12:02

4 Answers4

5

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();
Flavio
  • 11,925
  • 3
  • 32
  • 36
2

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.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Yes the 'been there, done that' was apparent (and quite understated, ..with just a hint of sage). :) – Andrew Thompson Aug 31 '12 at 12:23
  • @Stephen, you said `System.in/out/err are class fields that inevitably have the same value for both "applications"` but when I try as indicated by Flavio I don't get the output. But no runtime exceptions. – John Eipe Sep 03 '12 at 10:00
  • @John - that is because Flavio's code makes no attempt to connect output from one "application" to the input of the other. He is only really dealing with the simple part of the problem. – Stephen C Sep 03 '12 at 12:05
  • @Stephen could you give an example code on how to do that forwarding? – John Eipe Sep 03 '12 at 14:14
  • 1
    @John - no. It is too complicated. You would be better off using Process instead. – Stephen C Sep 04 '12 at 06:13
  • @StephenC I tried to do something similar to this, but I had an issue with resource getting. Basically, the jar I loaded using ClassLoaders and ran couldn't find its resources and classes via reflection because it was looking inside the jar that loaded it instead of its own jar. Any ideas on how to fix that? I've tried a lot, but gotten nowhere. I made it [a separate question](http://stackoverflow.com/questions/26352483/how-can-i-make-a-loaded-jar-use-resources-from-itself-rather-than-its-loader) earlier today. – Variadicism Oct 15 '14 at 00:23
1

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.

Filipe Fedalto
  • 2,540
  • 1
  • 18
  • 21
0

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.

Mehdi
  • 4,396
  • 4
  • 29
  • 30