1. 概述
Python 作为一门编程语言,近年来在科学计算、数据分析等领域越来越流行,尤其是得益于其丰富的数值计算和统计分析类库。因此,在 Java 应用中调用 Python 代码的需求也日益常见。
本文将系统性地介绍几种主流的 Java 调用 Python 的方式,涵盖从原生 API 到第三方库,再到服务化通信的多种方案。每种方式都有其适用场景和“坑”,我们一一拆解。
2. 一个简单的 Python 脚本
为统一示例,我们使用一个极简的 Python 脚本 hello.py
,内容如下:
print("Hello Baeldung Readers!!")
假设你的系统已正确安装 Python,执行该脚本会输出:
$ python hello.py
Hello Baeldung Readers!!
这个脚本将作为后续所有调用方式的测试目标。
3. 使用核心 Java API
Java 原生提供了进程控制能力,可以直接启动外部 Python 进程来执行脚本。这是最直接但也最“原始”的方式。
3.1. 使用 ProcessBuilder
ProcessBuilder
是 Java 中用于创建和管理外部进程的标准方式。我们可以用它来启动 python
命令并执行脚本。
@Test
public void givenPythonScript_whenPythonProcessInvoked_thenSuccess() throws Exception {
ProcessBuilder processBuilder = new ProcessBuilder("python", resolvePythonScriptPath("hello.py"));
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
List<String> results = readProcessOutput(process.getInputStream());
assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain output of script: ", results, hasItem(
containsString("Hello Baeldung Readers!!")));
int exitCode = process.waitFor();
assertEquals("No errors should be detected", 0, exitCode);
}
✅ 关键点说明:
redirectErrorStream(true)
:将标准输出和错误输出合并,避免需要同时读取两个流,简化处理逻辑。resolvePythonScriptPath()
:用于获取脚本的绝对路径,确保进程能找到文件。- ❌ 踩坑提醒:该方式依赖系统环境变量
PATH
中能正确解析python
命令。在不同环境(如 Windows/Linux)或虚拟环境(virtualenv)下容易出问题。 - ⚠️ 若 Python 脚本有依赖包,需确保环境已安装,否则执行会失败。
这种方式简单粗暴,适合一次性脚本调用,但不适合高频率或复杂交互场景。
3.2. 使用 JSR-223 脚本引擎
JSR-223 是 Java 6 引入的脚本引擎规范,允许 Java 与动态语言(如 JavaScript、Groovy、Python)交互。
要支持 Python,我们需要 Jython —— 一个在 JVM 上运行的 Python 实现。
添加依赖
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-slim</artifactId>
<version>2.7.3b1</version>
</dependency>
⚠️ 注意:Jython 目前仅支持 Python 2.7,不支持 Python 3.x,这是其最大限制。
查看可用脚本引擎
ScriptEngineManagerUtils.listEngines();
输出应包含:
Engine name: jython
Version: 2.7.2
Language: python
Short Names:
python
jython
执行 Python 脚本
@Test
public void givenPythonScriptEngineIsAvailable_whenScriptInvoked_thenOutputDisplayed() throws Exception {
StringWriter writer = new StringWriter();
ScriptContext context = new SimpleScriptContext();
context.setWriter(writer);
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("python");
engine.eval(new FileReader(resolvePythonScriptPath("hello.py")), context);
assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", writer.toString().trim());
}
✅ 优点:
- 脚本在 JVM 内执行,无需启动外部进程,性能更好。
- 支持 Java 与 Python 变量共享(通过
Bindings
)。
❌ 缺点:
- 仅支持 Python 2.7,无法使用现代 Python 生态。
- 不支持 C 扩展(如 NumPy、Pandas),很多科学计算库无法使用。
4. Jython 进阶用法
Jython 提供了 PythonInterpreter
类,允许直接在 Java 中嵌入 Python 代码,适合轻量级逻辑嵌入。
4.1. 执行内联 Python 代码
@Test
public void givenPythonInterpreter_whenPrintExecuted_thenOutputDisplayed() {
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
StringWriter output = new StringWriter();
pyInterp.setOut(output);
pyInterp.exec("print('Hello Baeldung Readers!!')");
assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", output.toString().trim());
}
}
✅ 使用 try-with-resources
确保资源释放。
4.2. 变量传递与获取
@Test
public void givenPythonInterpreter_whenNumbersAdded_thenOutputDisplayed() {
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("x = 10 + 10");
PyObject x = pyInterp.get("x");
assertEquals("x: ", 20, x.asInt());
}
}
Java 可以通过 get()
获取 Python 变量,反之也可用 set()
传值。
4.3. 异常处理
try (PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("import syds");
}
会抛出 PyException
,输出与原生 Python 一致:
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: No module named syds
⚠️ 重要提醒:
PythonInterpreter
名为“解释器”,实则在 JVM 上运行并编译为字节码。- Jython 并非完整 Python 实现,缺少大量原生模块和 C 扩展,不适合复杂科学计算场景。
5. 使用 Apache Commons Exec
ProcessBuilder
虽然原生,但 API 较底层。Apache Commons Exec 提供了更高级的封装,简化进程管理。
添加依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
执行 Python 脚本
@Test
public void givenPythonScript_whenPythonProcessExecuted_thenSuccess()
throws ExecuteException, IOException {
String line = "python " + resolvePythonScriptPath("hello.py");
CommandLine cmdLine = CommandLine.parse(line);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
int exitCode = executor.execute(cmdLine);
assertEquals("No errors should be detected", 0, exitCode);
assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", outputStream.toString().trim());
}
✅ 优势:
- 更简洁的流处理(
PumpStreamHandler
自动泵送输出)。 - 支持超时、监听器等高级功能。
- 跨平台兼容性更好。
❌ 本质仍是启动外部进程,性能开销与 ProcessBuilder
类似。
6. 借助 HTTP 实现语言互通
与其在进程层面“硬刚”,不如换种思路:将 Python 服务化,通过 HTTP 接口调用。这才是生产环境推荐的做法。
6.1. 使用 Python 内置 HTTP 服务器
Python 自带简单 HTTP 服务:
python -m http.server 9000
访问 http://localhost:9000
即可浏览当前目录文件。
6.2. 使用 Web 框架暴露接口
更常见的做法是使用 Flask 或 Django 构建 RESTful 接口。
示例 Flask 接口:
from flask import Flask
app = Flask(__name__)
@app.route("/hello")
def hello():
return "Hello Baeldung Readers!!"
if __name__ == "__main__":
app.run(port=9000)
6.3. Java 端调用接口
Java 可使用任意 HTTP 客户端(如 HttpClient
、OkHttp、RestTemplate)调用:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9000/hello"))
.GET()
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
assertEquals("Hello Baeldung Readers!!", response.body());
✅ 这种方式的优势:
- 解耦 Java 与 Python,各自独立部署、升级。
- 易于监控、日志、限流、熔断。
- 支持异步、批量、高并发调用。
- 技术栈无关,未来可替换为 Go、Node.js 等。
❌ 缺点是引入网络开销和运维复杂度。
7. 总结
方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
ProcessBuilder |
一次性脚本调用 | 原生支持,无需依赖 | 依赖环境,难调试 |
JSR-223 / Jython | 轻量嵌入 Python 2.7 逻辑 | JVM 内执行,性能好 | 不支持 Python 3,无 C 扩展 |
Apache Commons Exec | 复杂进程管理 | API 友好,功能丰富 | 仍是外部进程 |
HTTP 接口 | 生产环境推荐 | 解耦、可扩展、易维护 | 需额外服务 |
✅ 最终建议:
- 小工具、脚本调用 → 用
ProcessBuilder
或 Commons Exec - 嵌入简单 Python 逻辑(且能接受 Python 2.7)→ Jython
- 生产级应用 → 优先考虑 HTTP 服务化
源码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/language-interop