第 17 章:部署目标

Jython 应用程序的部署因容器而异。但是,它们都非常相似,通常允许部署 WAR 文件或解压缩目录 Web 应用程序。部署到“云端”是一个完全不同的场景。一些云环境提供典型的 Java 应用程序服务器用于托管,而另一些环境(如 Google App Engine 和移动设备)则运行方式略有不同。在本章中,我们将讨论如何将基于 Web 的 Jython 应用程序部署到一些最广泛使用的 Java 应用程序服务器。我们还将介绍将 Jython Web 应用程序部署到 Google App Engine 和移动设备。虽然许多部署场景非常相似,但本章将介绍一些容器之间的差异。

最后,要记住的最重要的事情之一是,我们需要让 Jython 可用于我们的应用程序。有不同的方法可以做到这一点,要么确保 jython.jar 文件包含在应用程序服务器中,要么将 JAR 直接打包到每个 Web 应用程序中。本章假设您使用的是后一种技术。将 jython.jar 直接放置到每个 Web 应用程序中是一个好主意,因为它允许 Web 应用程序遵循 Java 的“随处部署”范式。您无需担心是部署到 Tomcat 还是 Glassfish,因为 Jython 运行时已嵌入到您的应用程序中。

另一个新颖且极具吸引力的解决方案是部署到新的 Java Store。在撰写本文时,Java Store 仍处于 Alpha 阶段,但最终它将为 Java 应用程序开发人员提供一个地方来部署他们的应用程序,并通过一个经过精心打磨的商店应用程序进行分发。Java Store 的分发中心被称为 Java Warehouse。在本章中,我们将讨论一种可能的解决方案,用于打包应用程序以部署到 Java Warehouse。

最后,本节将简要介绍为什么移动部署目前还不是 Jython 的可行选择。虽然移动领域存在一些目标,即 Android 和 JavaFX,但这两个环境都还很新,Jython 尚未针对这两个环境进行优化。

应用程序服务器

与任何 Java Web 应用程序一样,标准 Web 存档 (WAR) 文件在当今可用的所有 Java 应用程序服务器中都是通用的。这很好,因为它在使用 Java 名称带来的“一次编写,随处运行”理念时,使事情变得更容易。使用 Jython 部署到应用程序服务器的优点就在于此,我们可以利用 JVM 的技术来简化我们的工作,并将 Jython Web 应用程序以 WAR 格式部署到任何应用程序服务器,而只需很少的调整。

如果您之前没有在 Jython 上使用过 Django 或 Pylons,那么您可能不知道最终要部署的应用程序是 WAR 格式的。这很好,因为它不依赖于应用程序应该如何部署的假设。所有 WAR 文件都以相同的方式部署,具体取决于每个应用程序服务器。本节将讨论如何在三个最广泛使用的 Java 应用程序服务器上部署 WAR 文件。现在,本节没有涵盖所有应用程序服务器,主要是因为当今可用的服务器数量众多。毫无疑问,这样的文档需要不止一节内容。但是,对于当今可用于以 WAR 文件格式部署 Jython Web 应用程序的任何应用程序服务器,您应该能够遵循类似的部署说明。

Tomcat

Tomcat 可能是所有 Java 应用程序服务器中最广泛使用的,与其他一些可用选项相比,它提供易于管理和占用空间小的优势。Tomcat 可以插入当今使用的大多数 IDE,因此您可以从开发环境中管理 Web 容器。这使得它非常方便在运行时部署和取消部署应用程序。在本节中,我使用了 Netbeans 6.7,因此可能包含一些对其的引用。

要开始,请从 http://tomcat.net.cn/ 上的网站下载 Apache Tomcat 服务器。Tomcat 不断发展,因此我注意到在撰写本书时,部署过程针对 6.0.20 版本。下载服务器并将其放置到硬盘驱动器上的某个位置后,您可能需要更改权限。我必须在整个 apache-tomcat-6.0.20 目录上使用 chmod +x 命令才能运行服务器。您还需要通过进入 /conf/tomcat-users.xml 文件并添加一个来配置管理帐户。请确保为管理帐户授予“manager”角色。完成后,它应该类似于以下内容。

*tomcat-users.xml*
<tomcat-users>
   <user username="admin" password="myadminpassword" roles="manager"/>
</tomcat-users>

完成此操作后,您可以根据需要将安装添加到您选择的 IDE 环境中。例如,如果您希望添加到 Netbeans 6.7,您需要转到导航器中的“服务”选项卡,右键单击服务器,选择“Tomcat 6.x”选项,然后填写与您的环境相关的适当信息。完成后,您将能够从 IDE 启动、停止和管理 Tomcat 安装。

部署 Web 启动

部署 Web 启动应用程序就像将必要的文件复制到 Web 服务器上可通过 Web 访问的位置一样简单。对于 Tomcat,您需要将 Web 启动应用程序的内容复制到“<tomcat-root>/webapps/ROOT”目录中包含的单个目录中。例如,如果您有一个名为 JythonWebStart 的 Web 启动应用程序,那么您将把 JAR 文件以及应用程序的 JNLP 和 HTML 文件打包到名为 JythonWebStart 的目录中,然后将该目录放置到“<tomcat-root>/webapps/ROOT”目录中。

将应用程序复制到适当的位置后,如果 Tomcat 已启动,您应该能够通过 Web 访问它。URL 应该类似于以下内容:http://your-server:8080/JythonWebStart/launch.jnlp。当然,您需要使用您的服务器名称和您正在使用的端口以及应用程序的适当 JNLP 名称。

部署 WAR 或展开目录应用程序

要将 Web 应用程序部署到 Tomcat,您有两个选择。您可以使用包含整个 Web 应用程序所有内容的 WAR 文件,也可以部署展开目录应用程序,这基本上是将整个 Web 应用程序目录结构复制粘贴到“<tomcat-root>/webapps/ROOT”目录中。两种方式都将起作用,我们将在本节中讨论每种技术。

