源自:http://www.ibm.com/developerworks/cn/java/j-lo-jythonconfig/
引言
伴随信息时代的的迅猛发展,互联网已经全面渗透到电子商务,金融,电信,物流,资源等各个领域并逐渐成为其不可或缺的一部分。
信息时代,时间就意味着金钱。伴随着各种大型应用系统的普及,人们对于系统的稳定性,对与零宕机硬件不断的提出更高要求,却往往会忽视应用系统自身升级所带来的问题。
什么是 Jython
Jython 是一种完整的语言,而不是一个简单的 Java 翻译器或 Python 编译器,它是 Python 在 Java 中的完整实现。由于 Jython 继承了 Java 和 Python 二者的特性从而使其显得很独特。
看到这里读者一定会问,那 Jython,Java 以及 Python 之间的关系到底是怎么样的呢?其实,我理解 Jython 说简单一些,就是用 Java 语言实现的 Python 解释器,这种关系也就意味着你可以用 Python 语言编写程序而同时使用 Java 库。
在这里我不想大篇幅的阐述 Jython 是什么,如果各位读者感兴趣,请参阅 Jython 介绍
发现问题
大型的应用系统从不同的应用层面大致可以分为 :
- UI 层
该层应该说是比较稳定的,或者说它的变化对于整个系统的运行不会带来特别严重的影响。
- 接口层
该层与上层的 UI 以及底层的实现,都有着紧密的联系,所谓牵一发而动全身。作为一位大型应用系统的架构师或是系统设计人员,往往会出于维护成本以及开发成本的考虑,尽可能会让这个层面的设计与定义相对灵活。
- 实现层
该层可以说是系统最核心的层面,整个系统的核心处理逻辑都会放在这一层面上。而往往该层,在实际使用过程中也是最不稳定的,最容易从需求角度发生变化。
- 数据层
该层其实说简单一些就是数据库层,用于用户数据的查询、组装、更新等等,提供持久的数据服务,我们可以认为它也是个比较稳定的层面。
问题焦点似乎发现了,如何让我们的实现层更加灵活成为核心问题所在。那么如何能让我们系统的核心“与时俱进”呢?
分析问题
接下来,让我们看看一个系统的实现层都可能会干哪些事情:
- 接收通过接口传递过来的用户数据
- 依据一定的业务逻辑,处理用户数据
- 将处理结果更新到数据库中(可选)
- 将处理结果响应给 UI 层并渲染出来
分析到这里。我们对于实现层的内部数据流向又有了更进一步直观的认识。而我们对于实现层定义的所谓变数所在,也就比较明显了,如何实现业务处理逻辑的分离成为我们的目标。
解决问题
动态算法核心
本段以一个简单的 Python 方法为例,介绍如何借助 Jython 的动态执行能力来实现系统核心算法,以供 Java 代码调用。
Java 层代码片段
PythonInterpreter interpreter=new PythonInterpreter(); PySystemState sys = Py.getSystemState(); // 将 Jython 库加入系统的 classpath 中或直接通过这种方式动态引入 sys.path.add("D:\\jython2.5.2\\Lib"); // 执行算法所在的 python 文件 interpreter.execfile("impl/demo.py");
Jython 层代码片段
def getValue(o): result = 0 if o < 2: return -1 else: for i in range(o): result +=i return result
该方法很简单,当用户数据小于 2 时返回运算结果 -1,当用户数据大于等于 2 时,返回从 0 到输入数据的和。
Java 调用
PyFunction pyFunction = (PyFunction)interpreter.get("getValue",PyFunction.class );
System.out.println("Result: "+pyFunction.__call__(new PyInteger(100)));
通过 PyFunction 对象的内置方法 __call__() 传入用户数据。因为 Python 文件本身具备动态编译执行能力,所以我们只需要更新算法核心部分的 Python 代码,就可以达到动态改变系统底层算法的目的,而不需要更新 jar 包或重新部署应用而重启应用系统。
上边这个例子其实很简单,我们假设这个简单的 Jython 方法就是我们的业务逻辑,它会返回处理结果供上层渲染,但当业务逻辑发生了变化,比如当用户输入等于 2 时也要求返回值是 -1,此时对于这样一个简单的需求,我们只需要修改 Jython 代码就好了,并不需要更新 Java 层的东西。
动态配置文件
本段着重介绍,当系统中配置文件中若该项的数值需要发生变化时,我们如何来解决动态获取最新配置的问题。
配置文件 ( demo.
properties )
假设我们为每一次查询结果定义了最大的返回数目为 100
MaxValue=100 …
Java 层代码片段
PythonInterpreter interpreter=new PythonInterpreter();
PySystemState sys = Py.getSystemState();
sys.path.add("D:\\jython2.5.2\\Lib");
//‘re’是 Python 自身提供的正则表达式库,在 Python 方法中我们需要用到这个库中的方法,所以需要提前导入进来
interpreter.exec("import re");
interpreter.execfile("impl/demo.py");
PyFunction pyFunction = (PyFunction)interpreter.get("getconfig",PyFunction.class );
Jython 层代码片段
import re def getconfig(flag): result='' f=open('demo.properties','r') for i in f: g=re.findall(flag+'.*=.*',i) if len(g)>0: al=g[0].split('=') result=al[1].strip() break return result
这个方法很简单根据我们传入的条目名称,获取配置文件中对应条目的定义数值。
Java 调用
System.out.println("Config value: "+pyFunction.__call__(new PyString("MaxValue")));
借助 Python 强大的文本处理能力,由 Python 作为 Java 代码和配置文件之间的桥梁,从而动态的获取最新配置项数值。
相比纯 Java 内存操作的实现方式,这种实现机制从运行速度上可能没有什么优势,但是却可以给我们带来一种新的理念。也许我们可以将这两种方式融合起来找到某种平衡,既不影响系统的运行效率又保留了 Python 动态的运行机制,达到双赢的目的。
动态后台实现
前边已经提到了系统接口定义层和实现层之间的关系。那么我们是否可以借助 Jython 来帮助我们动态实现定义的接口呢?答案很明显,当然可以。
这里我就以一个简单的例子,介绍一下如何借助 Jython 实现可插拔式的后台实现。利用 Jython 来实现 Java 定义的接口。从而可以被其他的 Java 方法调用。
Java 接口定义
package com; public interface Worker { public String getFirstName(); public String getLastName(); public String getId(); public void setId(); }
上边是一个简单的人员接口,接口的实现类中我们就需要实现这里定义的 4 个方法。
Jython 层代码片段 ( 实现接口 )
from com import Worker class Worker(Worker): def __init__(self): self.first = "FirstName" self.last = "LastName" def getFirstName(self): return self.first def getLastName(self): return self.last def getId(self): return self.Id def setId(self, newId): self.Id = newId
我们在 Worker 类的构造函数中为该类的两个私有成员赋值,当调用 getFirstName() 或 getLastName() 方法时我们返回这两个私有成员的当前值。
Java 层代码片段
Object javaObject; PythonInterpreter interpreter=new PythonInterpreter(); PySystemState sys = Py.getSystemState(); sys.path.add("D:\\jython2.5.2\\Lib"); interpreter.execfile("impl/demo.py"); interpreter.exec("worker=Worker()"); PyObject pyObject = interpreter.get("worker"); pyObject.invoke("setId",new PyString("8888")); try { Class Interface = Class.forName(interfaceName); javaObject = pyObject.__tojava__(Interface); } catch (ClassNotFoundException e) { e.printStackTrace(); }
Java 调用
Worker worker = (Worker) javaObject; System.out.println("Name:"+worker.getFirstName()+worker.getLastName()); System.out.println("ID:"+worker.getId());
看到这里就不必多说了,只需要动态的替换 Python 实现部分的代码,就可以实现可插拔式后台实现啦。而对于调用这个接口的 Java 对象来说,并不知道这个实现类是否发生了变化。
/**
* <br>
* java直接执行python脚本(任意类型脚本)</br> <br>
* http://bbs.csdn.net/topics/390970924</br> <br>
* http://www.cnblogs.com/lmyhao/p/3363385.html</br> <br>
* http://bbs.chinaunix.net/thread-4185335-1-1.html</br>
*
* @Title: runpythonbyexecfile
* @Description: <br>java直接执行python脚本</br>
* @param @param pythoncmd 设定文件
* @return String 返回类型
* @throws
*/
public String runpythonbyruntimeexec(String pythoncmd)
{
String defString = "result is:";
if (null == pythoncmd || "".equals(pythoncmd))
{
defString += "error,the parameters filePath is empty or null!";
return defString;
}
if (pythoncmd.matches("^python "))
{
defString += "error,the parameters filePath is error,it should be like ^python !";
return defString;
}
Process pr;
BufferedReader in;
try
{
// String[] args = new String[] {"youpath2python\\python.exe","youscriptpath\\test.py","a","b","c","d" };
// Process process = Runtime.getRuntime().exec(args);
pr = Runtime.getRuntime().exec(pythoncmd);
in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
defString += line+"\n";
}
in.close();
pr.waitFor();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
in = null;
pr = null;
}
return defString;
}
可参考:
http://blog.csdn.net/wodestudy/article/details/8190707