89、Jython在服务器端Web编程中的应用

Jython在服务器端Web编程中的应用

在服务器端Web编程领域,Jython有着独特的应用价值。它可以在Java适用的任何地方发挥作用,尤其在那些需要快速开发和高灵活性的Java应用场景中表现出色。Java服务器端Web编程主要通过Servlet和Java Server Pages(JSP)实现,因此Jython的主要应用也是Servlet和JSP。不过,Java的企业级包(j2ee)在Web应用开发中也很重要,EJB、JNDI、JDBC等技术在大多数Java Web应用中不可或缺,Jython在这些技术方面同样有效。

1. Jython的适用与不适用场景

Jython并非适用于所有场景。CPython是实现CGI脚本的流行语言,但Jython并不适合。在CGI中,Web服务器接收请求后启动一个进程进行响应,响应完成后关闭该子进程。虽然可以用Jython这样做,但由于JVM的启动时间较长,这并不是一个好选择。而Servlet和JSP则具有持久性,它们在Web请求之间会保留在内存中。

使用Jython进行Web编程有很多优势。高质量的Servlet容器很容易获取且广泛部署。Java Servlet应用可以受益于Jython的灵活性和高级语言特性,同时还能利用Java和Jython的库。常见的Servlet容器有WebLogic、WebSphere、Tomcat、Jigsaw、Resin和Jetty等,许多组织都部署了这些容器,这使得Jython在很多情况下都能立即使用。

2. Jython Servlet容器

Jython可以与任何兼容的Java Servlet容器配合使用,有很多这样的容器可供选择。这里以Tomcat为例,它是Servlet和Java Server Page规范的参考实现。以下是一些流行且免费的Servlet容器介绍:
| 容器名称 | 描述 | 下载地址 | 支持规范 |
| — | — | — | — |
| Jakarta’s Tomcat | 是Servlet和Java Server Pages规范的参考实现 | http://jakarta.apache.org/ | 3.2.3版本支持2.2 Servlet和1.1 JSP规范;4.0版本实现2.3 Servlet和1.2 JSP规范 |
| Apache JServ | 为Apache创建的Servlet(版本2.0)引擎 | http://java.apache.org/ | 需要Java Servlet Development Kit 2.0,可从http://java.sun.com/products/servlet/index.html下载;Java Server Pages需要外部模块,位于http://java.apache.org/jserv/ |
| Jigsaw | W3C的实验性Web服务器,是完全符合HTTP/1.1的Web服务器和缓存代理服务器 | http://www.w3.org/Jigsaw/ | 支持Servlet 2.2规范和Java Server Pages 1.1 |
| Jetty | 紧凑高效的Java Web服务器 | http://jetty.mortbay.com/ | 支持Servlet 2.2和JSP 1.1规范,支持HTTP 1.1,包含SSL支持,可轻松与EJB服务器集成 |

这些工具的文档很丰富,安装指南可从各自的网站获取。

3. 定义简单的Servlet类

下面比较一个简单的Java Servlet和一个简单的Jython Servlet,并介绍如何在Tomcat Web应用中安装jython.jar文件。

3.1 简单的Java Servlet

以下是一个基本的Java Servlet示例:

// Filename: JavaServlet.java 
import javax.servlet.*; 
import java.io.*; 

public class JavaServlet extends GenericServlet {
    public void service(ServletRequest req, ServletResponse res) 
    throws ServletException, IOException {
        res.setContentType("text/html"); 
        PrintWriter toClient = res.getWriter(); 
        toClient.println("<html><body>" + 
                         "This is a Java Servlet." + 
                         "</body></html>"); 
    } 
} 

这个示例使用了Servlet API中的GenericServlet类, service 方法是抽象的,必须在子类中实现,它会在接收到Web请求时被调用,输出内容通过 PrintWriter 对象发送给客户端。

3.2 简单的Jython Servlet

对应的Jython Servlet示例如下:

# Filename: JythonServlet.py 
from javax import servlet 
import random # Not used, just here to test module imports 
class JythonServlet(servlet.GenericServlet): 
    def service(self, req, res): 
        res.setContentType("text/html") 
        toClient = res.getWriter() 
        toClient.println("""<html><body> 
                         This is a Servlet of the Jython variety. 
                         </body></html>""") 

这个Jython Servlet与Java Servlet类似,继承自 GenericServlet 并实现了 service 方法。不过,Jython的语法有一些不同,如类定义后的括号指定父类、省略 throws 语句、 service 方法有显式的 self 参数、没有分号和显式类型声明等。

4. 测试Java和Jython Servlet

测试这两个Servlet需要安装Tomcat,Jython Servlet还需要将jython.jar文件包含在Tomcat中。

4.1 安装Tomcat

安装Tomcat的步骤如下:
1. 从http://jakarta.apache.org下载Tomcat,建议下载jakarta-tomcat-3.2.3版本,根据平台选择合适的zip或tar.gz文件。
2. 将下载的文件解压到有足够权限的目录,该目录即为Tomcat主目录。设置环境变量 TOMCAT_HOME 指向该目录,例如在Windows上:

set TOMCAT_HOME=c:\jakarta-tomcat-3.2.3 

在*nix系统上:

export TOMCAT_HOME=/usr/local/jakarta-tomcat-3.2.3 

如果不设置该环境变量,则必须在Tomcat主目录或bin目录下启动Tomcat。
3. 设置环境变量 JAVA_HOME 指向JDK安装的根目录,例如使用JDK1.3.1时,在Windows上:

set JAVA_HOME=c:\jdk1.3.1 

在*nix系统上:

export JAVA_HOME=/usr/java/jdk1.3.1 
  1. 安装完成后,使用适合平台的启动脚本启动Tomcat:
    在Windows上:
%TOMCAT_HOME%\bin\startup.bat 

在*nix系统上:

$TOMCAT_HOME/bin/startup.sh 

启动时,注意查看是否有如下信息:

date time - PoolTcpConnector: Starting HttpConnectionHandler on 8080 

这表示将使用8080端口连接到Servlet容器。
5. 停止Tomcat时,使用相应的关闭脚本:
在Windows上:

%TOMCAT_HOME%\bin\shutdown.bat 

在*nix系统上:

$TOMCAT_HOME/bin/shutdown.sh 

Servlet 2.2规范指定了Web应用的目录层次结构,从 %TOMCAT_HOME%\webapps 开始,该目录下的文件夹是Web应用或上下文,每个上下文都有特定的目录结构。这里使用名为 jython 的上下文,其目录结构如下:

%TOMCAT_HOME%\webapps\ 
%TOMCAT_HOME%\webapps\jython                    The context's root 
%TOMCAT_HOME%\webapps\jython\WEB-INF 
%TOMCAT_HOME%\webapps\jython\WEB-INF\classes    Servlet classes 
%TOMCAT_HOME%\webapps\jython\WEB-INF\lib        Library archives 

在继续示例之前,应创建这些目录。重启Tomcat时,若看到如下信息,则表示Tomcat已加载新的上下文:

date time - ContextManager: Adding context Ctx( /jython ) 
4.2 安装Java Servlet

安装Java Servlet的步骤如下:
1. 将 JavaServlet.java 文件放在 %TOMCAT_HOME%\webapps\jython\WEB-INF\classes 目录下,该目录是类文件的根目录。由于 JavaServlet 不在包中,所以放在 classes 目录的根目录下。
2. 在该目录下,使用以下命令编译 JavaServlet.java 文件:

javac -classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java 
  1. 编译完成后,启动Tomcat服务器,然后在浏览器中访问 http://localhost:8080/jython/servlet/JavaServlet ,应看到消息“This is a Java Servlet.”
4.3 安装Jython Servlet

使用Jython Servlet有两种方式:一种是使用jythonc编译Servlet并将生成的类文件放在 %TOMCAT_HOME%\jython\WEB-INF\classes 目录下;另一种是使用Jython的PyServlet映射类。这里使用jythonc,步骤如下:
1. 使用jythonc编译Jython Servlet模块
编译时,类路径中必须包含 servlet.jar 文件,该文件包含 javax.Servlet.* 类和包,位于Tomcat的 lib 目录( %TOMCAT_HOME%\lib )中。将 JythonServlet.py 文件放在 %TOMCAT_HOME%\jython\WEB-INF\classes 目录下,确保环境变量 CLASSPATH 包含 servlet.jar ,然后在该目录下使用以下命令编译Jython代码:

jythonc -w . JythonServlet.py 

-w 开关指定当前工作目录,避免将生成的类文件从 jpywork 目录复制出来。编译后, classes 目录中应有两个类文件,如 JythonServlet.java JythonServlet.class JythonServlet$_PyInner.class ,这两个类文件都必须在 WEB-INF\classes 目录中才能使用Servlet。编译时,要注意查看是否有如下信息:

Creating .java files: 
  JythonServlet module 
     JythonServlet extends javax.servlet.GenericServlet 

如果没有看到这些信息,说明有问题,Servlet将无法工作,需检查 CLASSPATH 和设置选项,重新编译直到看到这些信息。
2. 将jython.jar添加到类路径
所有Jython Servlet都必须能够访问jython.jar文件中的类,有三种方法将jython.jar文件添加到Tomcat的类路径:
- 添加到上下文的lib目录 :这是首选方法,将jython.jar文件放在 %TOMCAT_HOME%\webapps\jython\WEB-INF\lib 目录中,这样可以创建一个自包含的Web应用。
- 添加到Tomcat的lib目录 :不推荐这种方法,虽然可以减少jython.jar文件的重复,但Web应用不再自包含,且无法自动访问Jython的lib目录。
- 留在Jython的安装目录,但在运行Tomcat之前将其添加到类路径 :这种方法可以消除jython.jar文件的重复,还能访问注册表文件和Jython的lib目录,但自包含的上下文更受青睐。
3. 使Jython的lib目录对Servlet可用
有三种方法使Jython的lib目录中的模块对Servlet可用:
- 设置python.home属性 :可以设置环境变量 TOMCAT_OPTS 来设置 python.home 属性。建议在上下文的 WEB-INF 目录中创建一个名为 jylib 的目录,再在其中创建 Lib 目录,将所需模块放在该目录中,并将 jylib 目录指定为 python.home 。例如,在Windows上:

set TOMCAT_OPTS=-Dpython.home=%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib 

在*nix系统上:

export TOMCAT_OPTS=-Dpython.home=$TOMCAT_HOME/webapps/jython/WEB-INF/jylib 

注意,某些Windows版本不允许环境字符串中包含等号,这种情况下需编辑 tomcat.bat 文件以包含 python.home 参数设置。
- 显式将模块目录添加到sys.path :在Servlet顶部添加如下代码:

import sys 
libpath = "c:/jakarta-tomcat_3.2.3/webapps/jython/WEB-INF/jylibs/Lib" 
sys.path.append(libpath) 

但这种方法通常需要显式的、依赖于机器的路径,限制了跨平台的可移植性。
- 冻结所需模块 :使用 jythonc --deep 选项编译所有所需模块,使 python.home 和Jython的lib目录不再重要。在Jython上下文的 classes 目录下,使用以下命令冻结 JythonServlet.py 文件:

jythonc -w . --deep JythonServlet.py 

这样,JythonServlet和所有所需模块的类文件都位于上下文的 classes 目录中,即Servlet和模块安装在一个自包含的Web应用中。但要注意,用这种方式编译其他Jython Servlet会覆盖之前编译的模块,更新模块时需小心。编译后,生成的 .java 文件可删除。

4.4 测试Jython Servlet

编译 JythonServlet.py 文件、将jython.jar添加到类路径并使Jython的模块可访问后,在浏览器中访问 http://localhost:8080/jython/servlet/jythonServlet ,应看到消息“This is a Servlet of the Jython variety.”。若未看到该消息,可能出现以下三种错误:
- 若编译 JythonServlet 时类路径中没有 servlet.jar 文件,可能会看到 ClassCastException
- 若文件名和类名不同(即使只是大小写不同),也会看到 ClassCastException
- 若使用jythonc编译Servlet生成的类文件中有一个不在上下文的 classes 目录中,会看到 AttributeError ;若Jython的模块不可用,会看到 ImportError

5. 关于GenericServlet

JythonServlet.py 继承自 javax.Servlet.GenericServlet ,这是一个不特定于任何协议的Servlet类。 HttpServlet 在Web编程中更常见,因为它特定于HTTP协议,但 GenericServlet HttpServlet 的超类,了解其方法很重要。以下是 GenericServlet 的一些方法及其在Jython子类中的使用示例:
| Java签名 | Jython子类中的用法 |
| — | — |
| 重写的方法 | |
| public void init(ServletConfig config) throws ServletException; | def init(self, config): |
| public void destroy(); | def destroy(self): |
| public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException; | def service(self, request, response): |
| public String getServletInfo(); | def getServletInfo(self): return "Info string" |
| 使用self.方法语法的方法 | |
| public void log(String message); | self.log("message") |
| public ServletConfig getServletConfig(); | config = self.getServletConfig() |
| public java.util.enumeration getInitParameterNames(); | nameList = self.getInitParameterNames() |
| public String getInitParameter(String name) | param = self.getInitParameter("paramName") |
| public ServletContext getServletContext() | context = self.getServletContext() |

下面通过一个点击计数器Servlet示例来进一步了解这些方法。

5.1 点击计数器Servlet

以下是一个点击计数器Servlet的示例:

# filename: HitCounter.py 
from javax import servlet 
from time import time, ctime 
import os 
class HitCounter(servlet.GenericServlet): 
    def init(self, cfg=None): 
        if cfg: 
            servlet.GenericServlet.init(self, cfg) 
        else: 
            servlet.GenericServlet.init(self) 
        # Construct a path + filename to file storing hit data 
        contextRoot = self.servletContext.getRealPath(".") 
        self.file = os.path.join(contextRoot, "counterdata.txt") 
        if os.path.exists(self.file): 
            lastCount = open(self.file, "r").read() 
            try: 
                # within 'try' just in case the file is empty 
                self.totalHits = int(lastCount) 
            except: 
                self.totalHits = 0 
        else: 
            self.totalHits = 0 
    def service(self, req, res): 
        res.setContentType("text/html") 
        toClient = res.getWriter() 
        toClient.println("<html><body>") 
        toClient.println("Total Hits: %i<br>" % (self.totalHits,)) 
        self.totalHits += 1 
        toClient.println("</body></html>") 
    def destroy(self): 
        f = open(self.file, "w") 
        f.write(str(self.totalHits)) 
        f.close() 

HitCounter.py 文件放在上下文的 classes 目录( %TOMCAT_HOME%\webapps\jython\WEB-INF\classes )中,使用以下命令编译:

jythonc -w . --deep HitCounter.py 

