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

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

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

在服务器端Web编程领域,Jython凭借其独特的优势逐渐崭露头角。本文将深入探讨Jython在服务器端Web编程中的应用,包括Jython Servlet容器、简单Servlet类的定义、GenericServlet和HttpServlet的使用、Cookies和Sessions的管理、数据库连接以及与JSP的集成等方面。

1. Jython与Web开发

Jython在Web开发中的定位与Java紧密相关,Java能应用的地方,Jython通常也能适用。特别是在需要快速开发和提高灵活性的Java应用场景中,Jython表现出色。服务器端Java Web编程主要通过Servlets和Java Server Pages (JSP) 实现,因此Jython的主要应用也是Servlets和JSP。不过,Java的企业级包(j2ee)在Web应用开发中也占据重要地位,Jython在这些技术中同样有效。

然而,Jython并非适用于所有场景。例如,CPython是实现CGI脚本的流行语言,但Jython并不适合。在CGI中,Web服务器接收请求后启动一个进程进行响应,响应完成后关闭该子进程。虽然可以用Jython以这种方式工作,但由于JVM的启动时间较长,这并不是一个好的选择。而Servlets和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规范。预计后续会发布支持2.3 Servlet和1.2 JSP规范的Tomcat 4.0。所有Jython Servlets都在Tomcat 3.2.3上进行了测试,根据Sun的“一次编写,到处运行”原则,这些示例应适用于任何兼容Servlet 2.2/JSP 1.1的容器。Tomcat发行版包含了本文所需的Servlet和JSP类,无需额外下载。
- Apache JServ :是为Apache创建的Servlet(版本2.0)引擎,应用广泛。如果当前开发已经使用了JServ,那么它是使用Jython Servlets的一个不错选择。可从http://java.apache.org/获取相关信息,JServ需要单独从http://java.sun.com/products/servlet/index.html下载Java Servlet Development Kit 2.0,Java Server Pages需要从http://java.apache.org/jserv/获取外部模块。
- Jigsaw :是W3C的实验性Web服务器。虽然名为“实验性”,但实际上它比这个标签所暗示的更成熟。它是一个完全符合HTTP/1.1的Web服务器和缓存代理服务器,完全用Java编写,支持Servlet 2.2规范和Java Server Pages 1.1。可从http://www.w3.org/Jigsaw/获取,包含所需的Servlet和JSP文件。
- Jetty :是一个紧凑高效的Java Web服务器,支持Servlet 2.2和JSP 1.1规范,支持HTTP 1.1,包含SSL支持,并且可以轻松与EJB服务器(如JBoss)集成。可从http://jetty.mortbay.com/获取。

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

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使用了Servlet API中的GenericServlet类,该类的service方法是抽象的,因此必须在子类中实现。通过ServletResponse对象的PrintWriter将输出发送给客户端。

3.2 简单的Jython Servlet

以下是一个与上述Java 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同样继承自GenericServlet类,并实现了service方法。与Java Servlet相比,Jython Servlet在语法上有一些差异,例如类定义后的括号用于指定超类,省略了throws语句,service方法中需要显式的self参数,并且没有分号和显式的类型声明。此外,Jython的导入语句也有所不同。

4. 测试Java和Jython Servlets

测试上述Servlets需要安装Tomcat,并将jython.jar文件包含在Tomcat中。以下是具体步骤:

