正如我在评论中提到的,您可能应该使用 JMX 来执行此操作。
JMX 允许您从您的程序中公开“管理 bean”,然后可以远程调用。
让我们从一个简单的例子开始:
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();
}
这是一种扭曲的生产者/消费者模式。
消费者非常无聊,它从队列中吐出Strings,如果它被中断或收到String“退出”,它就会退出。另一方面,制作人更有趣。
首先我们有interface ProducerMBean,这是一个public interface,作为我们稍后会看到的向外的交互点。然后我们有一个Producer 哪个implements ProducerMBean,它只是将东西添加到BlockingQueue。值得注意的是,所有这些必须是线程安全的,否则您将遇到 JMX 线程将您的方法与应用程序线程异步插入的问题。
然后我们向平台注册我们的 MBean(管理 bean),这会将 interface 暴露给外部;我们需要小心我们暴露的东西,因为我们暴露的任何东西都可以被调用。 ObjectName 的格式是 <folder>:type=<name>,稍后你会明白我的意思。
所以现在,当应用程序启动时,它就像预期的那样挂起。为了发送Strings,我们需要打开 jconsole 并挂钩到 PID
ObjectName 的<folder> 部分成为顶级目录,ObjectName 的<name> 部分成为 MBean 名称本身。您现在可以使用add 方法发送应用程序Strings 并查看它打印它们。很不错,但是我们如何从命令行调用它呢?
首先,我们需要将 MBean 服务器注册到一个端口而不是 PID,这是通过使用以下 JVM 参数调用应用程序来完成的:
-Dcom.sun.management.jmxremote.port=<port> \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote=true
现在,如果您运行jconsole localhost:<port>,您将直接连接到应用程序的 MBean 服务器。魔法。
这仍然没有达到我们想要的效果。有一个 SO answer 处理对 JMX 服务器的命令行访问,但我更喜欢简单地使用另一个助手应用程序。
此应用程序将启动,从命令行读取所有参数,然后调用使用给定参数指定的 MBean 方法。
这是一个简单的例子
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");
}
这个应用程序需要在类路径上有interface ProducerMBean,最简单的方法是在类路径上运行带有主应用程序的助手。我们连接到在localhost:<port> 上运行的 JMX 服务器,然后获取与我们之前注册的名称相同的 MBean。
我们现在可以调用该 MBean 上的方法,向其他应用程序发送命令。
显然你需要重写帮助应用程序来读取命令行参数,然后你可以调用类似的东西
java -cp MyMainApp.jar:MyHelperApp.jar com.example.helper.Main add TEST EXIT
从您的脚本中将参数传递给主 Java 程序。