在浏览器中访问 http://localhost:8080/jython/servlet/HitCounter ,第一次访问时应看到消息“Total Hits: 0”,每次后续点击都会增加计数。关闭并重启Tomcat后,点击计数应继续上次的数字。

5.2 三个重要方法

init service destroy 这三个方法对Servlet至关重要,每个方法对应Servlet生命周期的一个阶段:
- init(ServletConfig)方法 :在Servlet启动时调用,且仅调用一次,适用于耗时的任务,如建立数据库连接、编译正则表达式或加载辅助文件。在上述示例中,该方法用于建立存储点击信息的文件,并将计数器设置为该文件中最后存储的整数,确保除非Servlet重启且 counter.txt 文件缺失,否则点击计数器不会重置为0。Servlet API 2.2版本有两个 init 方法:一个无参数版本和一个接受 ServletConfig 类实例的版本。在Jython中重写Java方法时,会重写所有同名方法,因此示例中通过为 cfg 变量指定默认参数 None ,然后测试 None 来决定是否在调用超类的 init 方法时使用该变量。
- service(ServletRequest, ServletResponse)方法 :每次接收到客户端请求时调用,Servlet必须定义该方法,因为它在 GenericServlet 中是抽象的。该方法的参数是 ServletRequest ServletResponse 对象, ServletRequest 包含客户端发送到服务器的请求信息, ServletResponse 用于将响应流以适当的mime编码格式发送回客户端。
- destroy()方法 :在关闭或卸载Servlet时调用,这里放置清理代码,如关闭数据库连接、刷新和关闭文件等。在上述示例中,该方法将点击计数器的值写入文件,以防止Servlet卸载时信息丢失,但这不是最佳的持久化方法,因为只有在服务器正常关闭时点击计数才会存储。

6. HttpServlet

GenericServlet 适用于任何聊天类型的协议,但Web开发主要涉及HTTP协议,因此最好扩展 javax.Servlet.http.HttpServlet HttpServlet GenericServlet 的子类,因此扩展 HttpServlet 时可以使用 GenericServlet init service destroy 方法。

6.1 HttpServlet的方法

HttpServlet 为每个HTTP方法定义了一个方法,如 doGet doPost doPut doOptions doDelete doTrace 。当Tomcat接收到GET类型的客户端请求时,会调用请求的Servlet的 doGet() 方法进行响应。此外, HttpServlet 有一个特定于HTTP的 service 方法,与 GenericServlet 的版本相比,其参数是特定于HTTP的请求和响应对象( javax.servlet.http.HttpServletRequest javax.servlet.http.HttpServletResponse )。

以下是 HttpServlet 类的方法及其在Jython中的使用示例:
| Java签名 | Jython子类中的用法 |
| — | — |
| doDelete(HttpServletRequest req, HttpServletResponse resp) | def doDelete(self, req, res): |
| doGet(HttpServletRequest req, HttpServletResponse resp) | def doGet(self, req, res): |
| doHead(HttpServletRequest req, HttpServletResponse resp) | def doHead(self, req, res): |
| doOptions(HttpServletRequest req, HttpServletResponse resp) | def doOptions(self, req, res): |
| doPost(HttpServletRequest req, HttpServletResponse resp) | def doPost(self, req, res): |
| doPut(HttpServletRequest req, HttpServletResponse resp) | def doPut(self, req, res): |
| doTrace(HttpServletRequest req, HttpServletResponse resp) | def doTrace(self, req, res): |
| getLastModified(HttpServletRequest req) | def getLastModified(self, req): |
| service(HttpServletRequest req, HttpServletResponse resp) | def service(self, req, res): |
| service(ServletRequest req, ServletResponse res) | def service(self, req, res): |

接受特定于HTTP的请求和响应对象的 service 方法会将请求重新分发到适当的 do* 方法(如果未被重写),接受通用Servlet请求和响应对象的 service 方法会将请求重新分发到特定于HTTP的 service 方法,这种重新分发在实现Servlet映射时很有价值。

6.2 HttpServlet示例

以下是一个扩展 javax.servlet.http.HttpServlet 的Servlet示例:

#file get_post.py 
from time import time, ctime 
from javax import servlet 
from javax.servlet import http 
class get_post(http.HttpServlet): 
    head = "<head><title>Jython Servlets</title></head>" 
    title = "<center><H2>%s</H2></center>" 
    def doGet(self, req, res): 
        res.setContentType("text/html") 
        out = res.getWriter() 
        out.println('<html>') 
        out.println(self.head) 
        out.println('<body>') 
        out.println(self.title % req.method) 
        out.println("This is a response to a %s request" % 
                    (req.getMethod(),)) 
        out.println("<P>In this GET request, we see the following " + 
                    "header variables.</P>") 
        out.println("<UL>") 
        for name in req.headerNames: 
            out.println(name + " : " + req.getHeader(name) + "<br>") 
        out.println("</UL>") 
        out.println(self._params(req)) 
        out.println(""" 
            <P>The submit button below is part of a form that uses the 
               "POST" method. Click on this button to do a POST request. 
            </P>""") 
        out.println('<br><form action="get_post" method="POST">' + 
                    '<INPUT type="hidden" name="variable1" value="one">' + 
                    '<INPUT type="hidden" name="variable1" value="two">' + 
                    '<INPUT type="hidden" name="variable2" value="three">' + 
                    '<INPUT type="submit" name="button" value="submit">') 
        out.println('<br><font size="-2">time accessed: %s</font>' 
                    % ctime(time())) 
        out.println('</body></html>') 
    def doPost(self, req, res): 
        res.setContentType("text/html"); 
        out = res.getWriter() 
        out.println('<html>') 
        out.println(self.head) 
        out.println('<body>') 
        out.println(self.title % req.method) 
        out.println("This was a %s<br><br>" % (req.getMethod(),)) 
        out.println(self._params(req)) 
        out.println('<br> back to <a href="get_post">GET</a>') 
        out.println('<br><font size="-2">time accessed: %s</font>' 
                    % ctime(time())) 
        out.println('</body></html>') 
    def _params(self, req): 
        params = "Here are the parameters sent with this request:<UL>" 
        names = req.getParameterNames() 
        if not names.hasMoreElements(): 
            params += "None<br>" 
        for name in names: 
            value = req.getParameterValues(name) 
            params += "%s : %r<br>" % (name, tuple(value)) 
        params += "</UL>" 
        return params 

get_post.py 文件放在 $TOMCAT_HOME/webapps/jython/WEB-INF/classes 目录下,使用以下命令编译:

jythonc -w . --deep get_post.py 

在浏览器中访问 http://localhost:8080/jython/servlet/get_post ,应看到一个类似于示例中描述的浏览器窗口。在这个示例中,GET操作的参数为空,但可以通过在URL末尾添加参数(如 http://localhost:8080/servlet/get_post?variable1=1&variable2=2 )来测试 doGet 方法中的其他参数。由于页面底部的提交按钮是一个POST表单,点击该按钮将执行同一个Servlet的 doPost 方法,结果应与示例中的描述相符。

6.3 HttpServletRequest和HttpServletResponse

与客户端连接的通信通过 HttpServletRequest HttpServletResponse 对象进行,它们是从客户端接收和发送到客户端的实际请求流的抽象,每个对象都添加了更高级的、特定于HTTP的方法,以方便处理请求和响应。

