第 13 章:简单 Web 应用程序

使用 Jython 的主要优势之一是能够利用 Java 平台功能,使用 Python 编程语言而不是 Java 进行编程。在当今的 Java 世界中,最广泛使用的 Web 开发技术是 Java Servlet。现在,在 JavaEE 中,存在一些技术和框架,使我们能够编写 HTML 或其他标记语言,而不是编写纯 Java Servlet。但是,有时编写纯 Java Servlet 仍然有其优势。我们可以使用 Jython 编写 Servlet,这比 Java 提供的优势更多,因为现在我们也可以利用 Python 语言功能。类似地,我们可以使用 Jython 而不是纯 Java 编写 Web Start 应用程序,从而使我们的生活更轻松。事实证明,使用纯 Java 编写这些应用程序有时是一项艰巨的任务。我们可以使用 Jython 中的一些可用技术来简化我们的工作。我们甚至可以使用 Jython 编写 WSGI 应用程序,利用 Jython 项目中的 modjy 集成。

在本章中,我们将介绍使用 Jython Servlet、Web Start 和 WSGI 编写简单 Web 应用程序的这些技术。我们将深入探讨使用每种不同技术的细节,但不会讨论此类解决方案的部署,因为这将在第 18 章:部署目标中介绍。

Servlet

在 Jython 中编写 Servlet 是一种非常高效且简便的方法,可以在 Web 应用程序中使用 Jython。毫无疑问,Java Servlet 很少再使用纯 Java 编写。大多数 Java 开发人员使用 JavaServer Pages (JSP)、JavaServer Faces (JSF) 或其他一些框架,以便他们可以使用标记语言来处理 Web 内容,而不是 Java 代码。但是,在某些情况下,使用纯 Java Servlet 仍然非常有用。对于这些情况,我们可以通过使用 Jython 来简化我们的工作。JSP 也有一些很好的用例,类似地,我们可以使用 Jython 来实现 JSP 代码中的逻辑。后一种技术使我们能够将模型-视图-控制器 (MVC) 范式应用于我们的编程模型,在该模型中,我们将前端标记与任何实现逻辑分开。无论您选择使用哪种技术,实现起来都相当容易,您甚至可以将此功能添加到任何现有的 Java Web 应用程序中,而不会有任何问题。

Jython Servlet 使用提供的另一个功能是动态测试。由于 Jython 在运行时编译,因此我们可以即时更改代码,而无需重新编译和重新部署 Web 应用程序。这可以使测试 Web 应用程序变得非常容易,因为通常 Web 应用程序开发中最痛苦的部分是在部署到 Servlet 容器和测试之间等待的时间。

为 Jython Servlet 配置您的 Web 应用程序

在任何 Web 应用程序中,要使其与 Jython servlet 兼容,只需要做很少的事情。Jython 包含一个名为PyServlet的内置类,它可以方便地使用 Jython 源文件创建 Java servlet。我们可以通过在应用程序的 web.xml 描述符中添加必要的 XML 配置,轻松地在应用程序中使用 PyServlet,这样PyServlet类将在运行时加载,并且任何包含.py后缀的文件都将传递给它。一旦将此配置添加到 Web 应用程序中,并将jython.jar添加到 CLASSPATH 中,那么 Web 应用程序就可以使用 Jython servlet 了。

<servlet>
    <servlet-name>PyServlet</servlet-name>
    <servlet-class>org.python.util.PyServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>PyServlet</servlet-name>
    <url-pattern>*.py</url-pattern>
</servlet-mapping>

任何要由 Java servlet 容器使用的 servlet 也需要添加到web.xml文件中,这允许通过 URL 正确映射 servlet。为了本书的目的,我们将在下一节中编写一个名为NewJythonServlet的 servlet,因此需要将以下 XML 配置添加到 web.xml 文件中。

<servlet>
    <servlet-name>NewJythonServlet</servlet-name>
    <servlet-class>NewJythonServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>NewJythonServlet</servlet-name>
    <url-pattern>/NewJythonServlet</url-pattern>
</servlet-mapping>

编写一个简单的 Servlet