对于手动部署 Web 应用程序,您可以将展开目录 Web 应用程序或 WAR 文件复制到“<tomat-root>/webapps”目录中。默认情况下,Tomcat 设置为“自动部署”应用程序。这意味着您可以在将 WAR 或展开目录复制到“webapps”位置时启动 Tomcat。完成此操作后,如果您打开了终端(或在 IDE 中),您应该会看到 Tomcat 服务器的一些反馈。几秒钟后,应用程序应该成功部署,并可以通过 URL 访问。部署展开目录应用程序的额外好处是,您可以获取应用程序中的任何文件并随意更改它。完成更改后,保存文件时,该文件将重新部署……这确实节省了开发时间!

如果您不想启用自动部署(可能是在生产环境中),那么您可以在服务器启动时部署应用程序。此过程基本上与“自动部署”相同,只是复制到“webapps”目录中的任何新应用程序在服务器重新启动之前都不会部署。最后,您始终可以使用 Tomcat 管理器来部署 Web 应用程序。为此,请在 Web 浏览器中打开 Tomcat 的索引,通常是 http://127.0.0.1:8080/index.html,然后单击左侧菜单中的“管理器”链接。您需要使用管理员密码进行身份验证,但一旦进入控制台,部署就非常容易。为了避免冗余,我将再次将您重定向到 Tomcat 文档,以获取有关通过 Tomcat 管理器控制台部署 Web 应用程序的更多信息。

Glassfish

在撰写本文时,Glassfish V2 应用程序服务器是主流且广泛使用的。Glassfish V3 服务器仍处于预览模式,但显示出对 Jython 应用程序部署的巨大潜力。在本节中,我们将介绍 WAR 和 Web 启动部署到 Glassfish V2,因为它是最广泛使用的版本。我们还将讨论在 Glassfish V3 上部署 Django,因为此版本已添加对 Django(以及更多 Python Web 框架)的支持。Glassfish 在部署方面与 Tomcat 非常相似,但有一些细微的差别,将在本节中介绍。

首先,您需要从 https://glassfish.dev.java.net/ 网站下载 Glassfish 发行版。同样,我建议下载 V2,因为它是在撰写本文时最广泛使用的版本。安装非常容易,但比 Tomcat 更复杂一些。本文不会介绍 Glassfish 的安装,因为它会根据您使用的版本而有所不同。Glassfish 网站上提供了每个版本的详细说明,因此我将您重定向到那里以获取更多信息。

安装完 Glassfish 后,您可以通过命令行或终端使用服务器,也可以像 Tomcat 一样使用 IDE。要将 Glassfish V2 或 V3 安装注册到 Netbeans 6.7,只需转到 Netbeans 导航器中的“服务”选项卡,右键单击“服务器”,然后添加您要注册的版本。出现“添加服务器实例”窗口后,只需根据您的环境填写信息即可。

有一个名为“admin”的管理员用户,默认情况下会使用 Glassfish 安装进行设置。为了更改默认密码,最好启动 Glassfish 并登录到管理控制台。默认管理控制台端口为 4848。

部署 Web 启动

部署 Web 启动应用程序与任何其他 Web 服务器基本相同,您只需通过 Web 使 Web 启动 JAR、JNLP 和 HTML 文件可访问即可。在 Glassfish 上,您需要进入“domain”目录,您将在其中找到一个“docroot”。路径应类似于“<glassfish-install-loc>/domains/domain1/docroot”。放置在 docroot 区域内的任何内容都对 Web 可见,因此当然,您将在此处放置任何 Web 启动应用程序目录。同样,典型的 Web 启动应用程序将包含您的应用程序 JAR 文件、JNLP 文件和用于打开 JNLP 的 HTML 页面。所有这些文件通常应放置在根据您的应用程序适当地命名的目录中,然后您可以将此目录复制到 docroot 中。

WAR 文件和展开目录部署

同样,使用 Glassfish 部署应用程序有多种方法。假设您使用的是 V2,您可以选择“热部署”或使用 Glassfish 管理控制台来部署您的应用程序。Glassfish 将与展开目录或 WAR 文件部署方案一起使用。默认情况下,Glassfish 的“自动部署”选项已启用,因此将 WAR 或展开目录应用程序复制到自动部署位置以进行部署非常容易。如果应用程序服务器已启动,它将自动启动您的应用程序(如果它在没有问题的情况下运行)。Glassfish V2 的自动部署目录位于“<glassfish-install-loc>/domains/domain1/autodeploy”位置。

Glassfish v3 Django 部署

Glassifish V3 服务器内置了一些功能,可以帮助简化部署 Django 应用程序的过程。将来,还将支持其他 Jython Web 框架,例如 Pylons。

其他 Java 应用程序服务器

如果您已阅读前面各节中包含的信息,那么您对将 Jython Web 应用程序部署到 Java 应用程序服务器的了解相当多。在大多数情况下,部署 Jython Web 应用程序与部署 Java Web 应用程序之间没有区别。您必须确保您包含了引言中提到的jython.jar,但在大多数情况下,部署是相同的。但是,我在一些应用程序服务器(如 JBoss)中遇到了情况,运行 Jython 应用程序并不那么简单。例如,我尝试在 JBoss 应用程序服务器 5.1.0 GA 上部署 Jython servlet 应用程序,遇到了很多问题。首先,我必须手动将servlet-api.jar 添加到应用程序中,因为我无法在没有这样做的情况下在 Netbeans 中编译应用程序……在 Tomcat 或 Glassfish 中情况并非如此。同样,我在尝试将 Jython Web 应用程序部署到 JBoss 时遇到了问题,因为容器扫描jython.jar 时出现了一些错误。