以下是 HttpServletRequest 对象的一些方法及其对应的bean属性名称:
| 方法和属性 | 描述 |
| — | — |
| getAuthType() / AuthType | 返回描述认证类型名称的字符串(PyString),若用户未认证,值为None |
| getContextPath() / contextPath | 返回描述请求的上下文路径信息的字符串(PyString) |
| getCookies() / cookies() | 返回客户端请求发送的所有cookie,作为 javax.servlet.http.Cookie 对象的数组 |
| getDateHeader(name) | 以长类型检索指定头的值 |
| getHeader(name) | 以字符串(PyString)形式返回指定头的值 |
| getHeaderNames() / headerNames | 返回请求中包含的所有头名称的枚举 |
| getHeaders(name) | 返回指定头名称的所有值的枚举 |
| getIntHeader(name) | 以Java int类型检索指定头的值,Jython将其转换为PyInteger |
| getMethod() / method | 返回请求类型的字符串 |
| getPathInfo() / pathInfo | 客户端发送的所有额外路径信息 |
| getPathTranslated() / pathTranslated | 返回从客户端请求的额外路径信息派生的实际路径 |
| getQueryString() / queryString | 返回客户端请求的查询字符串(路径后的字符串) |
| getRemoteUser() / remoteUser | 返回客户端的登录名,若客户端未认证,值为None |
| getRequestedSessionId() / requestedSessionId | 返回客户端的会话ID |
| getRequestURI() / requestURI | 返回协议名称和查询字符串之间的部分 |
| getServletPath() / servletPath | 返回指定当前Servlet的URL部分 |
| getSession() / session | 返回当前会话,若需要则创建一个会话,会话是 javax.servlet.http.HttpSession 的实例 |
| getSession(create) | 若存在当前会话则返回,若不存在且 create 值为 true 则创建一个新会话 |
| getUserPrincipal() / userPrincipal | 返回包含当前认证信息的 java.security.Principal 对象 |
| isRequestedSessionIdFromCookie() | 根据当前会话ID是否来自cookie返回1或0 |
| isRequestedSessionIdFromURL() | 根据当前会话ID是否来自请求的URL字符串返回1或0 |
| isRequestedSessionIdValid() | 根据请求的会话ID是否仍然有效返回1或0 |
| isUserInRole(role) | 指示用户是否在指定角色中,返回1或0 |

HttpServletResponse 对象用于将mime编码的流发送回客户端,它定义了一些在通用 ServletResponse 对象中不存在的特定于HTTP的方法。以下是 HttpServletResponse 对象的一些方法及其描述:
| 方法和属性 | 描述 |
| — | — |
| addCookie(cookie) | 向响应中添加一个cookie |
| addDateHeader(headerName, date) | 添加一个带有日期(长类型)值的头名称 |
| addHeader(headerName, value) | 添加一个头名称和值 |
| addIntHeader(headerName, value) | 添加一个带有整数值的头名称 |
| containsHeader(headerName) | 根据指定的头是否存在返回1或0 |
| encodeRedirectUrl(url) | 为 sendRedirect 方法编码URL(2.1及更高版本使用 encodeRedirectURL ) |
| encodeRedirectURL(url) | 为 sendRedirect 方法编码URL |
| encodeURL(url) | 通过包含会话ID对URL进行编码 |
| sendError(sc) | 使用状态码发送错误 |
| sendError(sc, msg) | 使用指定的状态码和消息发送错误 |
| sendRedirect(location) | 发送临时重定向到指定位置 |
| setDateHeader(headerName, date) | 将头名称设置为指定的日期(长类型)值 |
| setHeader(headerName, value) | 将头名称设置为指定的值 |
| setIntHeader(headerName, value) | 将头名称设置为指定的整数值 |
| setStatus(statusCode) / status | 设置响应状态码 |

HttpServletResponse 类还包含与标准HTTP响应代码对应的字段,可与 sendError(int) setStatus(int) 一起使用,以下是一些常见的状态码及其含义:
| 错误代码 | 状态 |
| — | — |
| 100 | SC_CONTINUE |
| 101 | SC_SWITCHING_PROTOCOLS |
| 200 | SC_OK |
| 201 | SC_CONTINUE |
| 202 | SC_ACCEPTED |
| 203 | SC_NON_AUTHORITATIVE_INFORMATION |
| 204 | SC_NO_CONTENT |
| 205 | SC_RESET_CONTENT |
| 206 | SC_PARTIAL_CONTENT |
| 300 | SC_MULTIPLE_CHOICES |
| 301 | SC_MOVED_PERMANENTLY |
| 302 | SC_MOVED_TEMPORARILY |
| 303 | SC_SEE_OTHER |
| 304 | SC_NOT_MODIFIED |
| 305 | SC_USE_PROXY |
| 400 | SC_BAD_REQUEST |
| 401 | SC_UNAUTHORIZED |
| 402 | SC_PAYMENT_REQUIRED |
| 403 | SC_FORBIDDEN |
| 404 | SC_NOT_FOUND |
| 405 | SC_METHOD_NOT_ALLOWED |
| 406 | SC_NOT_ACCEPTABLE |
| 407 | SC_PROXY_AUTHENTICATION_REQUIRED |
| 408 | SC_REQUEST_TIMEOUT |
| 409 | SC_CONFLICT |
| 410 | SC_GONE |
| 411 | SC_LENGTH_REQUIRED |
| 412 | SC_PRECONDITION_FAILED |
| 413 | SC_REQUEST_ENTITY_TOO_LARGE |
| 414 | SC_REQUEST_URI_TOO_LONG |
| 415 | SC_UNSUPPORTED_MEDIA_TYPE |
| 500 | SC_INTERNAL_SERVER_ERROR |
| 501 | SC_NOT_IMPLEMENTED |
| 502 | SC_BAD_GATEWAY |
| 503 | SC_SERVICE_UNAVAILABLE |
| 504 | SC_GATEWAY_TIMEOUT |
| 505 | SC_HTTP_VERSION_NOT_SUPPORTED |

7. PyServlet

对于Jython程序员来说, HttpServlet 编程的一个很大优势是Jython发行版附带了 org.python.util.PyServlet 。这个Servlet可以加载、执行和缓存Jython文件,这样就可以编写和查看Jython Servlet而无需中间的编译步骤。这通过Servlet映射实现,Servlet映射是特定Servlet(这里是 PyServlet )与特定URL模式(如 *.py )之间的关联。通过适当的 web.xml 文件,Jython的 PyServlet 类被映射到所有 *.py 文件,当请求 *.py 文件时, PyServlet 类会根据需要加载、缓存和调用 *.py 文件中的方法以进行响应。

7.1 PyServlet的限制

PyServlet 的设计对其服务的Jython文件有一些限制。Jython文件必须包含一个继承自 javax.servlet.http.HttpServlet 的类,该类的名称必须与文件名称(不带 .py 扩展名)匹配。此外,除了继承自 HttpServlet 的那个类之外,使用模块全局标识符是不安全的。

为了避免命名混淆,这里将实现Servlet的Jython模块称为jylets。

7.2 安装PyServlet

安装 PyServlet 只需定义一个Servlet映射并确保 jython.jar 文件在上下文的 lib 目录中。Servlet映射在上下文的部署描述符文件 web.xml 中定义,Jython上下文的 web.xml 文件位于 $TOMCAT_HOME/webapps/jython/WEB-INF/web.xml 。在Tomcat中,若上下文的 web.xml 未明确包含某些设置,则使用 $TOMCAT_HOME/conf/web.xml 中的默认 web.xml 文件。因此,上下文可能还没有 web.xml 文件,且无需在该上下文中定义所有属性,因为已有默认值。