为了编写 servlet,我们必须在 CLASSPATH 中拥有javax.servlet.http.HttpServlet抽象 Java 类,以便我们的 Jython servlet 可以扩展它来帮助简化代码。这个抽象类,以及其他 servlet 实现类,是servlet-api.jar文件的一部分。根据抽象类,我们应该在任何 Java servlet 中重写两个方法,分别是doGetdoPost。前者执行 HTTP GET 操作,而后者执行 servlet 的 HTTP POST 操作。其他常用的重写方法包括doPutdoDeletegetServletInfo。第一个执行 HTTP PUT 操作,第二个执行 HTTP DELETE 操作,最后一个提供 servlet 的描述。在下面的示例中,以及在大多数用例中,只使用doGetdoPost

让我们首先展示一个非常简单的 Java servlet 的代码。这个 servlet 除了将它的名称及其在 Web 应用程序中的位置打印到屏幕上之外,没有其他功能。在该代码之后,我们将看一下用 Jython 编写的相同 servlet,以便进行比较。

NewJavaServlet.java

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class NewJavaServlet extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {

            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet NewJavaServlet Test</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Servlet NewJavaServlet at " + request.getContextPath () + "</h1>");
            out.println("</body>");
            out.println("</html>");

        } finally {
            out.close();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }

}

为了使代码更短一些,代码中的所有注释都被删除了。现在,用 Jython 编写的等效 servlet 代码。

from javax.servlet.http import HttpServlet

class NewJythonServlet (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</title>" +
                         "<body><h1>Servlet Jython Servlet at" +
                         request.getContextPath() + "</h1></body></html>")

   def getServletInfo(self):
       return "Short Description"

简洁的代码不仅是一个吸引人的特性,而且还为使用动态 servlet 的开发周期提供了便利。如前所述,由于 Jython 提供了运行时编译,因此每次更改时都不需要重新部署。只需更改 Jython servlet,保存并重新加载网页即可查看更新。如果你开始思考可能性,你会意识到上面的代码只是一个基本示例,你可以在 Jython servlet 中做任何用 Java 可以做的事情,甚至可以做大多数用 Python 语言可以做的事情。

为了总结 Jython servlet 的使用,你只需将jython.jarservlet-api.jar包含在 CLASSPATH 中。将必要的 XML 添加到 web.xml 中,最后通过扩展 javax.servlet.http.HttpServlet 抽象类来编写 servlet。

使用 JSP 与 Jython

利用 Jython servlet 可以使开发周期更加高效,但在某些情况下,Jython 代码可能不是处理面向 Web 代码的最方便的方式。有时,使用 HTML 等标记语言更适合开发复杂的前端。例如,在 Jython servlet 中包含 javascript 代码非常容易。但是,所有 javascript 代码都将写在 String 的上下文中。这不仅消除了 IDE 在语义代码着色和自动完成等情况下的有用性,而且还使代码更难阅读和理解。将此类代码从 Jython 或 Java 中干净地分离出来,可以使代码更易于阅读,并更容易维护。使用 JSP 允许将 Java 代码集成到 HTML 标记中,以生成动态页面内容。我不喜欢 JSP。我说出来了,如果 JSP 技术没有被正确使用,它会使代码成为一场噩梦。虽然 JSP 可以很容易地将 javascript、HTML 和 Java 混合到一个文件中,但它会使维护变得非常困难。将 Java 代码与 HTML 或 Javascript 混合是一个坏主意。将 Jython 和 HTML 或 Javascript 混合在一起也是如此。如果使用得当,JSP 是一种非常智能且高效的技术,但如果使用不当,它可能是程序员的噩梦。

模型-视图-控制器 (MVC) 范式允许在逻辑代码(如 Java 或 Jython)和标记代码(如 HTML)之间进行干净的分离。Javascript 在这里是一个两难问题,但它总是与 HTML 分组在一起,因为它是一种客户端脚本语言。换句话说,Javascript 代码也应该与逻辑代码分离。在考虑 MVC 时,控制器代码将是用于从最终用户捕获数据的标记和 javascript 代码。模型代码将是操作数据的业务逻辑。模型代码包含在我们的 Jython 或 Java 中。视图将是显示结果的标记和 Javascript。