总而言之,通过一些调整,也许再加上一个额外的 XML 配置文件,Jython Web 应用程序就可以部署到大多数 Java 应用程序服务器上。将应用程序部署到 Java 应用程序服务器上的好处是,您可以完全控制环境。例如,您可以将 *jython.jar* 文件嵌入到应用程序服务器的 lib 目录中,以便在启动时加载它,并使其可供环境中运行的所有应用程序使用。同样,您还可以控制其他必要的组件,例如数据库连接池等等。如果您部署到“云”中的其他服务,那么您对环境的控制非常有限。在下一节中,我们将研究 Google 的一种这样的环境,称为 Google App Engine。虽然这种“云”服务与基本的 Java Web 应用程序服务器完全不同,但它包含一些不错的功能,允许您在云中部署之前测试应用程序。

Google App Engine

Google App Engine 是一个新兴的平台,至少在撰写本文时是这样。它对 Java 平台来说是新鲜事物,可以用来部署用几乎任何在 JVM 上运行的语言编写的应用程序,包括 Jython。App Engine 于 2008 年 4 月上线,允许 Python 开发人员开始使用它的服务来托管 Python 应用程序和库。在 2009 年春季,App Engine 添加了对 Java 平台的支持。除了支持 Java 语言之外,大多数其他在 JVM 上运行的语言也可以部署到 Google App Engine 上并运行,包括 Jython。据称,将来会支持更多编程语言,但在撰写本文时,Python 和 Java 是唯一支持的语言。

App Engine 实际上运行的是标准 Java 库的精简版本。您必须下载并使用 Google App Engine SDK for Java 进行开发,以确保您的应用程序可以在该环境中运行。您可以通过访问以下链接下载 SDK:http://code.google.com/appengine/downloads.html,并查看 Google App Engine 网站上提供的丰富文档。SDK 包含一个完整的开发 Web 服务器,可用于在部署之前测试您的代码,以及从简单的 JSP 程序到使用 Google 身份验证的复杂演示的多个演示应用程序。毫无疑问,Google 在为 App Engine 创建一个易于学习的环境方面做得很好,以便开发人员能够快速上手。

在本节中,您将学习如何开始使用 Google App Engine SDK,以及如何部署一些 Jython Web 应用程序。您将学习如何部署 Jython servlet 应用程序以及使用 modjy 的 WSGI 应用程序。一旦您学会了如何使用开发环境开发和使用 Jython Google App Engine 程序,您将学习一些关于云部署的具体知识。如果您还没有这样做,请务必访问上一段中提到的链接并下载 SDK,以便您能够在接下来的部分中跟随操作。

请注意,Google App Engine 是一个非常大的主题。可以写整本书来讲述在 App Engine 上运行的 Jython 应用程序的开发。也就是说,我将介绍开始使用 App Engine 开发 Jython 应用程序的基本知识。阅读完本节后,我建议您访问 Google App Engine 文档以获取更多详细信息。

从 SDK 演示开始

我们将从运行 Google App Engine SDK 附带的“guestbook”演示应用程序开始。这是一个非常简单的 Java 应用程序,允许用户使用电子邮件地址登录并发布消息到屏幕上。为了启动 SDK Web 服务器并运行“guestbook”应用程序,请打开一个终端并进入您解压缩 Google App Engine .zip 文件的目录,然后运行以下命令

<app-engine-base-directory>/bin/dev_appserver.sh demos/guestbook/war

当然,如果您在 Windows 上运行,则有一个相应的 .bat 脚本供您运行,该脚本将启动 Web 服务器。发出上述命令后,Web 服务器将在几秒钟内启动。然后,您可以打开浏览器并访问http://127.0.0.1:8080以调用“留言簿”应用程序。这是一个基于 JSP 的基本 Java Web 应用程序,但我们可以部署 Jython 应用程序并以相同的方式使用它,我们将在稍后看到。您可以通过按“CTRL+C”来停止 Web 服务器。

部署到云

在将您的应用程序部署到云之前,您当然必须先在 Google App Engine 上设置一个帐户。如果您有另一个 Google 帐户,例如 GMail,那么您可以轻松地使用相同的用户名激活您的 App Engine 帐户。为此,请访问 Google App Engine 链接:http://code.google.com/appengine/ 并点击“注册”。输入您现有的帐户信息或创建一个新帐户以开始使用。

帐户激活后,您需要通过点击“创建应用程序”按钮来创建一个应用程序。如果您使用的是免费的 App Engine 帐户,则您共有 10 个可用的应用程序插槽可以使用。创建应用程序后,您就可以开始部署到云了。在本节中,我们创建了一个名为jythongae的应用程序。这是您必须在 App Engine 上创建的应用程序的名称。您还必须确保在appengine-web.xml文件中提供此名称。

使用项目

Google App Engine 提供项目模板,帮助您开始使用正确的目录结构进行开发。Eclipse 有一个插件,可以轻松地生成 Google App Engine 项目并将它们部署到 App Engine。如果您有兴趣使用该插件,请访问http://code.google.com/appengine/docs/java/tools/eclipse.html以阅读更多信息并下载该插件。类似地,Netbeans 也有一个 App Engine 插件,该插件在 Kenai 网站上提供,名为nbappengine (http://kenai.com/projects/nbappengine)。在本教程中,我们将介绍使用 Netbeans 6.7 开发一个简单的 Jython servlet 应用程序以部署到 App Engine。您可以下载并使用这些 IDE 插件中提供的模板之一,或者简单地创建一个新的 Netbeans 项目并使用 App Engine SDK 提供的模板(<app-engine-base-directory/demos/new_project_template)来创建您的项目目录结构。在本教程中,我们将使用nbappengine插件。如果您使用的是 Eclipse,您将在本教程之后找到一个部分,其中提供了一些 Eclipse 插件的具体信息。