以下是一个示例部署描述符:

<web-app> 
    <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> 
</web-app> 

这个示例定义了 PyServlet 的名称和类,类是唯一标识Servlet类的完整包类层次结构,实际类位于 jython.jar 文件中,该文件应在上下文的 lib 目录中。

PyServlet 的Servlet定义可以选择包含初始化参数( init-params ), PyServlet 在初始化Jython时使用这些参数,因此可以在这里设置Jython属性,如 python.home python.path python.respectJavaAccessibility 等。以下是一个包含 python.home python.path 值作为 init-params web.xml 文件示例:

<web-app> 
    <servlet> 
        <servlet-name>PyServlet</servlet-name> 
        <servlet-class> 
            org.python.util.PyServlet 
        </servlet-class> 
        <load-on-startup>1</load-on-startup> 
        <init-param> 
            <param-name>python.home</param-name> 
            <param-value>c:\jython-2.1</param-value> 
        </init-param> 
        <init-param> 
            <param-name>python.path</param-name> 
            <param-value> 
                c:\jython-2.1\lib\site-packages 
            </param-value> 
        </init-param> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>PyServlet</servlet-name> 
        <url-pattern>*.py</url-pattern> 
    </servlet-mapping> 
</web-app> 

定义Jython资源位置的属性(如 python.home python.path )需要特别注意,它们会影响上下文是否自包含以及跨平台的可移植性。若 python.home 属性指向上下文之外,或 python.path 属性包含上下文之外的目录,则上下文不再自包含,且属性值必须是特定于平台的路径。幸运的是, PyServlet python.home 属性有一个默认值,可创建自包含且与平台无关的上下文。

PyServlet 的默认 python.home 值是上下文的 lib 目录,这使得Jython上下文的默认 python.home 值根据平台不同可能是 %TOMCAT_HOME\webapps\jython\WEB-INF\lib $TOMCAT_HOME/webapps/jython/WEB-INF/lib 。此外,Jython的 lib 目录自动变为 %TOMCAT_HOME%\webapps\jython\WEB-INF\lib\Lib $TOMCAT_HOME/webapps/jython/WEB-INF/lib/Lib ,这将所有Jython资源保留在上下文中,使其自包含,并且 PyServlet 以与平台无关的方式添加默认 python.home 路径,使使用默认值的上下文具有平台独立性。

7.3 测试PyServlet

可以通过启动Tomcat并在浏览器中查看一个简单的jylet来确认Servlet映射是否正常工作。以下是一个简单的测试jylet:

# File ServletMappingTest.py 
from javax.servlet.http import HttpServlet 
class ServletMappingTest(HttpServlet): 
    def doGet(self, req, res): 
        out = res.writer 
        res.contentType = "text/html" 
        print >> out, "Greetings from a jylet." 

将这个测试文件保存为 %TOMCAT_HOME%\webapps\jython\ServletMappingTest.py ,然后在浏览器中访问 http://localhost:8080/jython/ServletMappingTest.py ,若看到问候消息,则 PyServlet 映射正确。为完成安装,创建Jython的 lib 目录 %TOMCAT_HOME%\webapps\jython\WEB-INF\lib\Lib ,将在jylets中使用的任何Jython模块放在该目录中,安装即完成。

8. Cookies

Cookies允许在客户端机器上存储信息,这些信息将包含在后续请求中。在Jython中创建和操作Cookies可使用 javax.Servlet.http.Cookie 类。创建新的Cookie需要使用名称和值作为参数实例化 javax.servlet.http.Cookie 。例如,若使用Cookies跟踪图书购买情况,可以这样设置作者和标题:

from javax.servlet import http 
name = "book1" 
value = "Lewis Carroll, Alice In Wonderland" 
MyNewCookie = http.Cookie(name, value) 

要将这个Cookie发送给客户端,需要使用 HttpServletResponse addCookie(cookie) 方法:

res.addCookie(MyNewCookie) 

添加Cookies应在通过响应流发送其他内容之前进行。

每个Cookie实例可以使用 get set 方法或Jython的自动bean属性来设置以下属性:
- comment
- domain
- maxAge
- name
- path
- secure
- value
- version

以下是一个使用Cookie对象的自动bean属性创建和读取Web表单中定义的Cookies的示例:

# File: cookies.py 
from javax import servlet 
from javax.servlet import http 
class cookies(http.HttpServlet): 
    def doGet(self, req, res): 
        res.setContentType("text/html") 
        out = res.getOutputStream() 
        print >>out, "<html><head><title>Cookies with Jython</title></head>" 
        print >>out, """ 
            <body>\n<H2>Cookie list:</H2> 
            Remember, cookies must be enabled in your browser.<br><br>""" 
        # Here's the list of Cookies 
        for c in req.cookies: 
            print >>out, """ 
                <b>Cookie Name</b>= %s &nbsp; 
                <b>Value</b>= %s<br><br>""" % (c.name,c.value) 
        print >>out, """<br><br><br> 
            <HR><P>Use this form to add a new cookie</P> 
            <form action="cookies.py" method="POST"> 
            <P>Name:<br><INPUT type="text" name="name" size="30"></P> 
            <P>Value:<br><INPUT type="text" name="value" size="30"></P> 
            <P>Use the MaxAge field to set the cookie's time-to-expire. 
               A value of "0" deletes the cookie immediately,, a value of 
               "-1" saves the cookie until the browser exits,, and 
               any other integer represents seconds until it expires 
               (i.e.- using "10" would expire 10 seconds after being set)).</P> 
            <P>MaxAge:<br><INPUT type="text" name="maxAge" size="30"></P> 
            <INPUT type="submit" name="button" value="submit"> 
            \n</body> 
            \n</html> 
            """ 
    def doPost(self, req, res): 
        res.setContentType("text/html"); 
        out = res.getWriter() 
        name = req.getParameterValues("name")[0] 
        value = req.getParameterValues("value")[0] 
        maxAge = req.getParameterValues("maxAge")[0] 
        if name: 
            newCookie = http.Cookie(name, value) 
            newCookie.maxAge = int(maxAge or -1) 
            newCookie.comment = "Jython test cookie" 
            res.addCookie(newCookie) 
            print >>out, """ 
                <html><body>Cookie set successfully\n\n 
                <P>click <a href="cookies.py">here</a> 
                to view the new cookie.</P> 
                <P>If cookies are enabled in your 
                browser that is.</P> 
                \n</body> 
                \n</html>""" 
        else: 
            print >>out, """ 
                <html>\n<body> 
                Cookie not set 
                <P>No cookie "Name" provided<</P> 
                <P>click <a href="cookies">here</a> 
                to try again</P> 
                \n</body> 
                /n</html>""" 

测试这个jylet时,先确保浏览器允许使用Cookies,将 cookies.py 文件放在 %TOMCAT_HOME%\webapps\jython 目录中,在浏览器中访问 http://localhost:8080/jython/cookies.py ,首次访问时应只看到标题和表单。在表单中输入信息(如名称为“Jython Cookie”,值为“My first Jython cookie”),点击提交按钮( maxAge 可选),应看到添加Cookie成功的确认信息。返回 doGet 方法,查看Cookie列表以确认Cookie是否真正添加。

9. Sessions

