我有一个 Java 应用程序,它的主要功能是从一个带有 2 个参数的批处理文件中调用的。现在我想将另一对参数传递给同一个应用程序,而不是打开另一个实例。是否可以从外部调用正在运行的 jar 文件的主函数(或任何其他函数)?
谢谢您的帮助!
我有一个 Java 应用程序,它的主要功能是从一个带有 2 个参数的批处理文件中调用的。现在我想将另一对参数传递给同一个应用程序,而不是打开另一个实例。是否可以从外部调用正在运行的 jar 文件的主函数(或任何其他函数)?
谢谢您的帮助!
正如我在评论中提到的,您可能应该使用 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();
}
这是一种扭曲的生产者/消费者模式。
String
消费者非常无聊,它会从队列中吐出s 并在其中断或收到String
“退出”时退出。另一方面,制片人更有趣一些。
首先我们有interface ProducerMBean
, 这是 apublic interface
并且作为我们稍后将看到的面向外部的交互点。然后我们有一个Producer
which implements ProducerMBean
,它只是将东西添加到BlockingQueue
. 这里值得注意的是,所有这些都必须是线程安全的,否则 JMX 线程会遇到问题,因为 JMX 线程将您的方法与应用程序线程异步插入。
然后我们向平台注册我们的 MBean(管理 bean),这将暴露interface
给外部;我们需要小心我们暴露的东西,因为我们暴露的任何东西都可以被调用。的格式ObjectName
是<folder>:type=<name>
,稍后你会明白我的意思。
所以现在,当应用程序启动时,它就像预期的那样挂起。为了发送Strings
,我们需要打开 jconsole 并挂钩到 PID
的<folder>
部分ObjectName
成为顶级目录,而 的<name>
部分ObjectName
成为 MBean 名称本身。您现在可以使用该add
方法发送应用程序String
并查看它打印它们。很漂亮,但是我们如何从命令行调用它呢?
首先,我们需要将 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
在类路径上,最简单的方法是在类路径上运行主应用程序的助手。我们连接到在其上运行的 JMX 服务器localhost:<port>
,然后获取与我们之前注册的名称相同的 MBean。
我们现在可以调用该 MBean 上的方法,向其他应用程序发送命令。
显然你需要重写帮助应用程序来读取命令行参数,然后你可以调用类似的东西
java -cp MyMainApp.jar:MyHelperApp.jar com.example.helper.Main add TEST EXIT
从您的脚本中将参数传递给主 Java 程序。
您需要收听来自外部世界的数据。例如,它可以是网络端口或控制台。创建另一个线程,当收到一些数据时,它会监听并运行东西。您还可以使用反射来调用任意方法(不仅是预定义的)。
为什么不将方法本身的名称作为参数传递给主类并构建逻辑以在主类中执行该方法?
是的,您可以,再创建一个批处理文件并将您的不同参数传递给:
public static void main(String arg[])
作为主要方法的签名接受数组字符串。尽可能多地传递,并且你的 jar 支持。