使用 MVC 模式结合 JSP 和 Jython servlet 可以成功实现干净的分离。在本节中,我们将通过一个简单的示例来了解如何做到这一点。与本书中的许多其他示例一样,它只会触及可用的大量功能的表面。一旦您学会了如何使用 JSP 和 Jython servlet,您就可以进一步探索这项技术。

配置 JSP

除了配置 Web 应用程序以使用 Jython servlet 之外,没有其他真正的配置。将必要的 XML 添加到 web.xml 部署描述符中,将正确的 JAR 包含在您的应用程序中,然后开始编码。需要注意的是,将用于 Jython servlet 的 .py 文件必须位于您的 CLASSPATH 中。Jython servlet 通常与 JSP 网页本身位于同一个目录中。这可以使事情变得更容易,但它也可能不受欢迎,因为这个概念没有利用包来组织代码。为了简单起见,我们将 servlet 代码放在与 JSP 相同的目录中,但如果您愿意,也可以选择不同的方式。

编码控制器/视图

应用程序的控制器和视图部分将使用标记和 JavaScript 代码进行编码。显然,这种技术利用 JSP 来包含标记,而 JavaScript 可以直接嵌入到 JSP 中,也可以根据需要驻留在单独的 .js 文件中。后者是首选方法,以便使事情变得干净,但一些 Web 应用程序在页面本身中嵌入少量 JavaScript。

本示例中的 JSP 非常简单,示例中没有 JavaScript,它只包含几个输入文本区域。此 JSP 将包含两个表单,因为页面上将有两个单独的提交按钮。每个表单都将重定向到不同的 Jython servlet,该 servlet 将对输入文本中提供的數據进行处理。在我们的示例中,第一个表单包含一个小的文本框,用户可以在其中键入任何文本,该文本将在相应的提交按钮被按下后重新显示在页面上。很酷,对吧?其实不是,但它对于学习 JSP 和 servlet 实现之间的关联很有价值。第二个表单包含两个文本框,用户将在其中放置数字,点击此表单中的提交按钮将导致数字被传递到另一个 servlet,该 servlet 将计算并返回两个数字的总和。以下是此简单 JSP 的代码。

testJSP.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>

<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>
        <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>

在上面的 JSP 中,您可以看到第一个表单重定向到名为 add_to_page.py 的 Jython servlet。在这种情况下,包含在名为 p 的输入文本框中的文本将被传递到 servlet,并在页面上重新显示。要重新显示的文本将存储在名为 page_text 的属性中,您可以看到它在 JSP 页面中使用 ${} 符号进行引用。以下是 add_to_page.py 的代码。

#######################################################################
#  add_to_page.py
#
#  Simple servlet that takes some text from a web page and redisplays
#  it.
#######################################################################

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)

快速简单,servlet 接收请求并获取包含在参数 p 中的值。然后它将该值分配给名为 addtext 的变量。然后将此变量分配给请求中的名为 page_text 的属性,并转发回 testJython.jsp 页面。该代码也可以轻松地转发到不同的 JSP,这就是我们创建更深入的应用程序的方式。

我们 JSP 中的第二个表单接受两个值并返回结果总和到页面。如果有人在文本框中输入文本而不是数值,则会显示错误消息来代替总和。虽然非常简单,但此 servlet 演示了任何业务逻辑都可以编码在 servlet 中,包括数据库调用等。

#######################################################################
#  add_numbers.py
#
#  Calculates the sum for two numbers and returns it.
#######################################################################

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)

如果您将 JSP 和 servlet 添加到您在上一节 Jython Servlet 中创建的 Web 应用程序中,那么此示例应该可以开箱即用。

Applet 和 Java Web Start