为了安装nbappengine插件,您需要通过选择设置选项卡并将更新中心添加到 Netbeans 插件中心,使用http://deadlock.netbeans.org/hudson/job/nbappengine/lastSuccessfulBuild/artifact/build/updates/updates.xml.gz作为 URL。添加新更新中心后,您可以选择可用插件选项卡并添加“Google App Engine”类别中的所有插件,然后选择安装。完成此操作后,您可以使用“服务”选项卡将“App Engine”添加为 Netbeans 环境中的服务器。要添加服务器,请指向 Google App Engine SDK 的基本目录。将 App Engine 服务器添加到 Netbeans 后,它将成为 Web 应用程序的可用部署选项。

创建一个新的 Java Web 项目并将其命名为JythonGAE。对于部署服务器,选择“Google App Engine”,您会注意到,当您的 Web 应用程序创建时,将在WEB-INF目录中创建一个名为appengine-web.xml的附加文件。这是 JythonGAE 应用程序的 Google App Engine 配置文件。我们希望在应用程序中使用的任何 .py 文件都必须在此文件中映射,以便 Google App Engine 不会将它们视为静态文件。默认情况下,Google App Engine 将 WEB-INF 目录之外的所有文件视为静态文件,除非它们是 JSP 文件。我们的应用程序将使用三个 Jython servlet,即NewJythonServlet.pyAddNumbers.pyAddToPage.py。在我们的 appengine-web.xml 文件中,我们可以通过将后缀添加到排除列表中来排除所有 .py 文件被视为静态文件。

appengine-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>jythongae</application>
    <version>1</version>
    <static-files>
        <exclude path="/**.py"/>
    </static-files>
    <resource-files/>
    <ssl-enabled>false</ssl-enabled>
    <sessions-enabled>true</sessions-enabled>
</appengine-web-app>

此时,我们需要在 WEB-INF 项目目录中创建几个额外的目录。我们应该创建一个lib目录并将jython.jarappengine-api-1.0-sdk-1.2.2.jar放入该目录。请注意,App Engine JAR 的名称可能与您使用的版本不同。现在,我们应该有一个类似于以下内容的目录结构

JythonGAE
    WEB-INF
        lib
            jython.jar
            appengine-api-1.0-sdk-1.2.2.jar
        appengine-web.xml
        web.xml
    src
    web

现在我们已经建立了应用程序结构,是时候开始构建实际的逻辑了。在传统的 Jython servlet 应用程序中,我们需要确保 *PyServlet* 类在启动时初始化,并且所有以 *。py* 结尾的文件都被传递给它。正如我们在第 13 章中所见,这是在 *web.xml* 部署描述符中完成的。但是,我发现仅此一项在部署到云时不起作用。我在针对 Google App Engine 开发服务器部署和部署到云时发现了一些不一致之处。出于这个原因,我将向您展示我能够使应用程序在生产和开发 Google App Engine 环境中按预期运行的方式。在第 12 章中,讨论了将 Jython 类强制转换为 Java 的对象工厂模式。如果将相同的模式应用于 Jython servlet 应用程序,那么我们可以使用工厂在运行时将我们的 Jython servlet 强制转换为 Java 字节码。然后,我们将生成的强制转换类映射到应用程序的 *web.xml* 部署描述符中的 servlet 映射。我们还可以部署我们的 Jython applet 并利用 *PyServlet* 映射到 *web.xml* 中的 *。py* 扩展名。我将在源代码中注释两个实现的代码不同之处。

使用 App Engine 的对象工厂

为了使用对象工厂来强制转换我们的代码,我们必须使用对象工厂以及 Java 接口,并且我们再次将使用 PlyJy 项目来实现这一点。请注意,如果您选择不使用对象工厂模式,而是使用 PyServlet,您可以安全地跳到下一小节。第一步是将 *PlyJy.jar* 添加到我们之前创建的 *lib* 目录中,以确保它与我们的应用程序捆绑在一起。PlyJy 项目中包含一个名为 *JythonServletFacade* 的 Java servlet,这个 Java servlet 的作用本质上是使用 *JythonObjectFactory* 类来强制转换一个命名的 Jython servlet,然后调用它生成的 *doGet* 和 *doPost* 方法。项目中还有一个名为 *JythonServletInterface* 的简单 Java 接口,我们的 Jython servlet 必须实现它,才能使强制转换按预期工作。您将在下面看到 PlyJy 项目中包含的这两部分代码。

JythonServletFacade.java

public class JythonServletFacade extends HttpServlet {

    private JythonObjectFactory factory = null;

    String pyServletName = null;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        factory = factory.getInstance();
        pyServletName = getInitParameter("PyServletName");
        JythonServletInterface jythonServlet = (JythonServletInterface) factory.createObject(JythonServletInterface.class, pyServletName);
        jythonServlet.doGet(request, response);
    }
    ...
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        factory = factory.getInstance();
        pyServletName = getInitParameter("PyServletName");
        JythonServletInterface jythonServlet = (JythonServletInterface) factory.createObject(JythonServletInterface.class, pyServletName);
        jythonServlet.doPost(request, response);
    }
    ...
}

JythonServletInterface.java

public interface JythonServletInterface {
    public void doGet(HttpServletRequest request, HttpServletResponse response);
    public void doPost(HttpServletRequest request, HttpServletResponse response);
}

使用 PyServlet 映射

当我们使用 PyServlet 映射实现时,无需使用工厂来强制转换对象。您只需在 *web.xml* 中设置 servlet 映射,并使用您的 Jython servlet 直接使用 URL 中的 .py 扩展名。但是,我在 App Engine 上使用 PyServlet 时遇到了一些问题,因为这种实现将部署到开发 App Engine 服务器环境,但当部署到云时,您在尝试调用 servlet 时会收到错误。正是由于这些不一致之处,我选择为 Jython servlet 到 App Engine 部署实现对象工厂解决方案。

用于 App Engine 的示例 Jython Servlet 应用程序