Cookies是与客户端创建会话的最常见方式,会话用于通过一系列请求跟踪客户端信息。虽然前面的Cookie示例可以用于存储会话ID,但Java的 HttpSession 类使会话跟踪更加容易。

要创建会话,使用 HttpRequest getSession() 方法,该方法返回一个 HttpSession 实例。 HttpSession 是会话管理子系统更复杂行为的简单接口,在Jython中使用 HttpSession 对象与Java的区别仅在于语法和对象的 get* 方法的自动bean属性。

以下是一个会话管理示例:

# File: session.py 
from javax import servlet 
from javax.servlet import http 
class session(http.HttpServlet, servlet.RequestDispatcher): 
    def doGet(self, req, res): 
        sess = req.session 
        res.contentType = "text/html" 
        out = res.getWriter() 
        name = req.getParameterValues("name") 
        value = req.getParameterValues("value") 
        if name and value: 
            sess.putValue(name[0], value[0]) 
        print >>out, (""" 
            <html> 
            <body> 
            <H3>Session Test</H3> 
            Created at %s<br> 
            Last Accessed = %s<br> 
            <u>Values:</u>""" % 
            (sess.creationTime, sess.maxInactiveInterval) 
        ) 
        print >>out, "<ul>" 
        for key in sess.getValueNames(): 
            print >>out, "<li>%s: %s</li>" % (key, sess.getValue(key)) 
        print >>out, "</ul>" 
        print >>out, """ 
            <HR><P>Use this form to add a new values to the session</P> 
            <form action="session.py" method="GET"> 
            <P>Name:<br><INPUT type="text" name="name" size="30"></P> 
            <P>Value:<br><INPUT type="text" name="value" size="30"></P> 
            <INPUT type="submit" name="button" value="submit"> 
            </body> 
            </html> 
            """ 

session.py 文件保存到 %TOMCAT_HOME%\webapps\jython 目录中,在浏览器中访问 http://localhost:8080/jython/session.py ,使用网页表单向会话中添加一些变量以确认其正常工作,甚至可以尝试禁用Cookies,查看URL重写如何工作。

10. 数据库和Servlet

在jylet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用没有区别,但管理数据库连接和其他资源是一个主要问题,因为许多Web应用包含大量使用数据库内容的jylets。在每个jylet中创建数据库连接会迅速消耗数据库资源,而为每个请求创建和关闭数据库连接的开销又不可接受。

管理数据库资源有多种选择,主要有两种方法:每个jylet使用一个连接或连接池。每个jylet使用一个连接是一种流行但资源密集的方法,该方法在jylet的 init 方法中建立数据库连接,仅在jylet卸载时关闭该连接,这样在响应客户端请求时消除了连接开销,但只有在所需连接数量在资源限制范围内时才合理,大多数情况需要更谨慎的资源管理。

以下是一个在 init 方法中获取数据库连接和游标对象,并在 destroy 方法中关闭它们的jylet示例:

# file: DBDisplay.py 
from javax.servlet import http 
from com.ziclix.python.sql import zxJDBC 
class DBDisplay(http.HttpServlet): 
    def init(self, cnfg): 
        #define the JDBC url 
        url = "jdbc:mysql://192.168.1.77/products" 
        usr = "productsUser"     # replace with real user name 
        passwd = "secret"        # replace with real password 
        driver = "org.gjt.mm.mysql.Driver" 
        #connect to the database and get cursor object 
        self.db = zxJDBC.connect(url, usr, passwd, driver) 
        self.c = self.db.cursor() 
    def doGet(self, req, res): 
        res.setContentType("text/html") 
        out = res.getWriter() 
        print >>out, """ 
            <html> 
            <head> 
              <title>Jylet Database Connection</title> 
            </head> 
            <body> 
            <table align="center"> 
              <tr> 
                <td><b>ID</b></td> 
                <td><b>Title</b></td> 
                <td><b>Description</b></td> 
                <td><b>Price</b></td> 
                </tr>""" 
        self.c.execute("select code, name, description, price from products") 
        for row in self.c.fetchall(): 
            print >>out, """ 
                <tr> 
                  <td>%s</td> 
                  <td>%s</td> 
                  <td>%s</td> 
                  <td>%s</td>""" % row 
        print >>out, """ 
            </table> 
            </body> 
            </html>""" 
    def destroy(self): 
        self.c.close() 
        self.db.close() 

这个示例使用了 zxJDBC 包和MySQL数据库,因此必须将MySQL和 zxJDBC 所需的类放在上下文的 lib 目录中,如 mm_mysql-2_0_4-bin.jar 文件和 zxJDBC.jar 文件应放在 %TOMCAT_HOME%\webapps\jython\WEB-INF\lib 目录中,添加jar文件后重启Tomcat以确保其检测到新的jar文件。

示例假设存在一个名为 products 的数据库,其中包含 products 表,该表至少有 code name description price 字段。创建数据库和表的SQL语句如下:

create database products 
CREATE TABLE products (
  primarykey int(11) NOT NULL auto_increment, 
  code varchar(55) default NULL, 
  name varchar(255) default NULL, 
  description text, 
  price float(5,2) default NULL, 
  PRIMARY KEY (primarykey) 
) TYPE=MyISAM; 

创建数据库和表,为每个字段创建一些任意值,将 DBDisplay.py 文件放在上下文的根目录中,在浏览器中访问 http://localhost:8080/jython/DBDisplay.py ,应能看到数据库数据。

