在Python中调用Java类库和Java代码的桥接解决方案,背后使用的机制是JNI。虽然看起来这个库近年来更新不够活跃,原作者也试图用一个叫JEmbed(还没有发布)来替代之,但就我个人的使用体会来讲,现有版本是稳定可用的。

感谢Limodou~正是当初搜到了他在ChinaUnix上的旧帖子,才知道了jpype~

1.  基本安装

经试验,0.5.3版本不需要特殊处理,直接python setup.py install就可以了(安装过程中显示的warning可以不必理会)。用于测试是否安装成功的Hello World代码如下:

from jpype import *
startJVM(getDefaultJVMPath())
java.lang.System.out.println("hello world")
shutdownJVM()

2.  启动JVM

依靠startJVM这个函数来完成,一个使用的例子是这样的:

vmPath = jpype.getDefaultJVMPath()
jpype.startJVM(vmPath, "-Xms32m", "-Xmx256m", "-mx256m", "-Djava.class.path=/home/some-lib.jar:")

startJVM的第一个参数是JVM库所在的路径(和JAVA_HOME不是一回事儿),通常可以用jpype.getDefaultJVMPath()来自动获取系统默认JVM的路径。如果系统中安装了多个JDK,希望从中选择一个,则可以手动注明这个路径。比如Mac OSX下可以写成"/System/Library/Frameworks/JavaVM.framework/Libraries/libjvm_compat.dylib"

剩下的都是发送给JVM的启动参数,每个逗号见是一个参数。因为这里是不支持带空格的参数写法的,所以例子里特意把classpath参数写成了-Djava.class.path=...的形式。注意这里需要手工保证参数的正确性,jpype是不会对错误的参数给出提示的,它的反应很简单,就是在后面用到这个JVM的时候报一些怎么也想不明白的错误……所以,使用jpype遇到任何问题,首先检查传给startJVM的各参数正确性。

3.  如何调用一个Java函数

主要靠JPackage语句来实现,比如

 Document = jpype.JPackage('org').w3c.dom.Document
可以把Java里面的org.w3c.dom.Document映射给Python里面的Document变量。

java和javax两个包不需要以这种方式来调用,直接类似jpype.java.lang.System.out.println()这样就可以了。

有时候我们会遇到类似"TypeError: Package org.w3c.dom.Document is not Callable"这样的错误。通常这时用到的Java指令在jar里面,而这个jar没有被正确导入,所以JVM找不到它。也就是说,遇到这种错误时,要去检查startJVM函数中的-Djava.class.path=参数的设置,通常都是因为这里的路径写错了造成的。

4.  如果捕捉Java异常

可以在Python里使用 jpype.JavaException指代所有的Java异常,比如像下面这样:

import jpype
jpype.startJVM(jpype.getDefaultJVMPath())
try:
    jpype.java.lang.Integer("x1")
except jpype.JavaException, ex:
    print ex.javaClass(), ex.message()
    print ex.stacktrace()    
jpype.shutdownJVM()

如果要捕获特定的Java异常呢,则需要用到jpype.JException,比如像下面这样捕获的就是java.lang.NumberFormatException:

import jpype
jpype.startJVM(jpype.getDefaultJVMPath())
try:
    jpype.java.lang.Integer("x1")
except jpype.JException(jpype.java.lang.NumberFormatException), ex:
    print ex.javaClass(), ex.message()
    print ex.stacktrace()    
jpype.shutdownJVM()

5.  如何处理Java的函数多态

Java里面是允许参数格式不同的多个同名函数的,Python里面则不允许。这样在通过jpype调用Java api里面的函数时,有时会因为参数的类型乱掉而报错。那么怎么能调用到Java里面的特定函数呢?没办法,做强制类型转换吧。

比如jpype.java.lang.System.out.println(1)实际会调用println(int),那么如果我们想调用println(byte)%,则可以写成jpype.java.lang.System.out.println(JByte(1))这样。。

6.  如何重启JVM

jpype提供的shutdownJVM()方法实际调用的是JNI接口的unload实现,但是Sun对unload的实现有点问题,造成的结果就是jpype调用shutdownJVM()以后就没法再startJVM()了(会报错)。什么?您问关掉JVM干嘛还要重新开启它,这个折腾个什么劲?答案很简单:因为有时在未知的黑暗角落隐藏着邪恶的源头——内存泄露。。

既然jpype没法重启JVM,那么只好把jpype放到processing里面来用,需要重启时,就杀掉当前进程,重新启动一个新进程好了。。(processing安装很简单,Python 2.6官方发行版已经带了,之前的版本则可以easy_install processing)

下面给出一个processing下用jpype的例子:

import jpype
import processing

def java_loop(pipe, id):
    jpype.startJVM(jpype.getDefaultJVMPath())
    while True:
        jpype.java.lang.System.out.println(pipe.recv() + ' ' + id)
        pipe.send(None)

head1, head2 = processing.Pipe()
p = processing.Process(target = java_loop, args = [head2, '(JVM 1)'])
p.setDaemon(True)
p.start()
head1.send("Hello message from")
head1.recv()
p.terminate()

head1, head2 = processing.Pipe()
p = processing.Process(target = java_loop, args = [head2, '(JVM 2)'])
p.setDaemon(True)
p.start()
head1.send("Hello message from")
head1.recv()

这里用到的pipe.recv()要小心,一旦阻塞可能会与twisted之类的框架产生冲突。一个可能的解决办法是在recv()之前用pipe.poll()函数检测一下管道里面是否有待接收数据,如果没有就等一会重新poll()就是了。(poll()自称是非阻塞的,因为它只阻塞当前线程;而recv()则会阻塞当前进程,于是twisted就不干了^_^)

7.  关于多线程及Python实现Java接口等

这些我还没用到……所以,参考jpype官方文档吧……

GlossyBlue theme adapted by David Gilbert
Powered by PmWiki