拼图的下一部分是我们应用程序的代码。在这个例子中,我们将使用一个简单的 servlet 来显示一些文本,以及与第 13 章中使用 JSP 和 Jython 的相同示例。下面的代码设置了三个 Jython servlet。第一个 servlet 只是显示一些输出,接下来的两个执行一些数学逻辑,然后有一个 JSP 来显示数学 servlet 的结果。

NewJythonServlet.py

from javax.servlet.http import HttpServlet
from org.plyjy.interfaces import JythonServletInterface

class NewJythonServlet (JythonServletInterface, HttpServlet):
        def doGet(self,request,response):
                self.doPost (request,response)

        def doPost(self,request,response):
                toClient = response.getWriter()
                response.setContentType ("text/html")
                toClient.println ("<html><head><title>Jython Servlet Test Using Object Factory</title>" +
                                                  "<body><h1>Jython Servlet Test for GAE</h1></body></html>")

        def getServletInfo(self):
            return "Short Description"

AddNumbers.py

import javax
class add_numbers(javax.servlet.http.HttpServlet):
    def doGet(self, request, response):
        self.doPost(request, response)
    def doPost(self, request, response):
        x = request.getParameter("x")
        y = request.getParameter("y")
        if not x or not y:
            sum = "<font color='red'>You must place numbers in each value box</font>"
        else:
            try:
                sum = int(x) + int(y)
            except ValueError, e:
                sum = "<font color='red'>You must place numbers only in each value box</font>"
        request.setAttribute("sum", sum)
        dispatcher = request.getRequestDispatcher("testJython.jsp")
        dispatcher.forward(request, response)

AddToPage.py

import java, javax, sys

class add_to_page(javax.servlet.http.HttpServlet):
    def doGet(self, request, response):
        self.doPost(request, response)

    def doPost(self, request, response):
        addtext = request.getParameter("p")
        if not addtext:
            addtext = ""

        request.setAttribute("page_text", addtext)
        dispatcher = request.getRequestDispatcher("testJython.jsp")
        dispatcher.forward(request, response)

testjython.jsp - 请注意,如果您计划使用对象工厂技术,此实现会有所不同。您将使用 servlet 而不是使用 *add_to_page.py* 和 *add_numbers.py* 作为您的操作,即 * /add_to_page* 和 * /add_numbers*。

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Jython JSP Test</title>
    </head>
    <body>
        <form method="GET" action="add_to_page.py">
            <input type="text" name="p">
            <input type="submit">
        </form>
        <% Object page_text = request.getAttribute("page_text");
           Object sum = request.getAttribute("sum");
           if(page_text == null){
               page_text = "";
           }
           if(sum == null){
               sum = "";
           }
        %>
        <br/>
            <p><%= page_text %></p>
        <br/>
        <form method="GET" action="add_numbers.py">
            <input type="text" name="x">
            +
            <input type="text" name="y">
            =
            <%= sum %>
            <br/>
            <input type="submit" title="Add Numbers">
        </form>

    </body>
</html>

如前所述,所有 Jython servlet 都必须位于您的类路径中的某个位置。如果使用 Netbeans,您可以将 servlet 放置到项目的源根目录中(不在包内),或者您可以将它们放在包含 JSP 文件的 web 文件夹中。如果执行后者,我发现您可能需要稍微调整一下您的 CLASSPATH,方法是在项目属性中将 web 文件夹添加到您的库列表中。接下来,我们需要确保部署描述符包含应用程序所需的 servlet 定义和映射。现在,如果您使用的是对象工厂实现和 *JythonServletFacade* servlet,您会注意到有一个名为 *PyServletName* 的变量,JythonObjectFactory 使用它作为我们 Jython servlet 的名称。那么,在 *web.xml* 中,我们必须使用 *PyServletName* 作为 *<param-name>* 和我们的 Jython servlet 的名称作为 *<param-value>* 传递一个 *<init-param>*。这将基本上将 Jython servlet 的名称传递给 *JythonServletFacade* servlet,以便对象工厂可以使用它。

web.xml

<web-app>
    <display-name>Jython Google App Engine</display-name>

    <!-- Used for the PyServlet Implementation -->
    <servlet>
        <servlet-name>PyServlet</servlet-name>
        <servlet-class>org.python.util.PyServlet</servlet-class>
    </servlet>

    <!-- The next three servlets are used for the object factory implementation only.
         They can be excluded in the PyServlet implementation -->
    <servlet>
        <servlet-name>NewJythonServlet</servlet-name>
        <servlet-class>org.plyjy.servlets.JythonServletFacade</servlet-class>
        <init-param>
            <param-name>PyServletName</param-name>
            <param-value>NewJythonServlet</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>AddNumbers</servlet-name>
        <servlet-class>org.plyjy.servlets.JythonServletFacade</servlet-class>
        <init-param>
            <param-name>PyServletName</param-name>
            <param-value>AddNumbers</param-value>
        </init-param>
    </servlet>
    <servlet>
        <servlet-name>AddToPage</servlet-name>
        <servlet-class>org.plyjy.servlets.JythonServletFacade</servlet-class>
        <init-param>
            <param-name>PyServletName</param-name>
            <param-value>AddToPage</param-value>
        </init-param>
    </servlet>

    <!-- The following mapping should be used for the PyServlet implementation -->
    <servlet-mapping>
        <servlet-name>PyServlet</servlet-name>
        <url-pattern>*.py</url-pattern>
    </servlet-mapping>

    <!-- The following three mappings are used in the object factory implementation -->

    <servlet-mapping>
        <servlet-name>NewJythonServlet</servlet-name>
        <url-pattern>/NewJythonServlet</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>AddNumbers</servlet-name>
        <url-pattern>/AddNumbers</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>AddToPage</servlet-name>
        <url-pattern>/AddToPage</url-pattern>
    </servlet-mapping>