4.1 安装Tomcat
  1. 从http://jakarta.apache.org下载Tomcat,建议下载jakarta-tomcat-3.2.3版本,根据平台选择合适的zip或tar.gz文件。
  2. 将下载的文件解压到有足够权限的目录。例如,在Windows上解压到C:\jakarta-tomcat-3.2.3,在*nix系统上解压到/usr/local/jakarta-tomcat3.2.3。设置环境变量TOMCAT_HOME指向Tomcat的主目录:
    • 在Windows上: set TOMCAT_HOME=c:\jakarta-tomcat-3.2.3
    • 在*nix系统上: export TOMCAT_HOME=/usr/local/jakarta-tomcat-3.2.3
  3. 设置环境变量JAVA_HOME指向JDK的根目录。例如,使用JDK1.3.1时:
    • 在Windows上: set JAVA_HOME=c:\jdk1.3.1
    • 在*nix系统上: export JAVA_HOME=/usr/java/jdk1.3.1
  4. 启动Tomcat:
    • 在Windows上: %TOMCAT_HOME%\bin\startup.bat
    • 在*nix系统上: $TOMCAT_HOME/bin/startup.sh
  5. 启动过程中,注意查看以下信息: date time - PoolTcpConnector: Starting HttpConnectionHandler on 8080 ,这表示Tomcat正在监听8080端口。
  6. 停止Tomcat:
    • 在Windows上: %TOMCAT_HOME%\bin\shutdown.bat
    • 在*nix系统上: $TOMCAT_HOME/bin/shutdown.sh

Tomcat的Servlet 2.2规范指定了Web应用的目录层次结构,从%TOMCAT_HOME%\webapps开始。本文示例使用名为jython的上下文,其目录结构如下:
- %TOMCAT_HOME%\webapps\
- %TOMCAT_HOME%\webapps\jython(上下文的根目录)
- %TOMCAT_HOME%\webapps\jython\WEB-INF
- %TOMCAT_HOME%\webapps\jython\WEB-INF\classes(Servlet类目录)
- %TOMCAT_HOME%\webapps\jython\WEB-INF\lib(库归档目录)

在继续示例之前,需要创建这些目录。重启Tomcat后,应该会看到以下信息确认新上下文已加载: date time - ContextManager: Adding context Ctx( /jython )

4.2 安装Java Servlet

将JavaServlet.java文件放置在%TOMCAT_HOME%\webapps\jython\WEB-INF\classes目录中。由于该Servlet没有指定包,因此应放在classes目录的根目录下。在该目录中,使用以下命令编译JavaServlet.java文件:

javac -classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java 

编译完成后,启动Tomcat服务器,然后在浏览器中访问http://localhost:8080/jython/servlet/JavaServlet,应该会看到消息“This is a Java Servlet.”

4.3 安装Jython Servlet

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

jythonc -w . JythonServlet.py 

使用 -w 开关指定当前工作目录,避免将生成的类文件从jpywork目录复制出来。编译后,classes目录中应该有两个类文件,如JythonServlet.java、JythonServlet.class和JythonServlet$_PyInner.class。编译过程中,需要注意查看以下信息:

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

如果没有看到这些信息,说明编译可能存在问题,需要检查CLASSPATH和设置选项,重新编译直到看到这些信息。
2. 将jython.jar文件添加到Web应用 :有三种方式将jython.jar文件添加到Tomcat的类路径中:
- 推荐方式 :将jython.jar文件放在上下文的lib目录中,即%TOMCAT_HOME%\webapps\jython\WEB-INF\lib。这样可以使Web应用自包含,便于归档、打包和在其他服务器上安装。
- 不推荐方式 :将jython.jar文件放在Tomcat的lib目录(%TOMCAT_HOME%\lib)中。虽然该目录中的Jar文件会自动添加到类路径中,但会使Web应用不再自包含,并且无法自动访问Jython的lib目录。
- 合理方式 :将jython.jar文件留在Jython的安装目录中,但在运行Tomcat之前将其添加到类路径中。这种方式可以消除重复的jython.jar文件,并且可以访问注册表文件和Jython的lib目录,但自包含的上下文仍然是首选。
3. 使Jython的lib目录对Servlets可用 :有三种方式使Jython的lib目录中的模块对Servlets可用:
- 设置python.home属性 :可以通过设置TOMCAT_OPTS环境变量来设置python.home属性。建议在上下文的WEB-INF目录中创建一个额外的目录,例如jylib。创建目录%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib和%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib\Lib,并将所需的模块放在该目录中。然后设置TOMCAT_OPTS:
- 在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
-
冻结所需模块 *:使用jythonc的 –deep 选项编译所有所需模块,使python.home和Jython的lib目录不再重要。在Jython上下文的classes目录中,使用以下命令冻结JythonServlet.py文件:

jythonc -w . --deep JythonServlet.py 

编译后,JythonServlet和所有所需模块的类文件将位于上下文的classes目录中,实现了自包含的Web应用。但需要注意,以这种方式编译其他Jython Servlets可能会覆盖之前编译的模块,因此在更新模块时需要小心。编译完成后,生成的 .java文件可以删除。
-
每个Servlet显式地将模块位置添加到sys.path *:这种方式通常需要显式的、依赖于机器的路径,从而限制了跨平台的可移植性。例如:

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

编译完成并设置好相关环境后,在浏览器中访问http://localhost:8080/jython/servlet/jythonServlet,应该会看到消息“This is a Servlet of the Jython variety.”如果没有看到该消息,可能会出现以下错误:
- 如果编译JythonServlet时classpath中没有Servlet.jar文件,可能会看到ClassCastException。
- 如果文件名和类名不同(即使只是大小写不同),也会看到ClassCastException。
- 如果jythonc编译生成的类文件之一不在上下文的classes目录中,会看到AttributeError。
- 如果Jython的模块不可用,会看到ImportError。

5. 关于GenericServlet

Listing 12.2中的Jython Servlet继承自javax.Servlet.GenericServlet,这是一个不特定于任何协议的Servlet类。在Web编程中,HttpServlet更为常见,因为它特定于HTTP协议,但GenericServlet是HttpServlet的超类,了解其方法很重要。以下是GenericServlet的一些常用方法及其在Jython子类中的使用示例:
| Java Signature | Usage in Jython Subclass |
| — | — |
| METHODS YOU SUBCLASS | |
| 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” |
| METHODS YOU USE WITH THE SELF. METHOD SYNTAX | |
| 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 |

以下是一个使用GenericServlet的HitCounter 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后,计数应该会继续上一次的数值。

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

6. HttpServlet

GenericServlet适用于任何类似聊天的协议,但Web开发主要涉及HTTP协议。因此,在进行HTTP通信时,最好继承javax.Servlet.http.HttpServlet类,它是GenericServlet的子类,因此在继承HttpServlet时可以使用GenericServlet的init、service和destroy方法。

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

以下是HttpServlet类的一些方法及其在Jython中的使用示例:
| Java Signature | Usage in Jython Subclass |
| — | — |
| 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映射时非常有用。

以下是一个使用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,应该会看到一个类似于Figure 12.1的浏览器窗口。在这个示例中,GET操作的参数为None,但可以通过在URL末尾添加参数(如http://localhost:8080/servlet/get_post?variable1=1&variable2=2)来测试doGet方法中的其他参数。由于页面底部的提交按钮是一个使用POST方法的表单,点击该按钮将执行同一Servlet的doPost方法,结果应与Figure 12.2所示一致。

7. HttpServletRequest和HttpServletResponse

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

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

HttpServletResponse对象用于将mime编码的流发送回客户端,它定义了一些在通用ServletResponse对象中不存在的特定于HTTP的方法。以下是HttpServletResponse对象的一些方法及其对应的Bean属性名称:
| Method and Property | Description |
| — | — |
| 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) | 通过在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)方法一起使用。以下是一些常见的HTTP响应状态码:
| Error Code | Status |
| — | — |
| 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 |

8. 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文件中的方法进行响应。

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

为了避免命名混淆,这里将所有实现Servlet功能的代码统称为servlet,PyServlet特指org.python.util.PyServlet类。而PyServlet实际服务的Jython模块(匹配*.py模式的文件),为了便于区分,本文将其称为jylets。

8.1 安装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文件,并且无需在该上下文中定义所有属性,因为默认值已经存在。