在撰写本文时,Jython 2.5.0 中的 applet 尚未成为可用的选项。这是因为 applet 必须进行静态编译,并可以使用 <applet><object> 标签嵌入到网页中。名为 jythonc 的静态编译器已在 Jython 2.5.0 中删除,以便为更好的技术让路。Jythonc 非常适合执行某些任务,例如 Jython applet 的静态编译,但它在开发周期中造成了脱节,因为它是一个单独的编译步骤,对于执行简单任务(如 Jython 和 Java 集成)来说是不必要的。在 Jython 的未来版本中,即 2.5.1 或不久的将来发布的另一个版本中,将包含一种更好的方法来执行 applet 的静态编译。

目前,要开发 Jython 小程序,您需要使用包含 *jythonc* 的先前发行版,然后使用 *<applet>* 或 *<object>* 标签将它们与网页关联。在 Jython 中,小程序的编码方式与标准 Java 小程序非常相似。但是,由于 Jython 的语法复杂,生成的代码行数明显更少。与开发几乎相同的 Java Swing 应用程序相比,Jython 的 GUI 开发总体上极大地提高了生产力。这就是为什么在 Jython 中编码小程序是一个可行的解决方案,并且不应该被忽视的原因。

在 Web 上分发基于 GUI 的应用程序的另一种选择是利用 Java Web Start 技术。创建 Web Start 应用程序的唯一缺点是它不能直接嵌入到任何网页中。Web Start 应用程序将内容下载到客户端的桌面,然后在客户端的本地 JVM 上运行。Java Web Start 应用程序的开发与独立桌面应用程序的开发没有什么不同。用户界面可以使用 Jython 和 Java Swing API 进行编码,就像小程序用户界面的编码一样。准备好部署 Web Start 应用程序后,您需要创建一个用于部署的 Java 网络启动协议 (JNLP) 文件,并将其与应用程序捆绑在一起。完成此操作后,您需要将捆绑包复制到 Web 服务器并创建一个可用于启动应用程序的网页。

在本节中,我们将开发一个小型 Web Start 应用程序,以演示如何使用对象工厂设计模式以及使用纯 Jython 以及独立的 Jython JAR 文件进行分发来完成此操作。请注意,可能还有其他方法可以实现相同的结果,而这些只是此类应用程序的两种可能的实现方式。

编码简单的基于 GUI 的 Web 应用程序

我们将在此演示中开发的 Web Start 应用程序非常简单,但最终可以像您想要的那样高级。本节的目的是不是向您展示如何开发基于 Web 的 GUI 应用程序,而是开发此类应用程序的过程。您实际上可以将 GUI 章节中讨论的任何基于 Swing 的应用程序轻松地使用 Web Start 技术进行部署。如上一节所述,有许多不同的方法可以部署 Jython Web Start 应用程序。我个人更喜欢使用对象工厂设计模式来创建简单的 Jython Swing 应用程序。但是,它也可以使用所有 .py 文件完成,然后使用 Jython 独立 JAR 文件进行分发。我们将在本节中讨论每种技术。我经常发现,如果您混合使用 Java 和 Jython 代码,那么对象工厂模式最有效。如果您开发的是严格的 Jython 应用程序,那么 JAR 方法可能最适合您。

对象工厂应用程序设计

我们将在此部分中开发的应用程序是一个简单的 GUI,它接受一行文本并在 JTextArea 中重新显示它。我使用 Netbeans 6.7 开发了该应用程序,因此本节中的一些内容可能会引用该 IDE 中提供的特定功能。要开始创建对象工厂 Web Start 应用程序,我们首先需要创建一个项目。我在 Netbeans 中创建了一个名为 *JythonSwingApp* 的新 Java 应用程序,然后将 *jython.jar* 和 *plyjy.jar* 添加到类路径中。

首先,创建 *Main.java* 类,它实际上将是应用程序的驱动程序。*Main.java* 的目标是使用 Jython 对象工厂模式将基于 Jython 的 Swing 应用程序强制转换为 Java。此类将是应用程序的起点,然后 Jython 代码将在幕后执行所有工作。使用此模式,我们还需要一个可以通过 Jython 代码实现的 Java 接口,因此此示例还使用一个非常简单的接口,该接口定义了一个 *start()* 方法,该方法将用于使我们的 GUI 可见。最后,名为 Jython 类下面是我们的 *Main.java* 驱动程序和 Java 接口的代码。在我们的示例中,我们将此类命名为 *MainOF.java*,以将其与使用 *PythonInterpreter* 的下一个示例区分开来。此应用程序的目录结构如下所示。