</web-app>

请注意,当使用 PyServlet 实现时,您应该排除上面web.xml中用于对象工厂实现的部分。PyServlet 映射可以在两种实现的web.xml中包含,不会出现问题。就是这样,现在您可以将应用程序部署到您的 Google App Engine 开发环境,它应该可以正常运行。您也可以选择部署到另一个 Web 服务器以测试兼容性。您可以通过右键单击应用程序并选择“部署到 App Engine”选项直接部署到云。

使用 Eclipse

如果您希望使用 Eclipse IDE 进行开发,您应该下载本章前面提供的链接中的 Google App Engine 插件。您还应该使用 PyDev 插件,该插件可在 http://pydev.sourceforge.net/ 获取。在本节中,我使用了 Eclipse Galileo 并创建了一个名为“JythonGAE”的新项目,作为 Google Web 应用程序。创建项目时,请确保选中使用 Google App Engine 的复选框,并取消选中 Google Web Toolkit 选项。您会发现 Eclipse 为您的应用程序创建了一个目录结构,与 Google App Engine SDK 中包含的项目模板非常相似。

如果您按照上一节中的代码示例进行操作,您可以创建相同的代码并以相同的方式设置web.xmlappengine-web.xml。关键是确保您在WEB-INF中创建一个lib目录,并将文件放在适当的位置。您需要确保您的 Jython servlet 包含在您的 CLASSPATH 中,方法是将它们添加到项目的源根目录,或者进入项目属性并将war目录添加到您的Java Build Path。这样做时,请确保您包含WEB-INF目录,否则您将收到错误。

当您准备好部署应用程序时,您可以选择使用 Google App Engine 开发环境或部署到云。您可以通过右键单击项目并选择Run As选项,然后选择 Google Web 应用程序选项来运行应用程序。第一次运行应用程序时,您可能需要设置运行时。如果您准备好部署到云,您可以右键单击项目并选择Google -> Deploy to App Engine选项。输入您的 Google 用户名和密码后,您的应用程序将被部署。

将 Modjy 部署到 GAE

我们也可以使用 Jython 的 modjy API 轻松部署 WSGI 应用程序。为此,您需要将 Jython Lib 目录的存档添加到您的 WEB-INF 项目目录。根据 modjy 网站,您需要获取 Jython 的源代码,然后压缩Lib目录并将其与一个将充当指向压缩存档的指针的文件一起放置在另一个目录中。modjy 网站将目录命名为python-lib,并将指针文件命名为all.pth。此指针文件可以命名为任何名称,只要后缀为.pth即可。在指针文件中,您需要明确命名您为Lib目录内容创建的压缩存档。假设您将其命名为 lib.zip,在这种情况下,我们将把文本“lib.zip”(不带引号)放入.pth文件中。现在,如果我们将 modjy demo_app.py 演示应用程序添加到项目中,那么我们的目录结构应该如下所示

modjy_app
    demo_app.py
    WEB-INF
        lib
            jython.jar
            appengine-api-1.0-sdk-1.2.2.jar
        python-lib
            lib.zip
            all.pth

现在,如果我们使用 Tomcat 运行应用程序,它应该按预期运行。同样,我们可以使用 Google App Engine SDK Web 服务器运行它,它应该提供预期结果。

总结

Google App Engine 对于 Jython 来说无疑是一个重要的部署目标。Google 为较小的应用程序提供免费托管,并且他们还根据带宽对帐户定价。毫无疑问,这是一个建立小型网站的好方法,并且可能在以后进行扩展。最重要的是,您可以通过像本章中所示的示例一样设置您的 App Engine 应用程序,将 Django、Pylons 和其他应用程序通过 Jython 部署到 App Engine。

Java Store

在本书撰写之时,另一个备受关注的部署目标是 Java Store 或 Java Warehouse。这是 Sun Microsystems 推出的一个新概念,旨在帮助 Java 软件开发人员通过一个在线的 Web Start 应用程序商店来推广他们的应用程序。与其他应用程序平台类似,Java Store 是一个应用程序商店,用户可以在其中搜索开发人员提交的应用程序。Java Warehouse 是存储在 Java Store 中的应用程序的存储库。这对 Java 和 Jython 开发人员来说都是一个很有前景的目标。它应该像生成包含 Jython 应用程序的 JAR 文件并将其部署到 Java Store 一样简单。不幸的是,由于该程序目前仍处于 alpha 阶段,我无法提供有关通过 Java Store 分发 Jython 应用程序的任何具体细节。但是,未来计划是使替代 VM 语言应用程序能够轻松部署到 Java Warehouse。目前,当然可以将 Jython 应用程序部署到仓库,但它只能作为 Java 应用程序部署。截至本书撰写之时,只有 Java 和 JavaFX 应用程序可以直接部署到 Java Warehouse。请注意,由于该产品仍处于 alpha 阶段,本书不会讨论该程序的某些方面,例如在 Java Store 上托管应用程序可能产生的会员费或费用。

将应用程序发布到仓库的要求如下:

  • 将您的应用程序打包到单个 JAR 文件中。
  • 描述性文本,用于记录您的应用程序。
  • 用于图标的图形图像文件,以及让消费者了解您的应用程序外观的图像文件。

在第 13 章中,我们介绍了将 Jython GUI 应用程序打包和分发到 JAR 文件中。当 Jython 应用程序打包到 JAR 文件中时,可以使用 Java Web Start 通过网络托管应用程序。另一方面,如果希望将 Jython GUI 应用程序提供给用户购买或免费使用,Java Store 将是另一种方法。将应用程序部署到单个 JAR 文件中的一种可能方法是使用第 13 章中讨论的方法,但也有其他解决方案。例如,可以使用 One-Jar 产品创建一个包含所有必要 Jython 代码以及应用程序所需的其他 JAR 文件的单个 JAR 文件。在下一节中,我们将讨论使用 One-JAR 部署 Jython 应用程序,以便您可以看到与使用 Jython 独立 JAR 技术的一些相似之处和不同之处。