以下是一个Jython上下文的示例部署描述符:

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

这个部署描述符定义了Servlet的名称为PyServlet,类为org.python.util.PyServlet。该类的完整包类层次结构唯一标识了Servlet类,实际的类位于jython.jar文件中,该文件应位于上下文的lib目录中。

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

<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上下文,根据平台的不同,该值可能是%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路径,使得使用默认值的上下文具有跨平台的独立性。

8.2 测试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模块的目录。完成这些步骤后,安装就完成了。

9. Cookies

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

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

现在就创建了一个名为book1的新Cookie,其值为“Lewis Carroll, Alice In Wonderland”。要将这个Cookie发送给客户端,需要使用HttpServletResponse的addCookie(cookie)方法:

res.addCookie(MyNewCookie) 

需要注意的是,添加Cookies应该在通过响应流发送其他内容之前进行。

每个Cookie实例都有一些方法可以设置其具体属性,也可以使用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"> 
            </body> 
            </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> 
                </body> 
                </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> 
                </body> 
                </html>""" 

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

10. Sessions

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

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

以下是一个使用简单的req.session 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> 
            """ 

这个示例允许Cookie或URL包含会话值。由于Cookies可能存储会话值,因此在向输出流发送其他数据之前,应该使用req.session Bean属性或req.getSession()方法。如果客户端禁用了Cookies,会话对象仍然可以工作,因为所有URL都会使用encodeUrl方法进行重写。实际上,只需要重写一个URL,即表单的action属性。如果有其他URL,也必须通过res.encodeUrl()方法进行处理,以支持无Cookies的会话。

一旦有了HttpSession实例,通过会话传递数据就只需要使用键值对,使用putValue(key, value)方法存储数据,使用getValue(key)方法获取数据。

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

11. 数据库与Servlets

在jylet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用程序没有区别。然而,管理连接和其他数据库资源是一个主要问题,因为许多Web应用程序包含多个使用数据库内容的jylet。在每个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() 

这个示例假设存在一个名为products的数据库,其中包含products表,并且该表至少有code、name、description和price字段。要创建这样的数据库,可以使用以下SQL语句:

create database products 

要创建products表,可以使用以下SQL语句:

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应用程序开始使用过多的连接,可以考虑使用连接池。连接池允许进行谨慎的资源管理,并消除连接开销。连接池维护一定数量的活动数据库连接,jylet在响应客户端请求时借用这些连接,并在完成后将其返回给池。这样可以创建一个可预测的、静态的连接数量。也可以使用语句池,为语句提供相同的优势。一个流行的、免费的、经过测试且文档完善的连接池工具是PoolMan,可从http://www.codestudio.com/获取。其他Java连接池包也存在,并且都应该可以与Jython无缝协作。

12. JSP

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

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

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

<!--file: name.jsp --> 
<%@ page contentType="text/html" %> 
<jsp:useBean id="name" class="NameHandler" scope="session

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

##### 12.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解释器输出的简单消息。

需要注意的是,许多人认为在JSP中使用脚本片段不是好的做法,因为这会增加代码的复杂性。因此,使用Jython在Java脚本片段中编写代码显然是有问题的。更好的方法是使用Bean类和标签库来创建动态内容。

12.3 Jython标签库

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

为了使标签库能够访问所有必需的类和库,可以使用 jythonc --all 选项进行编译,就像之前在编译Servlet时所做的那样。这样会创建一个 jar 文件,然后将其放在上下文的 lib 目录中。但问题是,很可能许多资源都会使用Jython核心文件,因此反复使用 --core --deep --all 进行编译会造成不必要的冗余。更好的方法是将 jython.jar 文件包含在上下文的 lib 目录中,在上下文中为Jython的模块建立一个 lib 目录(建议使用 {context}\WEB-INF\lib\Lib ,具体解释见前面的“PyServlet”部分),然后在不依赖其他资源的情况下编译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 。由于 Tag 接口要求实现 setPageContext() setParent() getParent() 方法,Jython会为这些方法对应的属性名创建自动Bean属性。这可能会导致循环引用和 StackOverflowExceptions 。例如,在 setPageContext 方法中,如果直接将 context 赋值给 self.context ,由于 self.context 是自动Bean属性,实际上会调用 self.setPageContext(context) ,从而形成循环引用。这种情况在使用 jythonc 编译的类在Java框架中时需要特别关注。