若Web应用开始使用过多连接,可考虑使用连接池。连接池允许谨慎管理资源并消除连接开销,它维护一定数量的活动数据库连接,jylets在回复客户端请求时借用这些连接,完成后归还到池中,这样可以创建可预测的、静态数量的连接,也可以使用语句池,使语句具有相同的优势。一个流行、免费、经过测试且文档完善的连接池工具是PoolMan(http://www.codestudio.com/),其他Java连接池包也存在,且都应能与Jython无缝配合使用。

11. JSP

Java Server Pages(JSP)是Sun倡导的作为Servlet补充的模板系统。JSP文件包含网页的标记代码和文本,同时包含指定动态内容的特殊标签。目前,Tomcat仅在JSP中实现Java语言,那么如何在JSP中使用Jython呢?目前的答案是不能直接使用Jython,不能在代码标签( <% code %> )中使用Jython语言,但可以使用jythonc编译的类、嵌入PythonInterpreter或创建特定于Jython的自定义标签库。

11.1 jythonc编译的类和JSP

在JSP中使用jythonc编译的类需要创建一个与Java兼容的Jython类,该类的名称与模块名称相同,继承自Java类,并且为每个非从超类派生的方法包含 @sig 字符串。将jythonc生成的类文件放在上下文的 classes 目录中,JSP页面就可以像使用任何原生Java类一样使用这些类,当然,这也要求 jython.jar 文件放在上下文的 lib 目录中。

JSP文件可以通过两种方式使用jythonc编译的类:在脚本片段中使用或作为bean使用。若作为bean使用,Jython类必须符合bean约定,为每个读写属性包含 getProperty setProperty 方法;而脚本片段可以使用任何类。无论作为bean还是在脚本片段中使用,都必须先将jythonc编译的类加载到适当的作用域中。导入非bean类使用页面导入指令:

<%@ page import="fully.qualified.path.to.class" %> 

对于bean,使用 jsp:useBean 标签加载bean:

<jsp:useBean name="beanName" 
             class="fully.qualified path.to.class" 
             scope="scope(page or session)"> 

使用非bean类时,在脚本片段标签( <% %> )或表达式标签( <%= %> )中包含使用该类的Java代码。例如,若导入了假设的类 ProductListing ,可以在JSP页面中这样使用:

<%@ page import="ProductListing" %> 
<html> 
<body> 
<!--The next line begins the scriptlet --> 
<% ProductListing pl = new ProductListing(); %> 
<table> 
  <tr> 
    <td><%= pl.productOne %></td> 
    <td><%= pl.productTwo %></td> 
  </tr> 
</table> 

脚本片段通常不被推荐,因为它们会使JSP页面复杂化,首选的实现方式是使用jythonc编译的bean以及JSP的 useBean setProperty getProperty 标签。以下是一个简单的用Jython编写的bean示例:

# file: NameHandler.py 
import java 
class NameHandler(java.lang.Object): 
    def __init__(self): 
        self.name = "Fred" 
    def getUsername(self): 
        "@sig public String getname()" 
        return self.name 
    def setUsername(self, name): 
        "@sig public void setname(java.lang.String name)" 
        self.name = name 

nameHandler.py 文件放在 %TOMCAT_HOME%\webapps\jython\WEB-INF\classes 目录中,在该目录下使用以下命令编译:

jythonc -w . Namehandler.py 

以下是一个使用该bean的简单JSP页面示例:

<!--file: name.jsp --> 
<%@ page contentType="text/html"

### Jython在服务器端Web编程中的应用

##### 11.2 嵌入PythonInterpreter在JSP中
若想在JSP脚本片段中使用Jython代码,可以通过`PythonInterpreter`实例间接实现。这需要使用导入指令导入`org.python.util.PythonInterpreter`。以下是一个简单的JSP页面示例,展示了如何使用`PythonInterpreter`对象在JSP页面中包含Jython代码:
```jsp
<!--name: interp.jsp--> 
<%@ page contentType="text/html" %> 
<%@ page import="org.python.util.PythonInterpreter" %> 
<% PythonInterpreter interp = new PythonInterpreter(); 
   interp.set("out", out); %> 
<html> 
<body bgcolor="white"> 
<% interp.exec("out.println('Hello from JSP and the Jython interpreter.')"); %> 
</body> 
</html> 

要使用 interp.jsp 文件,需确保 jython.jar 文件在上下文的 lib 目录中,并将 interp.jsp 文件放在上下文的根目录( %TOMCAT_HOME%\webapps\jython )中。在浏览器中访问 http://localhost:8080/jython/interp.jsp ,应能看到Jython解释器输出的简单消息。

需要注意的是,很多人认为脚本片段不是好的做法,在脚本片段中从Java使用Jython会增加复杂度,显然不太可取。有更好的方法来创建动态内容,如使用bean类和标签库。

11.3 Jython标签库

标签库是可以在JSP页面中使用的自定义标签库。可以使用 jythonc 将Jython标签库模块编译成Java类来创建Jython标签库。Jython标签库模块必须符合一定的限制才能像Java类一样透明地工作。模块必须包含一个与模块名称相同(不带 .py 扩展名)的类,该类必须继承自 javax.servlet.jsp.tagext.Tag 接口或实现该接口的Java类,并且编译后的类文件必须能够访问 org.python.* 包和类以及Jython库模块(如果标签库导入了这些模块)。

为了使标签库能够访问所有必需的类和库,可以使用 jythonc --all 选项进行编译,就像之前编译Servlet那样。但这样做可能会导致重复编译Jython核心文件,造成资源浪费。更好的方法是将 jython.jar 文件放在上下文的 lib 目录中,在上下文中为Jython的模块建立一个 lib 目录(推荐为 {context}\WEB-INF\lib\Lib ),然后编译Jython标签库模块而不包含依赖项。

以下是一个简单的Jython标签库模块示例:

# file: JythonTag.py 
from javax.servlet.jsp import tagext 
class JythonTag(tagext.Tag): 
    def __init__(self): 
        self.context = None 
        self.paren = None 
    def doStartTag(self): 
        return tagext.Tag.SKIP_BODY 
    def doEndTag(self): 
        out = self.context.out 
        print >>out, "Message from a taglib" 
        return tagext.Tag.EVAL_PAGE 
    def release(self): 
        pass 
    def setPageContext(self, context): 
        self.context = context 
    def setParent(self, parent): 
        self.paren = parent 
    def getParent(self): 
        return self.paren 

在这个示例中,需要特别注意保存页面上下文和父标签信息的实例变量( self.context self.paren )。这些变量不能被标识为 self.pageContext self.parent ,或者所有对这些变量的访问都必须通过实例的 __dict__ 进行。因为 Tag 接口要求实现 setPageContext() setParent() getParent() 方法,Jython会为这些方法的相关属性名称创建自动bean属性,这可能会导致循环引用和 StackOverflowExceptions

安装标签库所需的类时,首先要确保 jython.jar 文件在上下文的 lib 目录中,然后使用以下命令编译 JythonTag.py 文件:

jythonc -w %TOMCAT_HOME%\webapps\jython\WEB-INF\classes JythonTag.py 

编译后,上下文的 classes 目录中会创建 JythonTag.class JythonTag$_PyInner.class 文件。但仅这些类文件还不足以使用标签库,在JSP页面中使用标签之前,还必须创建一个标签库描述文件。以下是一个适合描述上述标签的标签库描述文件示例:

<?xml version="1.0" encoding="ISO-8859-1" ?> 
<!DOCTYPE taglib 
  PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" 
  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> 
<taglib> 
    <tlibversion>1.0</tlibversion> 
    <jspversion>1.1</jspversion> 
    <shortname>JythonTag</shortname> 
    <info>A simple Jython tag library</info> 
    <tag> 
        <name>message</name> 
        <tagclass>JythonTag</tagclass> 
    </tag> 
</taglib> 

将上述标签库描述信息放在 %TOMCAT_HOME%\webapps\jython\WEB-INF\jython-taglibs.tld 文件中,JSP页面将使用该文件来识别页面中使用的标签。

标签库描述符标识了标签库的特征,如版本号、适用的JSP版本等,还指定了一个用于引用该标签库的名称( <shortname> )。其余元素定义了在上述示例中创建的特定标签库。 <tag> 元素标识了完全限定的类名,并为该类分配了一个名称。

使用标签库时,JSP页面必须包含一个指令,指定标签库描述符的路径并为该库分配一个简单的名称。所有后续对该库中标签的引用都将以该指令中分配的名称开头。以下是一个JSP指令示例,用于引用上述创建的标签库:

<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %> 

由于 .tld 文件为示例标签指定了名称 message ,声明该标签库后,可以使用以下方式使用 message 标签:

<jython:message/> 

以下是一个使用上述标签和标签库描述符的JSP文件示例:

<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %> 
<html> 
<body> 
<jython:message /> 
</body> 
</html> 

test.jsp 文件保存为 %TOMCAT_HOME%\webapps\jython\test.jsp ,在浏览器中访问 http://localhost:8080/jython/test.jsp ,应能看到自定义标签的消息。

使用编译后的Jython模块实现标签库时,会再次涉及Jython的主目录和 lib 目录的问题。 jythonc 编译的标签库不知道 python.home 在哪里或在哪里找到库模块,除非在 PySystemState 中建立了这些信息。可以在 TOMCAT_OPTS 环境变量中设置 python.home 属性,也可以利用 PyServlet 类来建立这些系统状态信息。如果上下文加载了 PyServlet 类,那么 python.home sys.path 信息可能已经为需要的标签库设置正确。在默认的 PyServlet 设置下, python.home %TOMCAT_HOME%\webapps\jython\WEB-INF\lib ,因此Jython的 lib 目录是 %TOMCAT_HOME%\webapps\jython\WEB-INF\lib\Lib 。加载 PyServlet 后,标签库可以从同一个 lib 目录加载Jython模块。

11.4 BSF

IBM的Bean Scripting Framework(BSF)是一个实现各种脚本语言的Java工具,Jython是目前BSF支持的语言之一。Apache Jakarta项目包含一个名为 taglibs 的子项目,它是一个广泛的Java标签库集合,其中有趣的是BSF标签库。BSF标签库与BSF的jar文件结合使用,可以让你快速将Jython脚本片段和表达式直接插入JSP页面。

目前,BSF发行版需要进行一些调整才能与Jython配合使用。由于BSF发行版最终会包含这些小更改,这里不再详细说明,但这意味着你需要确认你的BSF版本包含这些更改。为了方便起见,相关网站会提供正确的 bsf.jar 文件下载链接。下载 bsf.jar 文件后,将其放在上下文的 lib 目录( %TOMCAT_HOME%\webapps\jython\WEB-INF\lib )中。相关网站还会提供BSF标签库和 bsf.tld 文件的下载链接,将包含BSF标签库的jar文件放在上下文的 lib 目录中,将 bsf.tld 文件放在上下文的 WEB-INF 目录( %TOMCAT_HOME%\webapps\jython\WEB-INF\bsf.tld )中。

安装这些文件后,就可以在JSP文件中使用Jython脚本片段。首先,需要包含一个指令来识别BSF标签库:

<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %> 

然后,可以使用 bsf:scriptlet 标签,在标签体中包含Jython代码,示例如下:

<bsf:scriptlet language="jython"> 
import random 
print >> out, random.randint(1, 100) 
</bsf:scriptlet> 

脚本片段在解释器中自动设置了一些JSP对象,以下是这些对象及其Java类名的列表:
| 对象 | Java类名 |
| — | — |
| request | javax.servlet.http.HttpServletRequest |
| response | javax.servlet.http.HttpServletResponse |
| pageContext | javax.servlet.jsp.PageContext |
| application | javax.servlet.ServletContext |
| out | javax.servlet.jsp.JspWriter |
| config | javax.servlet.ServletConfig |
| page | java.lang.Object |
| exception | java.lang.Exception |
| session | javax.servlet.http.HttpSession |

大多数对象在Servlet中很常见,BSF脚本片段标签允许在JSP页面中使用这些对象。以下是一个使用 bsf:scriptlet 标签在JSP文件中创建时间戳的示例:

<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %> 
<html> 
<body> 
<center><H2>BSF scriptlets</H2></center> 
<b>client info:</b><br> 
<bsf:scriptlet language="jython"> 
for x in request.headerNames: 
    print >>out, "%s: &nbsp;%s<br>\n" % (x, request.getHeader(x)) 
</bsf:scriptlet> 
<br><br> 
<b>Time of request:</b><br> 
<bsf:scriptlet language="jython"> 
import time 
print >>out, time.ctime(time.time()) 
</bsf:scriptlet> 
</body> 
</html> 

将上述示例保存为 %TOMCAT_HOME%\webapps\jython\scriptlets.jsp 文件,在浏览器中访问 http://localhost:8080/jython/scriptlets.jsp ,应能看到所有客户端信息以及访问时间。

总结

综上所述,Jython在服务器端Web编程中具有广泛的应用场景和多种实现方式。从简单的Servlet编写到复杂的JSP页面动态内容生成,从Cookie和会话管理到数据库资源的有效利用,Jython都能发挥重要作用。通过合理选择和运用不同的技术手段,如使用 PyServlet 避免编译步骤、利用连接池管理数据库连接、创建自定义标签库等,可以提高开发效率、增强代码的灵活性和可维护性,同时充分利用Java生态系统的丰富资源。在实际开发中,开发者可以根据具体需求和项目特点,灵活组合这些技术,构建出高效、稳定的服务器端Web应用。

以下是一个简单的mermaid流程图,展示了Jython Servlet开发的基本流程:

graph LR
    A[选择Servlet容器] --> B[定义Servlet类]
    B --> C{选择编译方式}
    C -->|jythonc编译| D[编译Jython代码]
    C -->|PyServlet映射| E[配置web.xml]
    D --> F[部署类文件]
    E --> F
    F --> G[启动Tomcat服务器]
    G --> H[测试Servlet]

这个流程图概括了从选择Servlet容器开始,到最终测试Servlet的整个开发过程,帮助开发者更好地理解和掌握Jython Servlet开发的关键步骤。

【论文复现】一种基于价格弹性矩阵的居民峰谷分时电价激励策略【需求响应】(Matlab代码实现)内容概要:本文介绍了一种基于价格弹性矩阵的居民峰谷分时电价激励策略,旨在通过需求响应机制优化电力系统的负荷分布。该研究利用Matlab进行代码实现,构建了居民用电行为与电价变动之间的价格弹性模型,通过分析不同时间段电价调整对用户用电习惯的影响,设计合理的峰谷电价方案,引导用户错峰用电,从而实现电网负荷的削峰填谷,提升电力系统运行效率与稳定性。文中详细阐述了价格弹性矩阵的构建方法、优化目标函数的设计以及求解算法的实现过程,并通过仿真验证了所提策略的有效性。; 适合人群:具备一定电力系统基础知识和Matlab编程能力,从事需求响应、电价机制研究或智能电网优化等相关领域的科研人员及研究生。; 使用场景及目标:①研究居民用电行为对电价变化的响应特性;②设计并仿真基于价格弹性矩阵的峰谷分时电价激励策略;③实现需求响应下的电力负荷优化调度;④为电力公司制定科学合理的电价政策提供理论支持和技术工具。; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,深入理解价格弹性建模与优化求解过程,同时可参考文中方法拓展至其他需求响应场景,如工业用户、商业楼宇等,进一步提升研究的广度与深度。
针对TC275微控制器平台,基于AUTOSAR标准的引导加载程序实现方案 本方案详细阐述了一种专为英飞凌TC275系列微控制器设计的引导加载系统。该系统严格遵循汽车开放系统架构(AUTOSAR)规范进行开发,旨在实现可靠的应用程序刷写与启动管理功能。 核心设计严格遵循AUTOSAR分层软件架构。基础软件模块(BSW)的配置与管理完全符合标准要求,确保了与不同AUTOSAR兼容工具链及软件组件的无缝集成。引导加载程序本身作为独立的软件实体,实现了与上层应用软件的完全解耦,其功能涵盖启动阶段的硬件初始化、完整性校验、程序跳转逻辑以及通过指定通信接口(如CAN或以太网)接收和验证新软件数据包。 在具体实现层面,工程代码重点处理了TC275芯片特有的多核架构与内存映射机制。代码包含了对所有必要外设驱动(如Flash存储器驱动、通信控制器驱动)的初始化与抽象层封装,并设计了严谨的故障安全机制与回滚策略,以确保在软件更新过程中出现意外中断时,系统能够恢复到已知的稳定状态。整个引导流程的设计充分考虑了时序确定性、资源占用优化以及功能安全相关需求,为汽车电子控制单元的固件维护与升级提供了符合行业标准的底层支持。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值