部署单个 JAR

为了将应用程序部署到 Java Warehouse,它必须打包为单个 JAR 文件。我们已经在第 13 章中讨论了使用 Jython 独立方法将 Jython 应用程序打包到 JAR 文件中。在本节中,您将学习如何使用 One-JAR (http://one-jar.sourceforge.net/) 产品来分发基于客户端的 Jython 应用程序。要开始使用,您需要获取 One-JAR 的副本。下载网站上提供了一些选项,但出于我们的目的,我们将使用 One-JAR 的源文件来打包应用程序。下载后,项目的源代码应如下所示。

src
    com
        simontuffs
            onejar
                Boot.java
                Handler.java
                IProperties.java
                JarClassLoader.java

One-Jar 项目的源代码必须驻留在我们将构建的 JAR 文件中。接下来,我们需要为我们的 Jython 源代码和 Java 源代码创建单独的源代码目录。同样,我们将为 One-Jar 源代码创建一个单独的源代码目录。最后,我们将创建一个 lib 目录,我们将把应用程序所需的所有 JAR 文件放在其中。为了运行 Jython 应用程序,我们需要将 Jython 项目源代码打包到应用程序的 JAR 文件中。我们不需要使用整个 jython.jar,而只需要它的独立版本。获取独立 Jython JAR 的最简单方法是运行安装程序并选择独立选项。完成此操作后,只需将生成的 jython.jar 添加到应用程序的 lib 目录中。最后,目录结构应类似于以下内容。

one-jar-jython-example
    java
    lib
        jython.jar
    LICENSE.txt
    onejar
        src
            com
            one-jar-license.txt
                simontuffs
                    onejar
                        Boot.java
                        Handler.java
                        IProperties.java
                        JarClassLoader.java
    src

如您从上面的文件结构图中所见,src 目录将包含我们的 Jython 源文件。此示例中包含的 LICENSE.txt 由 Ryan McGuire (http://www.enigmacurry.com) 编写。他在他的博客中详细解释了如何使用 One-Jar,我在此示例中复制了他的部分工作……包括我们将要构建应用程序的 build.xml 版本。让我们看一下我们将用来构建应用程序 JAR 的构建文件。在此示例中,我使用 Apache Ant 作为构建系统,但如果您愿意,可以选择其他系统。

build.xml

<project name="JythonSwingApp" default="dist" basedir=".">

  <!-- #################################################
       These two properties are the only ones you are
       likely to want to change for your own projects:     -->
  <property name="jar.name" value="JythonSwingApp.jar" />
  <property name="java-main-class" value="Main" />
  <!-- ##################################################  -->

  <!-- Below here you dont' need to change for simple projects -->
  <property name="src.dir" location="src"/>
  <property name="java.dir" location="java"/>
  <property name="onejar.dir" location="onejar"/>
  <property name="java-build.dir" location="java-build"/>
  <property name="build.dir" location="build"/>
  <property name="lib.dir" location="lib"/>

  <path id="classpath">
    <fileset dir="${lib.dir}" includes="**/*.jar"/>
  </path>

  <target name="clean">
    <delete dir="${java-build.dir}"/>
    <delete dir="${build.dir}"/>
    <delete file="${jar.name}"/>
  </target>

  <target name="dist" depends="clean">
    <!-- prepare the build directory -->
    <mkdir dir="${build.dir}/lib"/>
    <mkdir dir="${build.dir}/main"/>
    <!-- Build java code -->
    <mkdir dir="${java-build.dir}"/>
    <javac srcdir="${java.dir}" destdir="${java-build.dir}" classpathref="classpath"/>
    <!-- Build main.jar -->
    <jar destfile="${build.dir}/main/main.jar" basedir="${java-build.dir}">
      <manifest>
            <attribute name="Main-Class" value="Main" />
      </manifest>
    </jar>
    <delete file="${java-build.dir}"/>
    <!-- Add the python source -->
    <copy todir="${build.dir}">
      <fileset dir="${src.dir}"/>
    </copy>
    <!-- Add the libs -->
    <copy todir="${build.dir}/lib">
      <fileset dir="${lib.dir}"/>
    </copy>
    <!-- Compile OneJar -->
    <javac srcdir="${onejar.dir}" destdir="${build.dir}" classpathref="classpath"/>
    <!-- Copy the OneJar license file -->
    <copy file="${onejar.dir}/one-jar-license.txt" tofile="${build.dir}/one-jar-license.txt" />
    <!-- Build the jar -->
    <jar destfile="${jar.name}" basedir="${build.dir}">
      <manifest>
            <attribute name="Main-Class" value="com.simontuffs.onejar.Boot" />
            <attribute name="Class-Path" value="lib/jython-full.jar" />
      </manifest>
    </jar>
    <!-- clean up -->
    <delete dir="${java-build.dir}" />
    <delete dir="${build.dir}" />
  </target>

</project>

由于这是一个 Jython 应用程序,我们可以使用尽可能多的 Java 源代码。在此示例中,我们将只使用一个 Java 源文件 Main.java 来“驱动”我们的应用程序。在这种情况下,我们将使用 Main.java 中的 PythonInterpreter 来调用我们简单的 Jython Swing 应用程序。现在让我们看一下 Main.java 源代码。

Main.java

import org.python.core.PyException;
import org.python.util.PythonInterpreter;

public class Main {
    public static void main(String[] args) throws PyException{
        PythonInterpreter intrp = new PythonInterpreter();
        intrp.exec("import JythonSimpleSwing as jy");
        intrp.exec("jy.JythonSimpleSwing().start()");
    }
}

现在我们已经编写了驱动程序类,我们将把它放到我们的 java 源代码目录中。如前所述,我们将把我们的 Jython 代码放到 src 目录中。在此示例中,我们使用的是我在第 13 章中编写的同一个简单的 Jython swing 应用程序。

JythonSimpleSwing.py

import sys
sys.packageManager.makeJavaPackage("javax.swing", "java.awt", None)
import javax.swing as swing
import java.awt as awt

class JythonSimpleSwing(object):
    def __init__(self):
        self.frame=swing.JFrame(title="My Frame", size=(300,300))
        self.frame.defaultCloseOperation=swing.JFrame.EXIT_ON_CLOSE;
        self.frame.layout=awt.BorderLayout()
        self.panel1=swing.JPanel(awt.BorderLayout())
        self.panel2=swing.JPanel(awt.GridLayout(4,1))
        self.panel2.preferredSize = awt.Dimension(10,100)
        self.panel3=swing.JPanel(awt.BorderLayout())

        self.title=swing.JLabel("Text Rendering")
        self.button1=swing.JButton("Print Text", actionPerformed=self.printMessage)
        self.button2=swing.JButton("Clear Text", actionPerformed=self.clearMessage)
        self.textField=swing.JTextField(30)
        self.outputText=swing.JTextArea(4,15)


        self.panel1.add(self.title)
        self.panel2.add(self.textField)
        self.panel2.add(self.button1)
        self.panel2.add(self.button2)
        self.panel3.add(self.outputText)

        self.frame.contentPane.add(self.panel1, awt.BorderLayout.PAGE_START)
        self.frame.contentPane.add(self.panel2, awt.BorderLayout.CENTER)
        self.frame.contentPane.add(self.panel3, awt.BorderLayout.PAGE_END)

    def start(self):
        self.frame.visible=1

    def printMessage(self,event):
        print "Print Text!"
        self.text = self.textField.getText()
        self.outputText.append(self.text)

    def clearMessage(self, event):
        self.outputText.text = ""

为了导入 swing 和 awt 包,我们需要使用 sys.packagemanager.makeJavaPackage 实用程序。出于某种原因,该应用程序在不使用此实用程序的情况下可以独立运行,但当使用此方法将其放入 JAR 中时,我们需要协助加载包。此时,该应用程序已准备好使用 Ant 构建。要运行构建,只需进入包含 build.xml 的目录并启动 ant 命令。可以使用以下语法运行生成的 JAR

java -jar JythonSwingApp.jar

在某些情况下,例如通过 Web Start 部署,此 JAR 文件还需要签名。网上有很多资源解释了 JAR 文件的签名,本文不会介绍该主题。现在,该 JAR 已准备好部署并在其他机器上使用。此方法将是通过 Java Store 分发应用程序的良好方法。

移动

移动应用程序是未来的趋势。目前,使用 Jython 开发移动应用程序有几种不同的选择。使用 Jython 开发移动应用程序的一种方法是利用 Jython 中的 JavaFX API。由于 JavaFX 背后的所有内容都是 Java,因此使用 Jython 代码利用 JavaFX API 相当简单。但是,在我看来,这种技术对于生产质量的结果来说并不理想,原因有二。首先,JavaFX 脚本语言使 GUI 开发非常容易。虽然这是可能的(有关更多详细信息,请参见 http://wiki.python.org/jython/JythonMonthly/Articles/December2007/2),但使用 Jython 翻译 JavaFX API 并不像使用 JavaFX 脚本语言那样容易。第二,在撰写本文时,JavaFX 并非在所有移动设备上都可用。它现在才刚刚开始进入移动世界,需要一段时间才能适应。

使用 Jython 开发移动应用程序的另一种方法是利用 Google 提供的 Android 操作系统。Android 如今已在移动设备上得到积极使用,并且其使用量还在不断增长。虽然还处于早期阶段,但有一个名为 Jythonroid 的项目,它是针对 Android Dalvik 虚拟机的 Jython 实现。不幸的是,在撰写本文时,它并没有处于积极开发阶段,尽管它确实存在使该项目步入正轨的可能性。

如果您有兴趣使用 Jython 进行移动开发,请密切关注本节中讨论的两种技术。它们是 Jython 在移动世界中的主要部署目标。至于 Jythonroid 项目,它是开源的,开发人员可以访问它。感兴趣的各方可以重新开始开发它,使其功能齐全,并使其与最新的 Android SDK 保持同步。

结论

部署 Jython 应用非常类似于 Java 应用部署。对于熟悉 Java 应用服务器的用户来说,部署 Jython 应用应该轻而易举。相反,对于不熟悉 Java 应用部署的用户来说,这个主题可能需要一些时间来适应。最终,使用现有的任何 Java 应用服务器,部署 Jython Web 或客户端应用都很容易。

使用 WAR 文件格式,部署 Jython Web 应用普遍都很容易。只要 *jython.jar* 位于应用服务器类路径中或与 Web 应用打包在一起,Jython servlet 应该能够正常运行。我们还了解到,可以通过 Java Web Start 技术部署包含 Jython GUI 应用的 JAR 文件。使用 JNLP 部署文件非常容易,通过 JAR 文件部署 Jython 的技巧在于正确设置文件。完成后,可以使用 HTML 页面引用 JNLP 并启动将 JAR 下载到客户端机器的操作。

最后,本节讨论了使用 Google App Engine 部署 Jython servlet 应用。虽然 Google App Engine 环境在撰写本文时还比较新,但对于任何 Python 或 Java 应用开发者来说,它都是一个极好的部署目标。使用对象工厂技术的一些技巧,可以部署 Jython servlet 并直接使用它们,或者通过 App Engine 上的 JSP 文件使用它们。敬请关注未来几个月和几年内将为 Jython 提供的更多部署目标。随着云计算和移动设备越来越流行,部署目标的数量将继续增长,Jython 将在每个目标中变得更加有用。