JythonSwingApp
JythonSimpleSwing.py
jythonswingapp
MainOF.java
jythonswingapp.interfaces
JySwingType.java
*MainOF.java*


package jythonswingapp;

import jythonswingapp.interfaces.JySwingType;
import org.plyjy.factory.JythonObjectFactory;


public class Main {

    JythonObjectFactory factory;

    public static void invokeJython(){

        JySwingType jySwing = (JySwingType) JythonObjectFactory
                .createObject(JySwingType.class, "JythonSimpleSwing");
        jySwing.start();
    }

    public static void main(String[] args) {
        invokeJython();
    }

}

如您所见,*MainOF.java* 除了强制执行 Jython 模块并调用 *start()* 方法之外,没有做太多其他事情。接下来,您将看到 *JySwingType.java* 接口以及显然用 Jython 编写的实现类。

*JySwingType.java*
package jythonswingapp.interfaces;

public interface JySwingType {
    public void start();
}


*JythonSimpleSwing.py*
import javax.swing as swing
import java.awt as awt
from jythonswingapp.interfaces import JySwingType
import add_player as add_player
import Player as Player

class JythonSimpleSwing(JySwingType, 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 = ""

如果您使用的是 Netbeans,那么当您清理和构建项目时,会自动为您生成一个 JAR 文件。但是,您可以通过确保 *JythonSimpleSwing.py* 模块驻留在您的类路径中并使用 *java -jar* 选项,轻松地在命令行或终端中创建 JAR 文件。使用 Netbeans 等 IDE 的另一个好处是,您可以通过进入项目属性并选中几个复选框,将其变成 Web Start 应用程序。具体来说,如果您进入项目属性并从左侧菜单中选择 *应用程序 - Web Start*,然后选中 *启用 Web Start* 选项,那么 IDE 将负责生成使此操作成为可能的必要文件。Netbeans 还具有自签名 JAR 文件的选项,这是在另一台机器上通过 Web Start 运行大多数应用程序所必需的。继续尝试一下,只需确保在进行更改后再次清理和构建项目即可。

要手动创建 Web 启动应用程序所需的文件,您需要生成两个附加文件,这些文件将放置在应用程序 JAR 之外。按照通常的方式创建项目的 JAR,然后创建一个相应的 JNLP 文件,用于启动应用程序,以及一个引用 JNLP 的 HTML 页面。HTML 页面显然是您从 Web 运行应用程序时打开的地方。以下是生成 JNLP 以及嵌入 HTML 的一些示例代码。

launch.jnlp

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jnlp codebase="file:/path-to-jar/" href="launch.jnlp" spec="1.0+">
    <information>
        <title>JythonSwingApp</title>
        <vendor>YourName</vendor>
        <homepage href=""/>
        <description>JythonSwingApp</description>
        <description kind="short">JythonSwingApp</description>
    </information>
<security>
<all-permissions/>
</security>
    <resources>
<j2se version="1.5+"/>
<jar eager="true" href="JythonSwingApp.jar" main="true"/>
    <jar href="lib/PlyJy.jar"/>
<jar href="lib/jython.jar"/>
</resources>
    <application-desc main-class="jythonswingapp.Main">
    </application-desc>
</jnlp>

launch.html

<html>
    <head>
        <title>Test page for launching the application via JNLP</title>
    </head>
    <body>
        <h3>Test page for launching the application via JNLP</h3>
        <a href="launch.jnlp">Launch the application</a>
        <!-- Or use the following script element to launch with the Deployment Toolkit -->
        <!-- Open the deployJava.js script to view its documentation -->
        <!--
        <script src="http://java.com/js/deployJava.js"></script>
        <script>
            var url="http://[fill in your URL]/launch.jnlp"
            deployJava.createWebStartLaunchButton(url, "1.6")
        </script>
        -->
    </body>
</html>

最终,Java Web 启动是通过 Web 分发 Jython 应用程序的非常好的方法。

PythonInterpreter 应用程序设计

也可以使用称为 PythonInterpreter 的特定于 Jython 的类来调用 Jython 脚本。此类本质上允许在 Java 中嵌入 Python 代码。实例化后,PythonInterpreter 将 Python 代码行作为字符串并执行它。此技术首次在本书第 10 章中介绍,该章涵盖了 Jython 和 Java 集成。使用 PythonInterpreter,可以在 Java 应用程序代码中直接调用 Jython 脚本。此技术还可用于使用 Java Main 类调用 Jython GUI 应用程序。此技术在将代码打包到 JAR 文件以进行分发时特别有用。

让我们看一下上面的示例,这次使用 PythonInterpreter 而不是对象工厂来调用我们的 Jython 代码。这里显示的唯一代码将是 Main.java,因为它是使用此技术时唯一需要更改的代码。当然,Java 接口不再需要,因此也可以从应用程序中删除它。

Main.java

package jythonswingapp;

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 代码中并不是最干净的技术。这会导致代码难以维护和排查问题。另一个缺点是,此技术迫使 Java 开发人员了解如何实例化 Jython 模块并在任何方式中使用它。对象工厂设计有助于强制抽象和简化,因为 Java 开发人员只需要了解 Java 接口中定义的那些方法。然而,在大多数情况下,应用程序只有一个开发人员,因此这些细微差别并不重要。

通过独立 JAR 分发

可以使用 Jython 独立 JAR 选项分发 Web 启动应用程序。为此,您必须拥有 Jython 独立 JAR 文件的副本,将其解压缩并将您的代码添加到文件中,然后将其重新打包以进行部署。使用此方法的唯一缺点是您可能需要稍微调整一下文件放置位置才能使其正常工作。

为了通过 JAR 分发您的 Jython 应用程序,首先下载 Jython 独立分发版。获得它后,您可以使用工具(例如“Stuffit”或“7zip”)从 jython.jar 中提取文件以扩展 JAR。JAR 解压缩后,您需要将任何 .py 脚本添加到 Lib 目录中,并将任何 Java 类添加到根目录中。例如,如果您有一个名为 org.jythonbook.Book 的 Java 类,您将根据包结构将其放置到相应的目录中。如果您有任何其他 JAR 文件要包含在您的应用程序中,那么您需要确保它们在您的类路径中。完成此设置后,使用前面提到的工具将修改后的独立 Jython JAR 重新打包到 ZIP 格式中。然后,您可以将 ZIP 重命名为 JAR。现在可以使用命令行中的 java “-jar” 选项运行应用程序,并使用可选的外部 .py 文件来调用您的应用程序。

java -jar newStandaloneJar.jar {optional .py file}

这只是用于创建包含应用程序的 JAR 文件的一种技术。还有其他方法可以执行此类技术,但这似乎是最直接和最容易执行的方法。

WSGI 和 modjy

WSGI,也称为 Web 服务器网关接口,是一个低级 API,提供 Web 服务器和 Web 应用程序之间的通信。实际上,WSGI 不止于此,您实际上可以使用 WSGI 编写完整的 Web 应用程序。但是,WSGI 更像是一个标准,可以从中编写 Web 框架。Python PEP 333 指定了 Web 服务器和 Python Web 应用程序或框架之间的建议标准接口,以促进 Web 应用程序在各种 Web 服务器上的可移植性。

WSGI 背后的基础超出了本节的范围。事实上,可以写出关于这个主题的完整书籍。本节将向您展示如何利用 WSGI 通过使用 modjy 来创建一个非常简单的“Hello Jython”应用程序。Modjy 是一个基于 Java/J2EE servlet 的 Jython 的 WSGI 兼容网关/服务器的实现。摘自 modjy 网站 (http://opensource.xhaus.com/projects/modjy/wiki),modjy 的特点如下

Jython WSGI 应用程序运行在 Java/J2EE 容器中,传入请求由 servlet 容器处理。容器被配置为将请求路由到 modjy servlet。modjy servlet 然后在 servlet 容器内创建一个嵌入式 jython 解释器,并加载配置的 jython web 应用程序。例如,Django 应用程序可以通过 modjy 加载。modjy servlet 然后将请求委托给配置的 WSGI 应用程序或框架。最后,WSGI 响应通过 servlet 容器路由回客户端。

在 Glassfish 中运行 modjy 应用程序

要在任何 Java servlet 容器中运行 modjy 应用程序,第一步是创建一个 Java web 应用程序,该应用程序将被打包为 WAR 文件。您可以从头开始创建应用程序,也可以使用 Netbeans 6.7 等 IDE 来协助。创建 web 应用程序后,确保 jython.jar 位于 CLASSPATH 中,因为 modjy 现在是 Jython 2.5.0 版本的一部分。最后,您需要在应用程序部署描述符 (web.xml) 中配置 modjy servlet。在本例中,我使用了 Google App Engine 的 modjy 示例应用程序,并将其部署在我的本地 Glassfish 环境中。

要使用 modjy 配置应用程序部署描述符,我们只需配置 modjy servlet,提供必要的参数,然后提供 servlet 映射。在下面显示的配置文件中,请注意 modjy servlet 类是 com.xhaus.modjy.ModjyServlet。您需要与 servlet 一起使用的第一个参数名为 python.home。将此参数的值设置为您的 jython 主目录。接下来,将参数 python.cachedir.skip 设置为 true。app_filename 参数提供应用程序可调用的名称。其他参数将为您配置的每个 modjy 应用程序设置相同。web.xml 中需要设置的最后一部分是 servlet 映射。在本例中,我们将所有 URL 设置为映射到 modjy servlet。

*web.xml*
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

  <display-name>modjy demo application</display-name>
  <description>
     modjy WSGI demo application
  </description>

  <servlet>
    <servlet-name>modjy</servlet-name>
    <servlet-class>com.xhaus.modjy.ModjyJServlet</servlet-class>
    <init-param>
      <param-name>python.home</param-name>
      <param-value>/Applications/jython/jython2.5.0/</param-value>
    </init-param>
    <init-param>
      <param-name>python.cachedir.skip</param-name>
      <param-value>true</param-value>
    </init-param>
<!--
        There are two different ways you can specify an application to modjy
        1. Using the app_import_name mechanism
        2. Using a combination of app_directory/app_filename/app_callable_name
        Examples of both are given below
        See the documenation for more details.
        http://modjy.xhaus.com/locating.html#locating_callables
-->
<!--
        This is the app_import_name mechanism. If you specify a value
        for this variable, then it will take precedence over the other mechanism
    <init-param>
      <param-name>app_import_name</param-name>
      <param-value>my_wsgi_module.my_handler_class().handler_method</param-value>
    </init-param>
-->
<!--
        And this is the app_directory/app_filename/app_callable_name combo
        The defaults for these three variables are ""/application.py/handler
        So if you specify no values at all for any of app_* variables, then modjy
        will by default look for "handler" in "application.py" in the servlet
        context root.
    <init-param>
      <param-name>app_directory</param-name>
      <param-value>some_sub_directory</param-value>
    </init-param>
-->
    <init-param>
      <param-name>app_filename</param-name>
      <param-value>demo_app.py</param-value>
    </init-param>
<!--
        Supply a value for this parameter if you want your application
        callable to have a different name than the default.
    <init-param>
      <param-name>app_callable_name</param-name>
      <param-value>my_handler_func</param-value>
    </init-param>
-->
        <!-- Do you want application callables to be cached? -->
    <init-param>
      <param-name>cache_callables</param-name>
      <param-value>1</param-value>
    </init-param>
    <!-- Should the application be reloaded if it's .py file changes? -->
    <!-- Does not work with the app_import_name mechanism -->
    <init-param>
      <param-name>reload_on_mod</param-name>
      <param-value>1</param-value>
    </init-param>
    <init-param>
      <param-name>log_level</param-name>
      <param-value>debug</param-value>
<!--  <param-value>info</param-value>  -->
<!--  <param-value>warn</param-value>  -->
<!--  <param-value>error</param-value> -->
<!--  <param-value>fatal</param-value> -->
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>modjy</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

demo_app 应该按如下方式编码。作为 WSGI 标准的一部分,应用程序提供了一个函数,服务器为每个请求调用该函数。在本例中,该函数名为 handler。该函数必须接受两个参数,第一个是 CGI 定义的环境变量字典。第二个是返回 HTTP 标头的回调函数。回调函数也应该按如下方式编码 start_response(status, response_headers, exx_info=None),其中 status 是 HTTP 状态,response_headers 是 HTTP 标头的列表,exc_info 用于异常处理。让我们看一下 demo_app.py 应用程序,并识别我们刚刚讨论的功能。

import sys

def escape_html(s): return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

def cutoff(s, n=100):
    if len(s) > n: return s[:n]+ '.. cut ..'
    return s

def handler(environ, start_response):
    writer = start_response("200 OK", [ ('content-type', 'text/html') ])
    response_parts = []
    response_parts.append("<html>")
    response_parts.append("<head>")
    response_parts.append("<title>Modjy demo WSGI application running on Local Server!</title>")
    response_parts.append("</head>")
    response_parts.append("<body>")
    response_parts.append("<p>Modjy servlet running correctly: jython %s on %s:</p>" % (sys.version, sys.platform))
    response_parts.append("<h3>Hello jython WSGI on your local server!</h3>")
    response_parts.append("<h4>Here are the contents of the WSGI environment</h4>")
    environ_str = "<table border='1'>"
    keys = environ.keys()
    keys.sort()
    for ix, name in enumerate(keys):
        if ix % 2:
            background='#ffffff'
        else:
            background='#eeeeee'
        style = " style='background-color:%s;'" % background
        value = escape_html(cutoff(str(environ[name]))) or '&#160;'
        environ_str = "%s\n<tr><td%s>%s</td><td%s>%s</td></tr>" % \
            (environ_str, style, name, style, value)
    environ_str = "%s\n</table>" % environ_str
    response_parts.append(environ_str)
    response_parts.append("</body>")
    response_parts.append("</html>")
    response_text = "\n".join(response_parts)
    return [response_text]

此应用程序返回您运行应用程序的服务器的环境配置。如您所见,该页面代码非常简单,实际上类似于 servlet。

应用程序设置和配置完成后,只需将代码编译成 WAR 文件,然后将其部署到您选择的 Java servlet 容器中。在本例中,我使用了 Glassfish V2,它工作得很好。但是,此应用程序应该可以部署到 Tomcat、JBoss 等。

结论

我们可以使用 Jython 创建简单的基于 web 的应用程序,方法有很多。Jython servlet 是在 web 上提供内容的好方法,您也可以将它们与 JSP 页面一起使用,从而实现 Model-View-Controller 模式。这是一种用于开发复杂 web 应用程序的好技术,尤其是那些将一些 Javascript 混合到操作中的应用程序,因为它确实有助于组织事物。大多数 Java web 应用程序使用框架或其他技术来帮助组织应用程序,以便应用 MVC 概念。能够使用 Jython 完成此类工作非常棒。

本章还讨论了使用 modjy 在 Jython 中创建 WSGI 应用程序。这是一种生成 web 应用程序的好方法,尽管 modjy 和 WSGI 通常用于实现 web 框架等。Django 等解决方案使用 WSGI 来遵循 PEP 333 为所有 Python web 框架制定的标准。您可以从本章中的部分内容看到,WSGI 也是一种快速编写 web 应用程序的好方法,就像在 Jython 中编写 servlet 一样。

在接下来的章节中,您将了解有关使用 Jython 可用的 web 框架的更多信息,特别是 Django 和 Pylons。这两个框架可以使任何 web 开发人员的生活更轻松,现在它们通过 Jython 在 Java 平台上可用,它们变得更加强大。使用 Django 等模板技术可以非常高效,并且是设计完整 web 应用程序的好方法。本章中讨论的技术也可以用于开发大型 web 应用程序,但不要忽视使用下一章中讨论的标准框架。如今,有许多很棒的方法可以编写 Jython web 应用程序,并且这些选项还在不断增长!