Python调用java的jar包

有时候和第三方对接,对方只提供了jar包接口,而我们的系统是Python实现的,这时候可以通过jpype库实现Python调用jar包里的代码。

为了完整说明,先写一个简单的Java类,然后打包成jar,再通过Python调用。

Hello.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Hello
{
public static void sayHello() {
System.out.println("Hello World!");
}

public Hello() {

}

public int add(int a, int b) {
return a + b;
}
};

编译得到Hello.class

1
javac Hello.java

创建jar包

1
jar cf Hello.jar Hello.class

安装jpype库

1
pip3 install jpype1

调用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import jpype
import sys, os


def get_full_path(path):
if os.path.isabs(path):
return path
# 尝试对相对路径进行查找
# 查找当前目录
t = os.path.abspath(path)
if os.path.exists(t):
return t
# 查找启动脚本所在目录
main_path = os.path.abspath(sys.argv[0])
t, _ = os.path.split(main_path)
t = os.path.join(t, path)
if os.path.exists(t):
return t
# 查找当前脚本所在目录
script_path = os.path.realpath(__file__)
t, _ = os.path.split(script_path)
t = os.path.join(t, path)
if os.path.exists(t):
return t
raise FileNotFoundError(f"Can NOT find '{path}'.")


class PyJar:
def __init__(self, path=None):
if not jpype.isJVMStarted():
jvmPath = jpype.getDefaultJVMPath()
jpype.startJVM(jvmPath, "-ea")
self.addClassPath(path)

def addClassPath(self, path):
if path:
if isinstance(path, list) or isinstance(path, tuple):
for e in path:
jpype.addClassPath(get_full_path(e))
else:
jpype.addClassPath(get_full_path(path))

def jClass(self, classpath):
return jpype.JClass(classpath)


def main():
jar = PyJar("./Hello.jar")

Hello = jar.jClass("Hello") # 取得Java类,这里要把类路径写全,例如 com.xxcompany.yy.zz
instance = Hello() # 创建对象
Hello.sayHello() # 调用类静态函数
print(instance.add(1,2)) # 调用对象方法


if __name__ == '__main__':
main()

需要说明的是,一个进程只能启动一个JVM,并且JVM被关闭后不能再次启动,所以虽然jpype提供了关闭JVM的方法jpype.shutdownJVM(),我并没有封装在PyJar中,这样PyJar就可以视作对一个jar包的载入,在不同的.py文件里可以创建多个PyJar对象载入不同的jar包而不会出现JVM重复启动或者JVM已经终止的报错。

关于jar包依赖

.java依赖jar包时的.class编译

1
javac -classpath "/path1/1.jar:/path2/2.jar" abc.java

在使用时需要通过addClassPath把依赖的包都加入进去。

关于包路径

如果.java文件中通过package com.xx.yy;指定了路径,那么打jar包时应该把当前目录cd到com的上层目录

1
jar cf x.jar com/xx/yy/zz.class

Maven项目

pom.xml 可以把很多java文件打在一个包中,我们可以自己编写或者我们得到了一个带有pom.xml的源码项目,可以使用如下命令打包.jar

1
mvn clean package

对于pom.xml依赖的jar包我们需要将其下载下来,通过命令

1
mvn dependency:copy-dependencies

这样我们就可以把打包出来的jar和依赖的jar一并提取出来拷到需要被python加载的路径下,通过上面类的addClassPath方法使用。