我正在尝试使用 py4j 打开一个网关,我可以用来将对象从 java 传递到 python。当我尝试使用 py4j 函数打开网关时launch_gateway
它似乎没有正确连接到我的 Java 类。但是,当我在命令行中启动我的 java 类,然后使用 python 连接到它时JavaGateway
一切都按预期工作。我希望能够使用内置方法,因为我确信我没有考虑py4j设计中已经考虑过的事情,但我只是不确定我做错了什么。
假设我想创建一个通往类sandbox.demo.solver.UtilityReporterEntryPoint.class
的网关。在命令行中,我可以通过执行以下命令来执行此操作:
java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer
这将按预期启动,我可以在连接到网关后从 python 中使用类中的方法。目前为止,一切都好。
我对py4j文档的理解使我相信我应该执行以下操作来启动python中的网关:
port = launch_gateway(classpath='sandbox.demo.solver.UtilityReporterEntryPoint')
params = GatewayParameters(port=port)
gateway= JavaGateway(gateway_parameters=params)
执行这三行时我没有收到任何错误,但是当我尝试使用gateway.entry_point.someMethod()
访问我的 java 类方法时,它失败并显示以下错误:
Py4JError:调用 t.getReport 时出错。跟踪: PY4J。Py4JException:此网关的目标对象 ID 不存在:t 在 PY4J。Gateway.invoke(Gateway.java:277) at py4j.command.AbstractCommand.invokeMethod(AbstractCommand.java:132) at py4j.command.CallCommand.execute(CallCommand.java:79) 在 PY4J。GatewayConnection.run(GatewayConnection.java:214) at java.lang.Thread.run(Thread.java:745)
显然,某些东西在launch_gateway
内没有被正确调用,或者我给它提供了错误的信息。
在 py4j 源代码中launch_gateway
您可以看到,给定您提供的输入和函数构造的输入,构造了一个命令,最终由subprocess.Popen
调用。因此,给定传递给launch_gateway
上面的输入,传递给Popen
的命令将是:
command = ['java', '-classpath', '/Users/grr/anaconda/share/py4j/py4j0.10.4.jar:sandbox.demo.solver.UtilityReporterEntryPoint', 'py4j.GatewayServer', '0']
将此命令传递给Popen
将按预期返回侦听端口。但是,连接到此侦听端口仍然不允许访问我的类方法。
最后,将命令作为单个字符串传递给 Popen,而不使用最后一个参数 ('0'),可以正确启动再次按预期运行的网关。看了一眼py4j的Java源代码。网关服务器.class这毫无意义,因为 main 方法似乎指示如果参数长度为 0,则类应以状态 1 退出。
在这一点上,我有点不知所措。我可以破解一个可行的解决方案,但正如我所说,我确信这忽略了网关行为的重要方面,我不喜欢黑客解决方案。我很想在这篇文章中标记@Barthelemy,但希望他能读到这篇文章。提前感谢任何帮助。
编辑
目前,我已经能够通过以下步骤解决此问题。
将包括所有外部依赖项的整个项目打包到单个 jar 文件中
magABM-all.jar
,并将"主类"设置为UtilityReporterEntryPoint
。包括
if...else
块,了解--die-on-exit
的存在,就像在GatewayServer.java
中一样使用
subprocess.Popen
调用命令以运行项目 jar。
UtilityReporterEntryPoint.java
public static void main(String[] args) throws IOException {
GatewayServer server = new GatewayServer(new UtilityReporterEntryPoint());
System.out.println("Gateway Server Started");
server.start();
if (args[0].equals("--die-on-exit")) {
try {
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in, Charset.forName("UTF-8")));
stdin.readLine();
System.exit(0);
} catch (java.io.IOException e) {
System.exit(1);
}
}
}
app.py
def setup_gateway()
"""Launch a py4j gateway using UtilityReporterEntryPoint."""
process = subprocess.Popen('java -jar magABM-all.jar --die-on-exit', shell=True)
time.sleep(0.5)
gateway = JavaGateway()
return gateway
这样,如有必要,我仍然可以使用gateway.shutdown
,如果启动py4j网关的python进程死亡或关闭,网关将被关闭。
注意:我绝不认为这是最终的解决方案,因为py4j是由更聪明的人编写的,并且有明确的目的,我相信有一种方法可以在py4j的范围内管理这个确切的工作流程。这只是权宜之计。
有几个问题:
-
launch_gateway
中的类路径参数应该是目录或 jar 文件,而不是类名。例如,如果要包含其他 Java 库,则可以将它们添加到类路径参数中。 -
调用
gateway.entry_point.someMethod()
时收到的错误意味着您没有入口点。当你调用launch_gateway
时,JVM是用GatewayServer.main启动的,它会启动一个没有入口点的网关服务器:GatewayServer server = new GatewayServer(null, port)
。当前无法使用launch_gateway
并指定入口点。 -
当你用
java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer
启动JVM时,我相信JVM使用UtilityReporterEntryPoint作为主类。虽然您没有提供代码,但我假设这个类有一个 main 方法,并且它启动了一个 GatewayServer,并将 UtilityReporterEntryPoint 的实例作为入口点。请注意,冒号和类名之间有一个空格,因此 UtilityReporterEntryPoint 被视为主类,而不是类路径的一部分。