Java调用Python
每个编程语言都有其适用的范围,当人们需要结合不同的生态去完成一些功能时,就会遇到不同语言通信的问题,本篇文章我们结合一个具体示例,展示如何通过FFI语言交互接口实现在Java中调用Python。
Why
主要有下面两种原因:
使用方式:例如采用Java开发的平台希望给用户提供易用的Python接口
生态集成:例如将Java中的分布式能力和Python的AI生态相结合
How
现如今有如下的解决方案:
本篇文章我们将给出一个示例说明如何使用FFI。
Prepare
首先需要安装以下环境,如遇到安装问题可以和ChatGPT聊一下~
编程语言
Java: 1.8
Python: 3.9
C:c99
编译与链接
gcc:动态链接
由于笔者开发环境采用MacOS + x86平台,所以以下教程只对此平台有效,后续再补上其他环境的相应命令
Learn By Doing
目标:Java传入日期参数, 由Python返回星期几
编写主体Java代码
package cn.syntomic.ffi;
/** Foreign function interface demo */
public class FFIDemo {
static {
System.load(System.getenv("LIBPYTHON"));
System.load(String.format("%s/FFIDemo.dylib", System.getProperty("user.dir")));
}
public static void main(String[] args) {
System.out.println(new FFIDemo().dayOfWeek("1994-05-05"));
}
/**
* 本地方法判断日期是星期几
* @param date 日期
*/
private native int dayOfWeek(String date);
}
可以看出我们需要首先导入两个共享库
运行Python所需的libpython库: 可以利用find-libpython得出
本地方法实现后的动态FFIDemo库:之后会介绍如何编译生成
生成Header文件
javac -h src/main/c/cn/syntomic/ffi/include src/main/java/cn/syntomic/ffi/FFIDemo.java
可见我们需要实现一个C方法
JNIEXPORT jint JNICALL Java_cn_syntomic_ffi_FFIDemo_dayOfWeek (JNIEnv *, jobject, jstring);
方法名: 由Java包名+类名+方法名组成
方法参数
JNIEnv
: 通过这个指针可以从运行的JVM中访问所需的类、对象、字段和方法jobject
: 方法所属于的Java对象jstring
: C JNI类型,详细对应可见JNI Types
Python模块实现: 直接调用Python函数实现
from datetime import datetime
def day_of_week(date):
return datetime.strptime(date, "%Y-%d-%m").weekday() + 1
一点数学:如果要直接去计算星期几,可以利用数论中Zeller公式
所以1994-05-05
这个日期的就是星期四:
将Python嵌入到C中
JNIEXPORT jint JNICALL Java_cn_syntomic_ffi_FFIDemo_dayOfWeek
(JNIEnv* env, jobject thisObject, jstring date) {
int weekOfDay;
// 初始化python解释器
Py_Initialize();
// 导入实现Python函数
const char* pName = "day_of_week";
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./src/main/python/cn/syntomic/ffi')");
PyObject* pModule = PyImport_Import(PyUnicode_FromString(pName));
PyObject* pFunc = PyObject_GetAttrString(pModule, pName);
// java类型转化为python参数
PyObject* pArgs = PyTuple_New(1);
PyTuple_SetItem(pArgs, 0, PyUnicode_FromString((*env)->GetStringUTFChars(env, date, NULL)));
// 调用python函数
PyObject* pValue = PyObject_CallObject(pFunc, pArgs);
weekOfDay = PyLong_AsLong(pValue);
// 关闭python解释器
if (Py_FinalizeEx() < 0) {
exit(120);
}
return weekOfDay;
}
编译与运行
export JAVA_HOME=${JAVA_HOME}
export PYTHONHOME=${PYTHONHOME}
export LIBPYTHON=${LIBPYTHON}
# 编译
gcc -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin -I${PYTHONHOME}/include/python3.9 -I${PYTHONHOME}/include src/main/c/cn/syntomic/ffi/cn_syntomic_ffi_FFIDemo.c -o FFIDemo.o
# 生成动态链接库
gcc -dynamiclib -L${PYTHONHOME}/lib -lpython3.9 -ldl -o FFIDemo.dylib FFIDemo.o
javac -d target/classes/cn/syntomic/ffi src/main/java/cn/syntomic/ffi/FFIDemo.java
java -cp target/classes cn.syntomic.ffi.FFIDemo
最终就会输出
4
Summary
本篇文章我们以一个示例展示如何使用FFI,详细代码参考ffi_demo, 深入研究的话可以参考Pemja