0

i have a Java application, which's main-function is called from a batch file witch 2 parameters. Now i want to pass another pair of parameters to the same app and not open another instance. Is it possible to call the main-function (or any other) of a running jar file from outside?

Thank you for your help!

Maik Fruhner
  • 300
  • 4
  • 14
  • You can use [JMX](http://docs.oracle.com/javase/tutorial/jmx/) for this. Alternatively you could open a server socket in the application and send data over that. The least robust way would be to use a file for data exchange. – Boris the Spider Jun 28 '13 at 07:37

4 Answers4

4

You should probably do this using JMX, as I mentioned in my comment.

JMX allows you to expose "management beans" from you program that can then be invoked remotely.

Lets start with a simple example:

private static class Consumer implements Runnable {

    private final BlockingQueue<String> blockingQueue;

    public Consumer(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                final String s = blockingQueue.take();
                System.out.println(s);
                if ("EXIT".equalsIgnoreCase(s)) {
                    return;
                }
            } catch (InterruptedException ex) {
                return;
            }
        }
    }
}

public static interface ProducerMBean {

    void add(String string);

    String contents();
}

private static class Producer implements ProducerMBean {

    private final BlockingQueue<String> blockingQueue;

    public Producer(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void add(String string) {
        blockingQueue.add(string);
    }

    @Override
    public String contents() {
        return blockingQueue.toString();
    }
}

public static void main(String[] args) throws Exception {
    final ExecutorService executorService = Executors.newSingleThreadExecutor();
    final BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
    executorService.submit(new Consumer(blockingQueue));
    final Producer producer = new Producer(blockingQueue);
    final ObjectName name = new ObjectName("com.example.producer:type=SomeUnqiueName");
    ManagementFactory.getPlatformMBeanServer().registerMBean((ProducerMBean) producer, name);
    executorService.shutdown();
}

This is a producer/consumer pattern with a twist.

The consumer is perfectly boring, it spits out Strings from the queue and bails out if its interrupted or receives the String "EXIT". The producer on the other hand is a little more interesting.

First we have the interface ProducerMBean, this is a public interface and acts as the outward facing point of interaction as we shall see later. We then have a Producer which implements ProducerMBean, it simply adds things to the BlockingQueue. It's worth noting here that all this must be thread-safe otherwise you will have issues with the JMX threads poking your methods asynchronously to you application threads.

We then register our MBean (management bean) with the platform, this exposes the interface to the outside would; we need to be careful what we expose as anything we expose can be called. The format for the ObjectName is <folder>:type=<name>, you will see what I mean later.

So now, when the application is started it just hangs, as expected. In order to send Strings we need to open jconsole and hook into the PID

accessing MBeans in jconsole

The <folder> part of the ObjectName becomes the top level directory and the <name> part of the ObjectName becomes the MBean name itself. You can now use the add method to send the app Strings and see it print them. Nifty, but how do we call that from the commandline?

First we need to register the MBean server to a port rather than a PID, this is done by invoking the application with the following JVM args:

-Dcom.sun.management.jmxremote.port=<port> \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote=true

Now if you run jconsole localhost:<port> you will connect directly to the application's MBean server. Magic.

This still doesn't quite do what we want. There is a SO answer that deals with commandline access to a JMX server, but I prefer to simply use another, helper, app.

This app would start, read all the arguments from the commandline, then invoke then MBean method specified with the arguments given.

Here is a simple example

public static void main(String[] args) throws Exception {
    final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:<port>/jmxrmi");
    final JMXConnector jmxc = JMXConnectorFactory.connect(url);
    final MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
    final ObjectName name = new ObjectName("com.example.producer:type=SomeUnqiueName");
    final ProducerMBean mbeanProxy = JMX.newMBeanProxy(mbsc, name, ProducerMBean.class, true);
    mbeanProxy.add("TEST");
    mbeanProxy.add("EXIT");
}

This application needs to have the interface ProducerMBean on the classpath, the easiest way is to run the helper with the main application on the classpath. We connect to the JMX server running on localhost:<port> and then get the MBean with the same name that we registered earlier.

We can now invoke methods on that MBean, sending commands to the other application.

Obviously you need to rewrite the helper app to read the commandline arguments, you could then invoke something like

java -cp MyMainApp.jar:MyHelperApp.jar com.example.helper.Main add TEST EXIT

From you script to pass in arguments to the main Java program.

Community
  • 1
  • 1
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
1

You need to listen for data from outside world. It can be network port, or console, for example. Make another thread which will listen and run stuff when received some data. You can also use reflection to call arbitrary methods (not only predefined).

Display Name
  • 8,022
  • 3
  • 31
  • 66
0

Why not pass the name of the method itself as an argument to main class and build a logic to execute that method in your main?

Srii
  • 543
  • 3
  • 7
  • 20
0

Yes you can , create one more batch file and pass your different parameters to :

public static void main(String arg[]) 

As signature of main method accepts string of array. Pass as many as you want and your jar supports.

Navdeep Singh
  • 699
  • 1
  • 10
  • 25
  • That's what I do, but when i call it again, another instance is called. I want to pass it the the same as before.. – Maik Fruhner Jun 28 '13 at 07:42
  • Then you should create a object reference and store the earlier instance and use that when required from the storage. – Navdeep Singh Jun 28 '13 at 08:47
  • You may not get access to actual instance but the data can be manipulated in new object. Please refer for more details : http://stackoverflow.com/questions/14741338/java-reflection-running-a-external-jar-and-referring-to-its-classes – Navdeep Singh Jun 28 '13 at 08:52