要安装标签库所需的类,首先确保 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> 元素标识了完全限定的类名,并为该类分配了一个名称。由于 JythonTag 没有在包中,它本身就是一个完全限定的名称,并且这个类与名称 message 相关联。

在JythonTag编译完成并保存了标签库描述文件后,接下来的步骤是在JSP页面中使用这个标签库。JSP页面必须包含一个指令,指定标签库描述文件的路径,并为该标签库分配一个简单的名称。后续对这个标签库中标签的引用都将以这个分配的名称开头。对于前面创建的标签库,JSP指令如下所示:

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

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

<jython:message/> 

jython 部分来自JSP声明中分配的名称,而 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的主目录和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模块。

12.4 BSF

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

BSF发行版目前需要进行一些调整才能与Jython一起使用。由于BSF发行版最终会包含这些小的更改,这里不详细说明具体的调整内容。为了方便使用,与本书相关的网站( http://www.newriders.com/ )将提供一个链接,你可以从那里下载正确的 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文件中创建时间戳的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容器的集成,到简单Servlet类的定义和测试;从对GenericServlet和HttpServlet的深入理解和使用,到Cookies和Sessions的管理;从数据库连接和资源管理,到与JSP的多种集成方式,Jython都提供了丰富的解决方案。

在选择使用Jython进行Web开发时,需要根据具体的项目需求和场景,合理选择合适的技术和方法。例如,在需要快速开发和提高灵活性的场景中,Jython的动态特性和简洁的语法可以发挥很大的优势;而在对性能和资源管理要求较高的情况下,则需要谨慎选择数据库连接和管理方式,如使用连接池等技术。

同时,Jython与Java的紧密结合也为开发者提供了更多的选择和便利。通过使用jythonc编译的类、嵌入PythonInterpreter或创建自定义标签库等方式,可以在JSP中间接使用Jython,从而充分利用Jython的功能和Java的生态系统。

总之,Jython为服务器端Web编程带来了新的思路和方法,相信在未来的Web开发中,Jython将发挥越来越重要的作用。

流程图

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B{选择开发方式}:::decision
    B -->|Servlet| C(定义Servlet类):::process
    B -->|JSP| D(选择集成方式):::process
    C --> E(选择Servlet容器):::process
    E --> F(安装和配置容器):::process
    F --> G(编写和测试Servlet):::process
    D --> H{使用方式}:::decision
    H -->|jythonc编译类| I(创建Java兼容Jython类):::process
    H -->|嵌入PythonInterpreter| J(导入并使用):::process
    H -->|自定义标签库| K(创建标签库模块):::process
    I --> L(编译并部署):::process
    J --> M(编写JSP页面):::process
    K --> N(编译和配置标签库):::process
    L --> O(在JSP中使用):::process
    M --> O
    N --> O
    G --> P([结束]):::startend
    O --> P

这个流程图展示了使用Jython进行服务器端Web编程的主要流程。首先需要选择开发方式,是使用Servlet还是JSP。如果选择Servlet,需要定义Servlet类,选择合适的Servlet容器,进行安装和配置,最后编写和测试Servlet。如果选择JSP,需要进一步选择集成方式,包括使用jythonc编译的类、嵌入PythonInterpreter或创建自定义标签库。每种方式都有相应的步骤,最终都在JSP中使用,整个流程以结束标志结束。

内容概要:本文详细介绍了“秒杀商城”微服务架构的设计与实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现与配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪与Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试与监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值