附录 B:Jython 食谱 - 社区提交代码示例的汇编¶
在网络上可以找到大量使用 Jython 的示例。本附录汇编了一些我们发现的最有用的示例。网络上提供了数百个示例。这些示例侧重于网络上其他地方没有广泛介绍的主题。
除非另有说明,否则这些示例中的每一个最初都是为在 Jython 2.5.x 之前的版本上运行而编写的,但我们已经使用 Jython 2.5.1 对它们进行了测试,并且它们按预期工作。
日志记录¶
使用 log4j 与 Jython - Josh Juneau¶
您是否仍在使用 Jython print 命令来显示错误?在生产环境中,您是否使用任何正式的日志记录?如果没有,您应该这样做……Apache log4j API 使得这样做变得很容易。许多 Java 开发人员已经开始喜欢 log4j API,并且它在整个社区中得到广泛使用。这对 Jython 开发人员来说是个好消息,因为我们可以直接访问 Java 库!
设置您的环境
使用 log4j 与 Jython 最困难的部分是设置。您必须确保 log4j.jar 存档位于 Jython PATH 中的某个位置(通常这需要将 CLASSPATH 设置为包含必要的文件)。然后,您为 log4j 设置一个属性文件。在属性文件中,您可以包含附加程序信息、日志应该驻留的位置等等。
示例属性文件
log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=C:\\Jython\\testlog4j.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
您现在已准备好将 log4j 用于您的 Jython 应用程序。如您所见,如果您曾经使用过 Java 中的 log4j,它基本上是一样的。
在 Jython 应用程序中使用 log4j
再次强调,在 Jython 应用程序中使用 log4j 与在 Java 世界中的使用非常相似。
首先,您必须导入 log4j 包
from org.apache.log4j import *
其次,您为您的类或模块获取一个新的记录器,并设置一个 PropertyConfigurator
logger = Logger.getLogger("myClass")
# Assume that the log4j properties resides within a folder named "utilities"
PropertyConfigurator.configure(sys.path[0] + "/utilities/log4j.properties")
最后,使用 log4j
# Example module within the class:
def submitDocument(self, event):
try:
# Assume we perform some SQL here
except SQLException, ex:
self.logger.error("docPanel#submitDocument ERROR: %s" % (ex))
您的日志记录现在将在您在 log4j.appender.R.File 的属性文件中指定的日志文件中进行。
在 Jython 脚本中使用 log4j
很多人可能会问,为什么你对记录脚本信息感兴趣?大多数情况下,脚本是通过命令行交互式执行的。但是,有很多情况下,让系统为你调用脚本是有意义的。你可能知道,这种技术在环境中经常被用来运行夜间任务,甚至每天在计划好的时间自动调用的任务。对于这些情况,使用 log4j 记录错误或信息非常有用。有些人甚至可能希望创建一个单独的自动化任务,在任务完成后将这些日志文件发送电子邮件。
总体实现与上面相同,最重要的是要记住,你必须在 Jython 路径中包含 log4j.jar 存档和属性文件。准备就绪后,你可以在脚本中使用 log4j。
from org.apache.log4j import *
logger = Logger.getLogger("scriptname")
PropertyConfigurator.configure("C:\path_to_properties\log4j.properties")
logger.info("Test the logging")
作者:Josh Juneau 网址:http://wiki.python.org/jython/JythonMonthly/Articles/August2006/1
另一个 log4j 示例 - Greg Moore¶
此示例需要以下几项。
- 类路径上的 log4j
- 与示例位于同一目录下的 log4j.properties(如下所示)
- 与以下示例位于同一目录下的 example.xml(如下所示)
- 当然还有 Jython
log4j 示例
这是一个简单的示例,展示了在自己的脚本中使用 log4j 的简便性。源代码有很好的文档,但如果你有任何问题或想了解更多信息,请使用你喜欢的搜索引擎并输入 log4j。
from org.apache.log4j import *
class logtest:
def __init__(self):
log.info("start of Logtest")
log.debug('just before file read')
try:
log.warn('file read proceding to processing')
xmlStringData = open('example.xml').read()
except:
#yes, more could have been done here but this is just an example
log.error('file read FAILURE')
log.info('file read proceding to processing')
# since this is just an example processing would go here.
log.warn('its just an example, OK?')
pi = 3.141592681
msg = 'do you like?' + str(pi)
log.info(msg)
log.debug('lets try to parse the string')
if '[CDATA' in xmlStringData:
log.warn('No CDATA section.')
#say good bye and close the log file.
log.info('That all. The End. Good Bye')
log.shutdown()
if __name__ == '__main__':
# loggingTest is just a string that identifies this log.
log = Logger.getLogger("loggingTest")
#use the config data in the properties file
PropertyConfigurator.configure('log4j.properties')
log.info('This is the start of the log file')
logit = logtest()
print '\n\nif you change the log level in the properties'
print "file you'll get varing amouts of log data."
log4j.properties
此文件是上面代码所需的。它需要与示例位于同一目录,但也可以位于任何位置,只要你提供文件的完整路径即可。它配置了 log4j 的运行方式。如果找不到它,它将默认使用默认的日志记录级别。由于这是为了示例目的,下面的文件比实际需要的要大。
#define loging level and output
log4j.rootLogger=debug, stdout, LOGFILE
#log4j.rootLogger=info, LOGFILE
# this 2 lines tie the apache logging into log4j
#log4j.logger.org.apache.axis.SOAPPart=DEBUG
#log4j.logger.httpclient.wire.header=info
#log4j.logger.org.apache.commons.httpclient=DEBUG
# where is the logging going.
# This is for std out and defines the log output format
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} | %p | [%c] %m%n %t
#log it to a file as well. and define a filename, max file size and number of backups
log4j.appender.LOGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.LOGFILE.File=jythonTest.log
log4j.appender.LOGFILE.MaxFileSize=100KB
# Keep one backup file
log4j.appender.LOGFILE.MaxBackupIndex=1
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
# Pattern for logfile - only diff is that date is added
log4j.appender.LOGFILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} | %p | [%c] %m%n
# Other Examples: only time, loglog level, loggerName
#log4j.appender.LOGFILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss},%p,%c %m%n
#above plus filename, linenumber, Class Name, method name
#log4j.appender.LOGFILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss},%p,%c,%F,%L,%C{1},%M %m%n
示例 xml 文件
这是为了完整性而提供的。任何文本文件都可以与上面的示例一起使用,只需更改上面代码中的“open”行即可。
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<GetXmlReport xmlns="http://localhost/Services/GetXmlReport">
<xmlrequest>
<Inquiry>
<Client>
<Type>W</Type>
</Client>
<Report>I</Report>
<Provider>
<ProviderID>TU</ProviderID>
</Provider>
<ClientInfo>
<Name>
<First>Cathrine</First>
<Middle />
<Surname>Knight</Surname>
</Name>
<Account>34-5424-77</Account>
<DateOfBirth>10/12/1938</DateOfBirth>
<Address>
<Line1>4780 Centerville</Line1>
<CityStPostal>Saint Paul, MN 55127</CityStPostal>
</Address>
</ClientInfo>
</Inquiry>
</xmlrequest>
</GetXmlReport>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
作者:Greg Moore 网址:http://wiki.python.org/jython/Log4jExample
使用电子表格¶
以下是一些 Apache Poi 示例。这些示例需要安装 Apache Poi 并将其放在类路径中。
创建电子表格
这来自 Jython 邮件列表,并于 2007 年 9 月发布
这是基于 http://officewriter.softartisans.com/OfficeWriter-306.aspx 上的 Java 代码,并由 Alfonso Reyes 转换为 Jython
#jython poi example. from Jython mailing list
from java.io import FileOutputStream
from java.util import Date
from java.lang import System, Math
from org.apache.poi.hssf.usermodel import *
from org.apache.poi.hssf.util import HSSFColor
startTime = System.currentTimeMillis()
wb = HSSFWorkbook()
fileOut = FileOutputStream("POIOut2.xls")
# Create 3 sheets
sheet1 = wb.createSheet("Sheet1")
sheet2 = wb.createSheet("Sheet2")
sheet3 = wb.createSheet("Sheet3")
sheet3 = wb.createSheet("Sheet4")
# Create a header style
styleHeader = wb.createCellStyle()
fontHeader = wb.createFont()
fontHeader.setBoldweight(2)
fontHeader.setFontHeightInPoints(14)
fontHeader.setFontName("Arial")
styleHeader.setFont(fontHeader)
# Create a style used for the first column
style0 = wb.createCellStyle()
font0 = wb.createFont()
font0.setColor(HSSFColor.RED.index)
style0.setFont(font0)
# Create the style used for dates.
styleDates = wb.createCellStyle()
styleDates.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy h:mm"))
# create the headers
rowHeader = sheet1.createRow(1)
# String value
cell0 = rowHeader.createCell(0)
cell0.setCellStyle(styleHeader)
cell0.setCellValue("Name")
# numbers
for i in range(0, 8, 1):
cell = rowHeader.createCell((i + 1))
cell.setCellStyle(styleHeader)
cell.setCellValue("Data " + str( (i + 1)) )
# Date
cell10 = rowHeader.createCell(9)
cell10.setCellValue("Date")
cell10.setCellStyle(styleHeader)
for i in range(0, 100, 1):
# create a new row
row = sheet1.createRow(i + 2)
for j in range(0, 10, 1):
# create each cell
cell = row.createCell(j)
# Fill the first column with strings
if j == 0:
cell.setCellValue("Product " + str(i))
cell.setCellStyle(style0)
# Fill the next 8 columns with numbers.
elif j < 9:
cell.setCellValue( (Math.random() * 100))
# Fill the last column with dates.
else:
cell.setCellValue(Date())
cell.setCellStyle(styleDates)
# Summary row
rowSummary = sheet1.createRow(102)
sumStyle = wb.createCellStyle()
sumFont = wb.createFont()
sumFont.setBoldweight( 5)
sumFont.setFontHeightInPoints(12)
sumStyle.setFont(sumFont)
sumStyle.setFillPattern(HSSFCellStyle.FINE_DOTS)
sumStyle.setFillForegroundColor(HSSFColor.GREEN.index)
cellSum0 = rowSummary.createCell( 0)
cellSum0.setCellValue("TOTALS:")
cellSum0.setCellStyle(sumStyle)
# numbers
# B
cellB = rowSummary.createCell( 1)
cellB.setCellStyle(sumStyle)
cellB.setCellFormula("SUM(B3:B102)")
读取 Excel 文件
由 Alfonso Reyes 于 2007 年 10 月 14 日发布到 Jython-users 邮件列表中。此 Jython 代码将打开并读取现有的 Excel 文件,你可以在 http://www.nabble.com/file/p13199712/Book1.xls 下载该文件
""" read.py
Read an existant Excel file (Book1.xls) and show it on the screen
"""
from org.apache.poi.hssf.usermodel import *
from java.io import FileInputStream
file = "H:Book1.xls"
print file
fis = FileInputStream(file)
wb = HSSFWorkbook(fis)
sheet = wb.getSheetAt(0)
# get No. of rows
rows = sheet.getPhysicalNumberOfRows()
print wb, sheet, rows
cols = 0 # No. of columns
tmp = 0
# This trick ensures that we get the data properly even if it
# does not start from first few rows
for i in range(0, 10,1):
row = sheet.getRow(i)
if(row != None):
tmp = sheet.getRow(i).getPhysicalNumberOfCells()
if tmp > cols:
cols = tmp
print cols
for r in range(0, rows, 1):
row = sheet.getRow(r)
print r
if(row != None):
for c in range(0, cols, 1):
cell = row.getCell(c)
if cell != None:
print cell
#wb.close()
fis.close()
Jython 和 XML - Greg Moore¶
元素树
这是一个使用 Jython 中的元素树的简单示例。元素树对于将分层数据结构(例如简化的 XML 信息集)存储到内存中,然后将其保存到磁盘非常有用。有关元素树的更多信息,请访问 http://effbot.org/zone/element-index.htm。
从 http://effbot.org/downloads/ 下载元素树
from elementtree import ElementTree as ET
root = ET.Element("html")
head = ET.SubElement(root, "head")
title = ET.SubElement(head, "title")
title.text = "Page Title"
body = ET.SubElement(root, "body")
body.set("bgcolor", "#ffffff")
body.text = "Hello, World!"
tree = ET.ElementTree(root)
tree.write("page.xhtml")
import sys
tree.write(sys.stdout)
这将生成
<html><head><title>Page Title</title></head><body bgcolor="#ffffff">Hello, World!</body></html>
作者:Greg Moore 网址:http://wiki.python.org/jython/XmlRelatedExamples
使用 ROME 编写和解析 RSS - Josh Juneau¶
简介
RSS 现在是一项古老的技术……它已经存在多年了。但是,它仍然是一项非常有用的技术,用于传播新闻和其他信息。java.net 上的 ROME 项目正在帮助任何 Java 开发人员轻松地解析、生成和发布 RSS 和 Atom 提要。
由于我特别喜欢将 Java 转换为 Jython 代码,因此我从 Project ROME wiki 中获取了简单的示例,并将 Java RSS 阅读器和编写器代码转换为 Jython。这非常容易做到,只需要几行代码即可。
请记住,您仍然需要为这样的 RSS 阅读器构建一个前端查看器,但我认为您会明白使用 Project ROME 和 Jython 解析提要有多么容易。
设置 CLASSPATH
为了使用此示例,您必须获取 ROME 和 JDOM jar 文件并将它们放入您的 CLASSPATH 中。
Windows
set CLASSPATH=C:\Jython\Jython2.2\rome-0.9.jar;%CLASSPATH%
set CLASSPATH=C:\Jython\Jython2.2\jdom.jar;%CLASSPATH%
OSX
export CLASSPATH=/path/to/rome-0.9.jar:/path/to/jdom.jar
解析提要
使用 ROME 解析提要很容易。使用 Jython 与 ROME 使得它更易于使用优雅的 Jython 语法。我并不是专业的 Python 或 Jython 程序员,我是一名专业的 Java 程序员,因此我的 Jython 解释可能比它应该的更冗长。
我从 ROME 网站获取了 FeedReader 示例,并将其翻译成下面的 Jython。您可以将以下代码复制粘贴到您自己的 FeedReader.py 模块中并运行它来解析提要。但是,输出是未格式化的且难看的……创建美观的前端取决于您。
FeedReader.py
########################################
# File: FeedReader.py
#
# This module can be used to parse an RSS feed
########################################
from java.net import URL
from java.io import InputStreamReader
from java.lang import Exception
from java.lang import Object
from com.sun.syndication.feed.synd import SyndFeed
from com.sun.syndication.io import SyndFeedInput
from com.sun.syndication.io import XmlReader
class FeedReader(Object):
def __init__(self, url):
self.inUrl = url
def readFeed(self):
ok = False
#####################################
# If url passed in is blank, then use a default
#####################################
if self.inUrl != '':
rssUrl = self.inUrl
else:
rssUrl = "http://www.dzone.com/feed/frontpage/java/rss.xml"
#####################################
# Parse feed located at given URL
#####################################
try:
feedUrl = URL(rssUrl)
input = SyndFeedInput()
feed = input.build(XmlReader(feedUrl))
####################################
# Do something here with feed data
####################################
print(feed)
ok = True
except Exception, e:
print 'An exception has occurred', e
if ok != True:
print 'An error has occurred in this reader'
if __name__== "__main__":
reader = FeedReader('')
reader.readFeed()
print '****************Command Complete...RSS has been parsed*****************'
创建提要
与解析提要类似,写入提要也很容易。当一个人创建提要时,它似乎比解析更复杂,但如果您熟悉 XML 及其一般结构,那么它应该相对容易。
创建提要是一个三步过程。您必须首先创建提要元素本身,然后必须添加单个提要条目,最后必须发布 XML。
FeedWriter.py
########################################
# File: FeedReader.py
#
# This module can be used to create an RSS feed
########################################
from com.sun.syndication.feed.synd import *
from com.sun.syndication.io import SyndFeedOutput
from java.io import FileWriter
from java.io import Writer
from java.text import DateFormat
from java.text import SimpleDateFormat
from java.util import ArrayList
from java.util import List
from java.lang import Object
class FeedWriter(Object):
####################################
# Set up the date format
####################################
def __init__(self, type, name):
self.DATE_PARSER = SimpleDateFormat('yyyy-MM-dd')
self.feedType = type
self.fileName = name
def writeFeed(self):
ok = False
try:
################################
# Create the feed itself
################################
feed = SyndFeedImpl()
feed.feedType =self.feedType
feed.title = 'Sample Feed (created with ROME)'
feed.link = 'http://rome.dev.java.net'
feed.description = 'This feed has been created using ROME and Jython'
###############################
# Add entries to the feed
###############################
entries = ArrayList()
entry = SyndEntryImpl()
entry.title = 'ROME v1.0'
entry.link = 'http://wiki.java.net/bin/view/Javawsxml/Rome01'
entry.publishedDate = self.DATE_PARSER.parse("2004-06-08")
description = SyndContentImpl()
description.type = 'text/plain'
description.value = 'Initial Release of ROME'
entry.description = description
entries.add(entry)
entry = SyndEntryImpl()
entry.title = 'ROME v2.0'
entry.link = 'http://wiki.java.net/bin/view/Javawsxml/Rome02'
entry.publishedDate = self.DATE_PARSER.parse("2004-06-16")
description = SyndContentImpl()
description.type = 'text/plain'
description.value = 'Bug fixes, minor API changes and some new features'
entry.description = description
entries.add(entry)
entry = SyndEntryImpl()
entry.title = 'ROME v3.0'
entry.link = 'http://wiki.java.net/bin/view/Javawsxml/Rome03'
entry.publishedDate = self.DATE_PARSER.parse("2004-07-27")
description = SyndContentImpl()
description.type = 'text/plain'
description.value = '<p>More Bug fixes, mor API changes, some new features and some Unit testing</p>'
entry.description = description
entries.add(entry)
feed.entries = entries
###############################
# Publish the XML
###############################
writer = FileWriter(self.fileName)
output = SyndFeedOutput()
output.output(feed,writer)
writer.close()
print('The feed has been written to the file')
ok = True
except Exception, e:
print 'There has been an exception raised',e
if ok == False:
print 'Feed Not Printed'
if __name__== "__main__":
####################################
# You must change his file location
# if not using Windows environment
####################################
writer = FeedWriter('rss_2.0','C:\\TEMP\\testRss.xml')
writer.writeFeed()
print '****************Command Complete...RSS XML has been created*****************'
创建 XML 后,您显然需要将其放置在某个 Web 服务器上,以便其他人可以使用您的提要。FeedWriter.py 模块可能是用于创建和管理 RSS 提要的应用程序中的众多模块之一,但您明白了。
结论
如您所见,使用 ROME 库处理 RSS 提要非常容易。在 Jython 应用程序中使用 ROME 库非常简单。既然您已经了解了创建和解析提要的简单性,如果您愿意,可以将这些技术应用于更完整的 RSS 管理应用程序。RSS 通信的世界就在您的指尖!
作者:Josh Juneau URL:http://wiki.python.org/jython/JythonMonthly/Articles/October2007/1
使用 CLASSPATH - Steve Langer¶
简介
在 2006 年 10 月至 11 月期间,jython-users 组中有一个名为“将 JAR 添加到 sys.path”的线程。更准确地说,那里的目标是在运行时将 JAR 添加到 sys.path。许多人问:“为什么要这样做?” 嗯,至少有两个很好的理由。首先,如果您想分发一个包含非标准 Jar 的 jython 或 Java 包。也许您想让目标用户的生活更轻松,而不是要求他们知道如何设置环境变量。第二个更令人信服的理由是在没有正常用户帐户来提供环境变量的情况下。
“什么?”,你问。嗯,在我的情况下,我以以下方式遇到了这个问题。我正在开发一个开源 IHE 图像存档参与者,并且需要一个 Web 界面。我在客户端使用 AJAX 通过 CGI 将数据库调用路由到启用 jython-JDBC 的 API。从命令行测试 jython-JDBC API 工作正常,我在 CLASSPATH 中有 PostGres 驱动程序。但是,当通过 Web 界面调用时,我得到了“zxJDBC 错误,未找到 postGres 驱动程序”错误。为什么?因为 APACHE 正在调用 API,而 APACHE 不是具有环境变量的正常帐户。
该怎么办?
jython-users 线程有很多建议,但没有一个被发现有效。对于书籍,O’Reilly 的“Jython Essentials”第 11 章在“系统和文件模块”下提到“……要在运行时加载类,还需要一个合适的类加载器。” 当然,除此之外没有提及。过了一会儿,我突然意识到,也许 Java 世界中有人遇到了类似的问题并解决了它。然后,所有需要做的就是翻译那个解决方案。而这正是发生的事情。
方法
为了简洁起见,我们不会在此重复原始的 Java 代码。这是我调用 Jython 类的方式(请注意,可以使用 addFile 或 addURL,具体取决于 Jar 是否位于本地可访问的文件系统或远程服务器上)。
import sys
from com.ziclix.python.sql import zxJDBC
d,u,p,v = "jdbc:postgresql://localhost/img_arc2","postgres","","org.postgresql.Driver"
try :
# if called from command line with .login CLASSPATH setup right,this works
db = zxJDBC.connect(d, u, p, v)
except:
# if called from Apache or account where the .login has not set CLASSPATH
# need to use run-time CLASSPATH Hacker
try :
jarLoad = classPathHacker()
a = jarLoad.addFile("/usr/share/java/postgresql-jdbc3.jar")
db = zxJDBC.connect(d, u, p, v)
except :
sys.exit ("still failed \n%s" % (sys.exc_info() ))
这是“classPathHacker”类,这是原始作者对他的解决方案的称呼。实际上,您只需在 Google 上搜索“classPathHacker”即可找到 Java 解决方案。
class classPathHacker :
##########################################################
# from http://forum.java.sun.com/thread.jspa?threadID=300557
#
# Author: SG Langer Jan 2007 translated the above Java to this
# Jython class
# Purpose: Allow runtime additions of new Class/jars either from
# local files or URL
######################################################
import java.lang.reflect.Method
import java.io.File
import java.net.URL
import java.net.URLClassLoader
import jarray
def addFile (self, s):
#############################################
# Purpose: If adding a file/jar call this first
# with s = path_to_jar
#############################################
module = "utils:classPathHacker: addFile"
# make a URL out of 's'
f = self.java.io.File (s)
u = f.toURL ()
a = self.addURL (u)
return a
def addURL (self, u):
##################################
# Purpose: Call this with u= URL for
# the new Class/jar to be loaded
#################################
module = "utils:classPathHacker: addURL"
parameters = self.jarray.array([self.java.net.URL], self.java.lang.Class)
sysloader = self.java.lang.ClassLoader.getSystemClassLoader()
sysclass = self.java.net.URLClassLoader
method = sysclass.getDeclaredMethod("addURL", parameters)
a = method.setAccessible(1)
jar_a = self.jarray.array([u], self.java.lang.Object)
b = method.invoke(sysloader, jar_a)
return u
结论
就是这样。对于它所做的事情来说,令人沮丧地简短,但这又证明了这种语言的强大功能。我希望您能像我一样发现它强大而有用。它允许将 jython 包及其所有文件依赖项一起分发到安装目录中,从而使用户或开发人员无需更改用户环境变量,这将带来更多程序员控制,从而提高可靠性。
作者:Steve Langer URL:http://wiki.python.org/jython/JythonMonthly/Articles/January2007/3
Ant¶
以下 Ant 示例仅适用于 Jython 版本 2.2.1 及更早版本,因为需要使用 jythonc。从 2.5.0 开始,Jythonc 不再与 Jython 一起分发。此示例可以使用对象工厂重写,以适用于当前版本的 Jython。
使用 Jython 编写 Ant 任务 - Ed Takema¶
Ant 是当前用于 Java 构建的首选工具。这部分是因为它是第一个出现在场景中的面向 Java 的构建工具,并且因为当时的冠军Make已经老旧,并且在 Java 社区中不受欢迎。但是,Java 构建变得越来越困难,如今人们普遍对 ant1 不满意。请特别注意 Bruce Eckel 的评论和 Martin Fowler 的进一步评论。Bruce Eckels 帖子中的评论显示了类似的挫折。Fowler 总结了这些问题,如下所示
… 简单的构建很容易表达为一系列任务和依赖项。对于此类构建,ant/make 的功能运行良好。但是,更复杂的构建需要条件逻辑,这需要更通用的编程语言结构 - 这就是 ant/make 失败的地方。Ken Arnold 的文章 The Sum of Ant 使我了解了 Jonathon Simon 的文章 Scripting with Jython Instead of XML,并让我开始思考如何使用 Jython 扩展 ant。Simon 的文章介绍了一种从 Jython 驱动 Ant 任务、测试等的技巧。我在这里介绍的是一种将 Jython 脚本嵌入 Ant 的技巧,这与 Simon 的方法相反,但希望为 ant 构建增加功能和灵活性。
我使用 ant 自动化大型构建的经验与 Fowler 所指的并不相同。最终,构建需要在 xml 文件中执行大量奇特的条件逻辑,并最终将逻辑埋藏在脚本中,或者在用 Java 编写的许多自定义任务中。如果您的构建包含 ant 不了解如何构建的非 Java 源代码,情况尤其如此。在一种特定情况下,用于构建的自定义任务集实际上是它自己的系统,其维护和人员成本相当高。大量脚本对于企业构建系统来说很快就会成为问题,因为它们难以标准化,并且跨平台问题始终存在。
幸运的是,并非一切都已丢失。Ant 仍在不断发展,版本 1.6 对于大型构建系统来说是一个重大进步。Mike Spille 在他的文章 ANT’s Finally a Real Build Tool 中证明,新的 <import> 标签现在允许构建管理器编写真正模块化和标准化的基于 Ant 的构建系统!随着 Ant 的发展,越来越多的这些问题将得到解决。
Make 始终具有的优势之一是能够轻松调用脚本和命令实用程序。这绝对是 Ant script/exec 任务可以做到的,但感觉非常不 Java。我们需要一种优雅的方式来为 Ant 构建添加临时行为……以一种 Java 式的方式。
编写自定义 Ant 任务¶
我认为可以做的是,在 ant 构建中使用脚本工具时采取更谨慎的方法。与其仅仅创建一堆从 exec 或 script 任务调用的脚本,我建议我们在高级脚本语言中编写自定义 ant 构建任务……在本例中为 Jython。
编写自定义 ant 任务允许构建管理器利用其构建中已编写的海量任务,同时在自定义 ant 任务中编写自然属于更灵活工具的内容,这些自定义 ant 任务本身可以重复使用,与 Java 本身一样跨平台,并且完全集成到 Ant 中。由于 Ant 使用 Java 自省来确定自定义任务的功能,因此 Jython 是完成此任务的完美工具。我们只需要确保 Jython 类中存在 Ant 预期的方法,Ant 不会注意到区别。
我们将实现的是永恒的 SimpleTask,它只不过是 ant 的“Hello World”。它应该足以演示关键步骤。
设置开发环境
要编译本文中的 Jython 源代码,您需要将 ant.jar 文件添加到您的类路径中。这将使 Jython 可以扩展它,我们将在下面进行操作。为此,请定义您的类路径
<DOS>
set CLASSPATH=c:\path\to\ant\lib\ant.jar
<UNIX>
export CLASSPATH=/path/to/ant/lib/ant.jar
SimpleTask Jython 类
以下是使用 Jython(python)编写的非常简单的 Ant 任务。将其保存为 SimpleTask.py
from org.apache.tools.ant import Task
class SimpleTask(Task):
message = ""
def execute(self):
"""@sig public void execute()"""
Task.log(self, "Message: " + self.message)
def setMessage(this, aMessage):
"""@sig public void setMessage(java.lang.String str)"""
this.message = aMessage
这个简单的 Jython 类扩展了 ant Task 超类。对于我们想要为该任务支持的每个属性,我们编写一个 setXXXXX 方法,其中 XXXXX 对应于我们将在 ant 构建文件中设置的属性。Ant 从该类创建对象,调用 setXXXXX 方法来设置属性,然后调用 execute 方法(实际上,它调用 Task 超类上的 perform 方法,该方法调用 execute() 方法)。所以让我们试试吧。
将 Jython 代码编译为 Jar
要将其构建为可在 Ant 中使用的 jar 文件,请执行以下操作
jythonc -a -c -d -j myTasks.jar SimpleTask.py
这将生成一个 jar 文件 myTasks.jar,并将 jython 核心支持类包含在 jar 中。将此 jar 文件复制到您的 ant 安装的 lib 目录中。在我的情况下,我将其复制到 c:toolsantlib。
用于使用该任务的 Build.XML 文件
一旦您完成了这些操作,这里有一个非常简单的测试 ant 构建文件来测试您的自定义 jython 任务。
<project name="ant jython demo" default="testit" basedir=".">
<!-- Define the tasks we are building -->
<taskdef name="Simple" classname="SimpleTask" />
<!-- Test Case starts here -->
<target name="testit">
<Simple message="Hello World!" />
</target>
</project>
一个任务容器任务
好吧,这是一个非常简单的任务。我们还能做什么?好吧,天空才是极限。这是一个任务容器的示例。在这种情况下,该任务保存对一组其他任务的引用(在本例中为 SimpleTask 任务)
from org.apache.tools.ant import Task
from org.apache.tools.ant import TaskContainer
class SimpleContainer(TaskContainer):
subtasks = []
def execute(this):
"""@sig public void execute()"""
for task in this.subtasks:
task.perform()
def createSimpleTask(self):
"""@sig public java.lang.Object createSimpleTask()"""
task = SimpleTask()
self.subtasks.append(task)
return task
class SimpleTask(Task):
message = ""
def execute(self):
"""@sig public void execute()"""
Task.log(self, "Message: " + self.message)
def setMessage(this, aMessage):
"""@sig public void setMessage(java.lang.String str)"""
this.message = aMessage
SimpleContainer 扩展了 TaskContainer java 类。它的 createSimpleTask 方法创建一个 SimpleTask 对象并将其返回给 Ant,以便可以设置其属性。然后,当所有任务都已添加到容器中并设置了它们的属性后,将调用 SimpleContainer 类上的 execute 方法,该方法依次调用每个包含任务上的 perform 方法。请注意,perform 方法是从 Task 超类继承的,它依次调用 execute 方法,我们已经重写了该方法。
用于使用 TaskContainer 的 Build.XML 文件
这里有一个 ant 构建文件来测试您的自定义 jython 任务容器。请注意,您不需要为包含的 SimpleTask 包含任务定义,除非您想直接使用它。createSimpleTask 工厂方法会为您完成此操作。
<project name="ant jython demo" default="testit" basedir=".">
<!-- Define the tasks we are building -->
<taskdef name="Container" classname="SimpleContainer" />
<!-- Test Case starts here -->
<target name="testit">
<Container>
<SimpleTask message="hello" />
<SimpleTask message="there" />
</Container>
</target>
</project>
需要注意的事项
当我学习这种技术时,我发现魔术文档字符串对于强制 Jython 将正确的方法放入生成的 java 类中是真正必要的。例如
"""@sig public void execute()"""
这主要是因为 Ant 的内省会查找这些特定方法和签名。这些文档字符串是必需的,否则 Ant 不会将这些类识别为 Ant 任务。
我还了解到,为了让 Jython 扩展 java 类,它必须使用以下语法专门导入 java 类
from org.apache.tools.ant import Task
from org.apache.tools.ant import TaskContainer
class MyTask(Task):
...
You can not use this syntax:
import org.apache.tools.ant.Task
import org.apache.tools.ant.TaskContainer
class MyTask(org.apache.tools.ant.Task):
...
这是因为,出于某种原因,Jython 无法确定 MyTask 正在扩展此 java 类,因此不会生成正确的 Java 包装类。当您运行 jythonc 编译器时,您会看到类似以下内容的输出,您就会知道它正常工作了
processing SimpleTask
Required packages:
org.apache.tools.ant
Creating adapters:
Creating .java files:
SimpleTask module
SimpleTask extends org.apache.tools.ant.Task <<<
总结¶
所以,这就是它。以下是对为什么这是一种有用技术的快速总结。
首先,使用粘合语言编写与第三方工具和系统集成的 ant 任务要快得多,而 python/jython 在这方面非常出色。这确实是我尝试这种技术的首要动机。
其次,Jython 比其他脚本语言(可以使用 Ant 的 exec 或 script 任务运行)具有优势,因为它可以与 Ant 紧密集成(即使用相同的日志记录方法、相同的设置等)。这使得构建标准化的构建环境变得更加容易。
最后,与最后一点相关的是,Jython 可以编译为 java 字节码,它像任何 java 类文件一样运行。这意味着您不必安装 jython 就可以使用自定义任务,并且您的自定义任务(如果编写良好)可以在各种平台上运行。
我认为这是为 Ant 构建添加灵活性和更多集成点的合理方法。
作者:Ed Taekema 网址:http://www.fishandcross.com/articles/AntTasksWithJython.html