可能很多人都听说过 Jython,或者也叫 JPython,如果没听过的话,我大概介绍一下,Jyhton 就是在 Java 虚拟机(JVM) 上运行 python 代码的一种语言,代码是用 python 编写的,然后编译成 JVM 可以解析的字节码,运行在 JVM 上。
很明显,Jython 是给 Java程序员 运行 Python 程序用的,如果是 Python程序员 想运行 Java代码 怎么办?方式还是很多的,有 JPype/JEP/JPE 等等,这些项目都可以在 Python 运行环境执行 Java代码,那么,本文就以 JPype 为主题,介绍一下如何在 Python代码中运行 Java 代码,包括但不限于直接编写 Java代码,加载jar包 和 执行jar包。
在开始之前,先介绍一下本文使用的 Python 和 Java 运行环境
Java 版本
在本文中,我运行的 Java 版本是 1.8,是 Oracle 的 Hotspot 版,不是 OpenJDK,但是对于本文的内容,OpenJDK 也适用,亲测。
|
|
Python 版本
在本文中,我使用的 Python 版本为 3.5.2,这里有个很尴尬的问题,Python 不得不吐槽的一点就是版本太乱,2.6, 2.7, 3.4/3.5 这三个版本应该是比较多人用的,但是不兼容!!,而且大部分系统默认依赖的是 2.7,所以当你在项目使用时,一定要注意版本的选择,我目前推荐 3.5。
但是,不用担心,对于本文来说,JPype 是支持 2.7 和 3.5 的,所以,你使用本文的代码是完全可以在 2.7 上跑通的,而且更舒适的是在 2.7 上安装 JPype比 3.5 上方便多了。
在 Python 3.5 中,要安装 JPype 你不能直接使用 pip3 安装,因为 JPype 默认是为 Python2 编写的(其实是开发那时Python3还未进入主流,并且早在 2009 年就断更了),但是,不要紧张,有专业人士为 Python3 编写了另外一个兼容的版本,所以我们需要从源码安装了,具体步骤为:
|
|
然后就会编译安装了,正常的话是可以顺利安装成功地,这样安装也就完事了。
如果编译出错的话,请检查一下你的系统是否缺乏 python3-dev 库或者根本没有安装 C++ 编译工具,如果在 Linux 的话,你可以直接敲这样的命令:
|
|
如果还失败的话,那么可能你需要制定一下 JAVA_HOME,在尝试安装:
> JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64 python3 setup.py install
这里的 JAVA_HOME 需要根据你的 JDK 情况进行修改,千万不要照抄!!!
如果还有问题,那我就没办法了,来项目的 GITHUB 提 ISSUE 吧。
安装完了之后,我们就要开始写代码了,下面我将会介绍一些常用的套路,依照这些套路,你应该可以顺利得完成大部分的工作需求了。
一开始肯定要来个最简单的 ”Hello World“ 了,毕竟都是国际惯例了,所以啥也不多说了,先来一小段代码让大家看看,如果简单快速得使用 JPype:
|
|
可以看到,这里的代码很简单,寥寥三句而已,开头的 import 是必须的套路了,然后关键代码都在 __main__ 里面,可以看到的是 __main__里头只有三句代码,其中第一句和第三句都是和 JVM 密切相关的,那就是 startJVM 和 shutdownJVM,其实就是开启和关闭 JVM 的执行环境;然后中间这一句才是真正执行的 Java代码,可以看到,除了和在 Java 中直接调用 System.out.println 多了前面的包名之外,其他都是一样的,所以这让 Python 使用 Java代码 变得非常简单。
可能有时我们在代码中只会偶尔跑一下 Java代码,并不会一直运行着,这时候,为了性能考虑,可能回想在运行完 Java代码 之后会关掉 JVM,然后在下次运行Java代码的时候再启动一次JVM。说实话,这个想法是挺好的,所以我们来实践一遍:
|
|
然后我们执行一下,well,悲剧发生了,我们来看看输出:
|
|
没错,在第二次启动 JVM 的时候出错了,JVM居然启动不起来了!!!是的,不要惊讶,这是 BUG,而且是很久的 BUG,作者也不准备改的BUG,ISSUE 84 说了这个问题。
刚才第一坑说了,不能重启,那我们只能保持 JVM 一直运行了,同时,为了放置误操作重启了 JVM,所以 JPype 提供了另外一个函数判断 JVM 是否已启动,这里也给出一个示例看看:
|
|
这里的第 9 行判断了一下 JVM 是否已经启动,然后再决定要不要启动 JVM,最后再运行 Java代码。
在使用 Java 的时候,你说不引用第三方 jar 包是不可能的,因为 Java 之所以流行,和他拥有大量的第三方库是分不开的,所以,这里必须要介绍一下这个功能,如何引用第三方 jar 包,这里就以 alibaba 出品的 fastjson为例,介绍一下如何使用第三方 jar 包
下载 jar 包
下载链接: https://search.maven.org/remote_content?g=com.alibaba&a=fastjson&v=LATEST
引入 jar 包
在 JPype 中引入 jar 包和我们平时执行 Java 命令时相似,回想一下我们平时指定 jar 包时,使用的是 -cp 参数,然后后面接 jar包的位置,这里也一样
|
|
代码示例
```python import json from jpype import *
if __name__ == "__main__":
jars = ["/Users/yetship/tools/libs/fastjson-1.2.21.jar"]
jvm_path = getDefaultJVMPath()
jvm_cp = "-Djava.class.path={}".format(":".join(jars))
startJVM(jvm_path, jvm_cp)
JSONObject = JClass("com.alibaba.fastjson.JSONObject")
json_str = json.dumps({"name": "yetship", "site": "https://liuliqiang.info"})
jsonObj = JSONObject.parse(json_str)
print(jsonObj.getString("name"))
print(jsonObj.getString("site"))
shutdownJVM()
```
可以看到,这里获取了 JSONObject 的对象,然后解析了一个 json 字符串,然后从解析出来的 JSON 对象中获取值,这就是一个使用第三方 jar 包的应用,从这开始,你就开启了 Python 调用 Java 代码广泛的天空。
可能引用第三方 jar 包我们还不满意,我们还希望能够直接执行 jar 包,其实执行 jar 包未必需要使用 JPype来做,我们直接使用 subprocess 来做也行,但是这样管理就变得稍微有点复杂了,为了方便管理,统一代码,所以这里介绍一下如何使用 JPype 执行 jar 包。
首先,我们必须知道的是,所以得运行 jar 包,其实就是我们在打 jar 包的时候都会在 META-INF/MANIFEST.MF 指定了默认调用哪个类的 main 方法而已,这里不是讲 Java 知识的文章,所以就到此了,我们需要知道的是所谓的执行 jar 包其实就是调用了一个 main 方法而已,所以这里可以这么来调用一个 jar 包:
制作 jar 包:
java 代码为:
|
|
执行 jar 包
``` from jpype import *
if __name__ == "__main__":
jars = ["/Users/yetship/tools/libs/SimpleJar.jar"]
jvm_path = getDefaultJVMPath()
jvm_cp = "-Djava.class.path={}".format(":".join(jars))
startJVM(jvm_path, jvm_cp)
# 获取 Main 类
Main = JClass("info.liuliqiang.Main")
# 执行 main 函数,注意参数是 String[] args
Main.main([])
shutdownJVM()
```
不同的语言支持的数据类型是不一样的,例如 Java 中有 int,long,double,float 等类型,而 python 中却是没有 double 的,只有 float,所以如何转换这些类型是个问题,这里就列举出转换的关系:
虽然在 JPype 中,Java 的特性受到了一些限制,但是内部类却还是可以使用的,但是,在使用的过程中,有一些内容你还是需要注意的:
对于跨语言调用,我们离不开的一个话题就是性能了,对于 JPype 来说,因为它低层使用的是 JNI;JNI 在 Java 中被用来和低层的 C/C++ 代码进行交互,因此可以发现 JPype 其实不是简单得做了一层转换,实际上是做了两层的转换。
但事实上,在 Python 中使用 Java 代码有点类似于在 Python 中部分用 C 代码编写一般,不仅不会降低 python 代码的性能,反而会提高 python 代码的性能。
既然是跨语言,那么当频繁交互的时候必然带来开销,如果你经常往 Java 代码中传递相同的对象(例如 String/Object/Array)的话,那么你可以使用包装器来预转换,这样多少能提升一些代码的执行速度。
好了,到了 Python 的软项了,熟悉 Python 的同学都知道 Python 没有真正的多线程,所以只有伪多线程,这就导致了调用 Java 代码也没有多线程,同时Java代码中的多线程是不会生效的。
JProxy 是一个能让你在 Python 代码中实现任意数量的 Java 接口的方式。使用 JProxy 可以很简单得实现接口,JProxy 支持两种方式实现接口:
方式一:指定实例
指定实例就是指 JProxy 接收两个参数:
第二个参数就是一个 Python 类,这个类已经实现了这些接口的所有方法
举个例子,Java 代码中的接口是这样的:
|
|
然后再 Python 中可以这么实现:
|
|
方式二:指定方法
指定方法的话就是 JProxy 也接收两个参数:
第二个参数是一个字典,key 是要实现的方法,value 是一个可调用对象
还是举刚才的例子,Java 接口还是那样,然后 Python 实现的话就应该这么写:
|
|
对于继承父类这样的操作在 Python 中是不支持的,同时,因为 JPype 提供了大量的 Java API,所以继承也就变得意义没那么大了,如果有兴趣的话可以参考 AWT 和 SWING 的实现。
如果希望在 Python 中捕获 Java 异常的话,那么 JPype 提供有 JavaException 这个类用于捕获所有的 Java 异常,通过捕获 JavaException 这个类,你可以使用 message(), stackTrace()和 javaClass() 这些方法来获得更多的异常信息。这里举个简单的例子:
|
|
如果你真的想直接捕获真正的 Java 异常的话,那么你可以使用 JException 这个装饰器,例如:
try :
# Code that throws a java.lang.RuntimeException
except jpype.JException(java.lang.RuntimeException), ex :
print "Caught the runtime exception : ", JavaException.message()
print JavaException.stackTrace()
通过前面那么多的介绍,我们知道 JPype 是很强大的,实现了很多 Python 和 Java 交互的问题,但是 JPype 还是有一些局限性的,例如:
重启 JVM
相信之前那个第一坑让你有点无奈,居然不能重启 JVM,但是这个不合理的设计背后是有原因的。我们现在已经知道了 JPype 低层依赖的实现是 JNI,然后 JNI 的 API 中实现了一个 destroyJVM() 的函数,然而这个函数并不工作,所以当你再次调用 startupJVM() 的时候,你得到的是一个异常,
依赖于“当前类”的方法
在 Java 的库中,有很多方法依赖于调用的类来查找信息,所以,当我们在 Python 中调用这些方法时,那就会发生错误了,因为没有java 调用类提供给它,而且 JNI api 也没有办法来提供仿真的方法。
目前遇到过会出现这种问题的函数有:
|
|
第一句可以使用这种方式类替换:
|
|
第二句的话没有太好的方法,如果真的需要解决的话,我们只有自己先实例化好 driver 对象,然后再传递给 connect 方法了。
好的,终于到最后了,本文从安装到入门到深入,直至最后介绍了 JPype 的局限性,带大家全局认识了一下 JPype,希望能够帮助到大家在工作中,平时日常玩耍中解决一些问题。
原文来自:SDK.cn
声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com
支持全球约2.4万个城市地区天气查询,如:天气实况、逐日天气预报、24小时历史天气等
支持识别各类商场、超市及药店的购物小票,包括店名、单号、总金额、消费时间、明细商品名称、单价、数量、金额等信息,可用于商品售卖信息统计、购物中心用户积分兑换及企业内部报销等场景
涉农贷款地址识别,支持对私和对公两种方式。输入地址的行政区划越完整,识别准确度越高。
根据给定的手机号、姓名、身份证、人像图片核验是否一致
通过企业关键词查询企业涉讼详情,如裁判文书、开庭公告、执行公告、失信公告、案件流程等等。