43.2. Apusic Http Servlets 开发

43.2.1. Http Servlets 概述

本章节介绍了HTTP Servlet的基本概念,概述了如何在Apusic应用服务器上进行HTTP Servlet的开发。

43.2.1.1. 什么是Servlet

Servlet是基于Java技术的Web组件,用来扩展以请求/响应为模型的服务器的能力。Servlets可以响应任何类型请求,但我们通常使用Http Servlets来处理HTTP请求(request)和提供HTTP响应(response)。

Servlet继承层次

Apusic HTTP Servlets通常用来创建交互式的应用:以标准Web浏览器作为客户端表示,Apusic应用服务器处理服务器端的商业逻辑。Apusic HTTP Servlets可访问数据库、EJB 、消息API 、HTTP Session和其它Apusic应用服务器提供的其他功能。

Apusic服务器完全支持最新的Servlet2.5规范。HTTP Servlets是JavaEE标准的组成部分。

43.2.1.2. 使用Servlet的目的

  • 创建动态Web页面。使用HTML表单得到最终用户的输入,并提供HTML页面作为输出,响应用户的请求。例如在线购物车,金融服务和个性化内容等。

  • 生成如在线会议等协作系统。

  • 运行在Apusic应用服务器上的Servlets可以获得各种不同的API和系统服务的支持,如:

    • Session跟踪——使Web站点可以跨越多个页面跟踪用户的过程。这个功能支持Web站点提供类似于电子商务中的购物车。Apusic应用服务器在集群服务器之间提供Session迁移。

    • JDBC驱动程序——可以使用JDBC驱动程序提供的基本的数据库访问服务,同时也可以使用Apusic应用服务器提供的数据库连接池、服务器端的数据缓存和事务处理等多种服务。

    • 可对Servlets使用不同的安全类型,如使用SSL来提供安全通讯。

    • 可使用EJB来封装Sessions 、数据库中的数据和其他功能。

    • JMS——允许Servlet与其他Servlet和程序交换消息。

    • Servlets可使用标准JDK API 。

    • 可传递一个请求(request)到其他Servlet或其他资源。

  • 为任何其他J2EE兼容的Servlet引擎编写的Servlet可以轻松的部署到Apusic应用服务器上。

  • Servlets和JSP可以一起创建Web应用。

43.2.1.3. Servlet开发概述

  • HTTP Servlets程序员使用标准的Java API(javax.servlet.http)来创建交互式应用程序。

  • HTTP Servlets可以读取HTTP头信息,并向客户端浏览器发送HTML代码。

  • 在Apusic应用服务器中,Servlet作为web应用的一部分部署。Web应用是一个由诸如servlet类、JSP、静态Web页面、图像、安全描述等应用组件组成的。

43.2.1.4. Servlet与JavaEE

Servlet2.5规范作为JavaEE的组成部分,定义了Servlet API的实现和Servlet如何部署在企业应用中。在兼容服务器如Apusic应用服务器上部署Servlet ,是通过将组成企业应用的Servlet和其他资源打包到一个"Web应用"中来完成的。Web应用通过特定的目录结构来容纳资源和部署描述。Web应用可使用后缀是".war" 的压缩文件包部署。

43.2.1.5. Servlet API参考

Apusic应用服务器支持Servlet 2.5规范,可以从Sun Microsystems的网站获得这些文档:

43.2.2. Apusic Http Servlets变动及其升级

本章节主要介绍Apusic Http Servlets部分跟随规范变动和升级的内容

43.2.2.1. 从Servlet2.4到Servlet2.5的主要变动

43.2.2.1.1. 依赖于J2SE5.0

Servlet 2.5规范现在把J2SE 5.0(JDK 1.5)作为最低要求。这个变动保证了J2SE5.0中所有新的语言特性都在Servlet 2.5中可用,包括:泛型支持、自动包装、增强的迭代循环、新的enum枚举类型、静态导入、可变参数表、元数据注解等等

43.2.2.1.2. 支持注解(Annotation)

注解提供了一套机制,通过元数据来装饰Java代码结构(类、方法、域等等)。注解并不象代码一样执行,而是对代码进行标记,使代码处理机可以根据这些元数据信息作出相应的行为。以前的版本提供了一些不同的小技巧来对类或方法进行注解,例如使用Serializable接口来标记序列化类,或使用@deprecated的Javadoc注释来标记弃用的方法。新的元数据机制提供一套标准的方法来对代码进行注解,并提供了类库来创建自定义的注解类型。请参考第 43.2.4.2 节 “使用注解”

43.2.2.1.3. 更便利的web.xml的配置

Servlet 2.5中引入了一些对web.xml的小变动,使得部署配置更为方便。

43.2.2.1.3.1. servlet名称通配符

当编写<filter-mapping>元素时,现在可以在<servlet-name>中使用*号来代表多个servlet(当然也包括了多个jsp)。例如:

 <filter-mapping>
  <filter-name>Image Filter</filter-name>
  <servlet-name>*</servlet-name>  <!-- 新特性 -->
</filter-mapping> 

 <filter-mapping>
  <filter-name>Dispatch Filter</filter-name>
  <servlet-name>*</servlet-name>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping> 
43.2.2.1.3.2. 在servlet映射中使用多个url-pattern标签

现在一个<servlet-mapping>元素中可以包含多个<url-pattern>元素,例如:

<servlet-mapping>
  <servlet-name>color</servlet-name>
  <url-pattern>/color/*</url-pattern>
  <url-pattern>/colour/*</url-pattern>
</servlet-mapping> 

同样地,现在一个<filter-mapping>可以包含多个<url-pattern>与<servlet-name>元素,例如:

<filter-mapping>
  <filter-name>Multipe Mappings Filter</filter-name>
  <url-pattern>/foo/*</url-pattern>
  <servlet-name>Servlet1</servlet-name>
  <servlet-name>Servlet2</servlet-name>
  <url-pattern>/bar/*</url-pattern>
</filter-mapping> 
43.2.2.1.3.3. HTTP方法名称

以前的版本中,<http-method>元素只能接受7种标准HTTP/1.1方法之一:GET,POST,PUT,DELETE,HEAD,OPTIONS,与TRACE。在Servlet 2.5规范中,可接受所有合法的HTTP/1.1方法名,包括WebDAV方法如:LOCK,UNLOCK,COPY,MOVE等

43.2.2.2. 从Servlet2.3到Servlet2.4的变动

43.2.2.2.1. Servlet 2.4版使用XML Schema作为部署描述文件定义

Servlet 2.4版使用XML Schema定义作为部署描述文件,这样使Web容器更容易校验web.xml语法。同时XML Schema提供了更好的扩充性。

DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

        

变为在可以根元素指定属性,当然以前的方式也兼容

version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
43.2.2.2.2. Servlet 2.4版在加入了ServletRequest监听器

Servlet 2.4在事件监听器中加入了ServletRequest监听器,包括新的API:ServletRequestListener, ServletRequestAttributeListener和其他相关类。这些类可以用来管理和控制与ServletRequest动作有关的事件。请参考第 43.2.5.2 节 “Application Events”

43.2.2.2.3. Servlet 2.4版增强了请求分发器的过滤功能

Servlet 2.4的Web程序增强了filter和request dispatcher的配合功能,这样过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。请参考:第 43.2.5.1 节 “Servlet Filtering”

43.2.2.2.4. Servlet 2.4版可以定义网站的字符编码方式

Servlet 2.4增加了Web程序国际化功能,在web.xml中可以定义网站的字符编码方式。

    <locale-encoding-mapping>
      <locale>zh</locale>
      <encoding>GB2312</encoding>
    </locale-encoding-mapping>

当客户请求了特定语言的Web资源时,servlet程序通过ServletResponse接口的setLocale方法设置一个Web响应的语言属性。

43.2.3. Apusic Http Servlets编程初步

本章节介绍了基本的Http Servlet编程知识。

43.2.3.1. Servlet生命周期

Servlet的生命周期是由运行Servlet的容器控制的。当一个请求(request)被映射到Servlet上时,容器会按照下面的步骤执行:

如果容器需要移去一个Servlet,它调用destroy方法来结束这个Servlet。相关的讨论在第 43.2.4.9 节 “结束一个Servlet”

Servlet 的生命周期

43.2.3.2. 编写一个基本HTTP Servlet的步骤

这一节描述了编写一个简单的Http Servlet的基本步骤。

1. 引入相应的包和类,包括

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

2. 继承javax.servlet.http.HttpServlet

public class MyServlet extends HttpServlet{

3. 实现service方法

Servlet的主要功能是接受从浏览器发送过来的HTTP请求(request),并返回HTTP响应(response)。这个工作是在service方法中完成。service方法包括从request对象获得客户端数据和向response对象创建输出。

如果一个servlet从javax.servlet.http.HttpServlet继承,实现了doPost或doGet方法,那么这个servlet只能对POST或GET作出响应。如果开发人员想处理所有类型的请求(request),只要简单的实现service方法即可(但假如选择实现service方法,则不必实现doPost或doGet方法,除非在service方法的开始调用super.service())。

Service方法对请求的处理

HTTP Servlet规范描述了用来处理其他请求(request)类型的方法,所有这些方法都可以归属于service方法。 所有的service 方法使用一样的参数。HttpServletRequest提供关于请求(request)的信息,servlet可以使用 HttpServletResponse 对HTTP客户端作出响应。

public void service(HttpServletRequest req,HttpServletResponse res)
    throws IOException{

4. 设置响应内容的类型:

res.setContentType("text/html");

5. 获得java.io.PrintWriter对象的引用,用来输出:

PrintWriter out = res.getWriter();

6. 使用PrintWriter对象的println()方法创建一些HTML代码,例如:

out.println("<html><head><title>Hello World!</title></head>");
out.println("<body><h1>Hello World!</h1></body></html>");
  }
}

7. 编译Servlet:

从存放此Servlet源代码文件的目录编译此Servlet到包含此Servlet的应用中的WEB-INF/classes目录。如:

javac -d /your_application_dir/WEB-INF/classes your_servlet.java

8. 将此Servlet作为应用的一部分部署,请参看 第 48.2 节 “打包和部署Web模块”

9. 从浏览器访问Servlet

一般说来,调用Servlet的URL取决于包含Servlet的Web应用的名字和Web应用部署描述中的Servlet映射的名字。请求(request)参数也可是调用Servlet的URL的一部分,一般Servlet的URL如以下模式:

http://APUSIC_ADDRESS:<port>/your_web_application_name/mapped_servlet_name? parameter

43.2.3.3. 高级特征

通过前面的步骤创建了一个基本的Servlet,开发人员也许需要使用一些Servlet的高级特性:

43.2.4. Apusic Http Servlets编程进阶

本节介绍了如何在Apusic应用服务器的环境下编写Http Servlets。

43.2.4.1. 初始化Servlet

当Web容器装载和实例化一个Servlet类,在这个Servelt接收客户请求之前,Web容器需要初始化Servlet。开发人员可以覆盖Servlet接口的init()方法,允许Servlet读取配置信息、初始化资源和执行其他任何一次性的行为。Servlet初始化过程的失败将抛出UnavailableException异常。

  • 启动Apusic应用服务器时初始化Servlet

    通过设定Apusic Web应用的部署描述文件中Servlet类的<load-on-startup>标记,可使服务器在启动时初始化Servlet。同时,通过设定包含此Servlet的Web应用部署描述中的参数,可传递初始化参数到此Servlet 。

    下例在Web应用部署描述文件中设定了一个名为“company_name”,值为“Apusic”的初始化参数,

    <servlet>
    ...
    <init-param>
      <param-name>company_name</paramname>
      <param-value>Apusic</param-value>
    </init-param>
    </servlet>

    通过调用getInitParameter(String name)方法可取得此参数。

  • 重载init()方法

    通过重载init()方法可以定制Servlet初始化的动作。下面代码重载了init()方法,并使用了前面定义的company_name初始化参数。

    String company_name;
    public void init(ServletConfig config)
                throws ServletException{
        company_name=getInitParameter("company_name");
    }

43.2.4.2. 使用注解

Servlet2.5规范对某些注解如何在servlet环境中运作进行了规定。简单的servlet容器可以忽略这些规则,但JavaEE容器中的servlet则必须遵循这些规定。某些注解提供了部署信息,作为web.xml部署描述文件的替代品;某些注解请求容器进行某些动作,否则servlet就需要自行去实现这些任务。还有些注解则同时兼具以上两种功用。这些注解并不是由Servlet2.5规范定义的,规范仅仅解释了这些注解如何在servlet环境中运作。以下列出一些在JavaEE5中常见的注解以及它们的使用意图:

43.2.4.2.1. @Resource与@Resources

@Resource用来标记一个类或变量,请求servlet容器对其进行“资源注入”。当容器遇到这个注解时,将在servlet进入服务状态之前对变量赋予合适的值。通过这个注解,避免了在代码中显式使用一个JNDI查找调用以及在web.xml中声明资源。服务器自动通过反射机制完成这两个任务。注入资源可以是数据源、JMS目的地、或环境变量。以下是个简单的例子:

@Resource javax.sql.DataSource catalog;
public getData() {
  Connection con = catalog.getConnection();
} 

在servlet进入服务状态之前,容器将自动通过JNDI找到名为catalog,类型为DataSource的变量并将其引用赋值给此处的catalog变量。

@Resources注解作用与@Resource相似,用来包含一系列的@Resource注解

43.2.4.2.2. @PostConstruct与@PreDestroy

这两个注解把一个或多个类方法标记为生命周期回调方法。标记为@PostConstruct的方法将在资源注入后被回调,提供机会对注入的资源进行初始化。标记为@PreDestroy的方法将在servlet停止服务前被回调,提供机会释放注入的资源。这些回调方法必须为无返回值(void)且不抛出任何checked异常的非静态方法。在servlet中,这些注解的作用基本上是可以让任意方法成为第二个init()或destroy()方法。

43.2.4.2.3. @EJB

与@Resource类似,但用于注入Enterprise JavaBeans引用。

43.2.4.2.4. @WebServiceRef

与@Resource和@EJB类似,但用于注入Web service引用。

43.2.4.2.5. @Persistence系列注解

@PersistenceContext,@PersistenceContexts,@PersistenceUnit,@PersistenceUnits:在EJB3.0规范中定义的用于持久对象的注解。

43.2.4.2.6. @DeclareRoles

定义了在应用中使用的安全角色。当用于servlet时,这个注解可作为web.xml文件中<security-role>标签的替代品。

43.2.4.2.7. @RunAs

声明一个类将以哪个角色的名义运行。当用于servlet时,这个注解可作为web.xml文件中<run-as>标签的替代品。

43.2.4.3. 编写Service方法

Servlet通过下面的方法来提供服务:

  • 实现GenericServlet的service方法

  • 实现HttpServlet的doMethod方法(doGet、doDelete、doOptions、doPost、doPut、doTrace)

  • 实现Servlet接口并提供任何其他协议相关的方法。

通常,service方法用来从客户请求(request)中提取信息,访问扩展资源,并基于上面的信息提供响应(response)。

对于HTTP servlets,正确提供响应的过程是首先填写响应(response)的头信息,然后从响应(response)中得到输出流,最后向输出流中写入内容信息。响应(response)头信息必须最先设置。下面两节将描述从请求(request)中获得信息和产生HTTP响应(response)。

43.2.4.3.1. 取得客户端请求

客户端请求(request)包含了从客户端传递到Servlet的数据。所有的请求(request)都实现了ServletRequest接口。这个接口定义了一些方法来访问下面的信息:

类型描述对应方法
参数,用来在客户端和Servlet之间传送信息
  • getAttribute(java.lang.String name)

  • getAttributeNames()

  • getInputStream()

  • getParameter(java.lang.String name)

  • getParameterMap()

  • getParameterNames()

  • getParameterValues(java.lang.String name)

对象值属性,用来在Servlet容器和Servlet之间,或者协作的Servlet之间传递信息
  • removeAttribute(java.lang.String name)

  • setAttribute(java.lang.String name, java.lang.Object o)

有关请求使用的协议信息,客户端和服务器在请求中的调用
  • getContentLength()

  • getContentType()

  • getProtocol()

  • getReader()

  • getRealPath(java.lang.String path)

  • getRemoteAddr()

  • getRemoteHost()

  • getRequestDispatcher(java.lang.String path)

  • getScheme()

  • getServerName()

  • getServerPort()

  • isSecure()

有关localization的信息
  • getCharacterEncoding()

  • getLocale()

  • getLocales()

  • setCharacterEncoding(java.lang.String env)

下面的代码片断示范了如何使用request中的方法获得客户端信息。

Enumeration params = request.getParameterNames();

String paramName = null;
String[] paramValues = null;

while (params.hasMoreElements()) {
    paramName = (String) params.nextElement();
    paramValues = request.getParameterValues(paramName);

    System.out.println("\nParameter name is " + paramName);

    for (int i = 0; i < paramValues.length; i++) {
      System.out.println(", value " + i + " is " + paramValues[i].toString());
    }
}

HTTP servlets使用HTTP request对象(HttpServletRequest),它包含了request URL,HTTP头信息, 查询字符串等等。

HTTP request URL 包括几个部分:

http://<host>:<port><request path>?<query string>

一般情况下:

requestURI = contextPath + servletPath + pathInfo

  • Context path:通过getContextPath方法获得

  • Servlet Path:通过getServletPath方法获得

  • PathInfo:通过getPathInfo方法获得

例如:

Request PathPath Elements
/catalog/help/feedback.jsp
  • ContextPath: /catalog

  • ServletPath: /help/feedback.jsp

  • PathInfo: null

43.2.4.3.2. 提供HTTP响应

响应(response)包含了在服务器和客户端之间传递的数据。所有的响应(response)都实现了ServletResponse接口。这个接口定义了一些方法提供给开发人员使用:

类型描述对应方法
获得向客户端发送数据的输出流
  • 发送字符流:getWriter()

  • 发送字节流:getOutputStream()

指示响应返回的内容类型(例如:text/html)。已经注册的内容类型名称保存在IANA(Internet Assigned Numbers Authority):ftp://ftp.isi.edu/in-notes/iana/assignments/media-types
  • setContentType(java.lang.String type)

指出是否是缓冲输出。缺省情况下写入输出的内容被立即发送到客户端。使用缓冲后写入输出的内容先不发送到客户端,这样Servlet有更多的时间设置相应的状态码和头信息,或者转移到其他的Web资源
  • flushBuffer()

  • getBufferSize()

  • isCommitted()

  • reset()

  • resetBuffer()

  • setBufferSize(int size)

  • setContentLength(int len)

设置localization信息
  • getCharacterEncoding()

  • getLocale()

  • setLocale(java.util.Locale loc)

HTTP response类(HttpServletResponse)有一些代表HTTP头信息的域:

  • 状态码,用来指出响应(response)失败的原因

  • Cookies,在客户端存储应用相关的信息,有时cookies用来维护和标识用户的session。

Servlet首先设置响应(response)头信息:响应(response)的内容类别和缓冲区大小,然后在doGet方法中从响应(response)获得PrintWriter,最后向输出中写入HTML代码,调用close()方法提交这次对客户端的响应(response)。

    public void doGet (HttpServletRequest request,
         HttpServletResponse response)
         throws ServletException, IOException {

      // 设置头信息
      response.setContentType("text/html");
      response.setBufferSize(8192);
      PrintWriter out = response.getWriter();

      // 向response中输出
      out.println("<html>" +
         "<head><title>+
         messages.getString("TitleBookDescription")
         +</title></head>");
      ...
      out.println("</body></html>");

      // 关闭输出流
      out.close();
   }

43.2.4.4. 处理多线程情况

HTTP servlets通常具有处理多个客户端并发访问的能力。如果在Servlet中的方法访问了共享资源,那么开发人员需要创建在某一时刻只接收一个客户端请求(request)的Servlet来进行并发控制。

如果Servlet不仅继承HttpServlet类,而且同时实现SingleThreadModel接口,那么Servlet就可以在某一时刻只处理一个客户端请求(request)。

实现了SingleThreadModel接口不需要写任何额外的方法。只要Servlet实现了SingleThreadModel接口,Apusic应用服务器就确保在某一时刻只运行一个service方法。

例如,ReceiptServlet接受用户名和信用卡号,然后处理用户的订单并更新数据库。数据库连接作为共享资源,servlet应该同步对数据库连接的访问,或者servlet实现SingleThreadModel接口。

public class ReceiptServlet extends HttpServlet
        implements SingleThreadModel {

     public void doPost(HttpServletRequest request,
   HttpServletResponse response)
	    throws ServletException, IOException {
         ...
     }
     ...
}

43.2.4.5. 维护客户端状态

许多应用需要获得一系列互相关联的客户端请求,例如购物车功能。因为HTTP是无状态的协议,Web应用应该维护这些状态,这就叫做session。为了使应用具有session功能,Java Servlet技术提供了管理session的API和集中实现session的机制。

43.2.4.5.1. 访问Session

Session表现为HttpSession对象。调用request对象的getSession方法访问session。这个方法返回当前客户端请求(request)所关联的session,如果不存在,则为当前请求(request)创建一个session。由于getSession方法可能会改变响应(response)的头信息,所以需要在获得PrintWriter或ServletOutputStream之前被调用。

43.2.4.5.2. 使属性和Session相关联

可以通过名称使对象值属性和一个session相关联。例如把购物车作为属性存储在session中,在其他Servlet中可以通过session再获得购物车。

      // 得到用户session和购物篮
      HttpSession session = request.getSession();
      ShoppingCart cart =
         (ShoppingCart)session.
            getAttribute("cart");
      ...

43.2.4.5.3. Session管理

由于没有办法知道HTTP客户端不再需要session,因此每个session都关联一个时间期限使它的资源可以被回收。通过session的setMaxInactiveInterval和getMaxInactiveInterval方法访问超时时间。

为了确保session的有效,不超时,开发人员应该在service方法中周期性的访问session。当客户端完成一个(组)完整的交互过程后,可以使用invalidate()方法使服务器端的session无效,并清除session数据。

      // 得到用户session和购物篮
      HttpSession session = request.getSession();

      // 付款完成,使session无效
      session.invalidate();
      ...
43.2.4.5.4. Session跟踪

Web容器使用了一些方法使用户和特定的session相关联,这些方法在客户端与服务器端之间传递session的标识。这个标识可以作为cookies在客户端被维护,或者Web组件把这个标识包含在每个URL中返回到客户端。

如果应用需要使用session对象,那么开发人员必须确保在用户关闭cookies的情况下,应用能够改写URL使session跟踪功能激活。在所有返回给用户URL之前都调用response的encodeURL(URL)方法,这样在用户关闭cookies的情况下URL中就会包含session ID,否则不改变URL。 例如:

   out.println("<p> &nbsp; <p><strong><a href=\"" +

   response.encodeURL(request.getContextPath() + "/catalog") +
      "\">" + messages.getString("ContinueShopping") +
      "</a> &nbsp; &nbsp; &nbsp;" +
      "<a href=\"" +

   response.encodeURL(request.getContextPath() + "/cashier") +
      "\">" + messages.getString("Checkout") +
      "</a> &nbsp; &nbsp; &nbsp;" +
      "<a href=\"" +

   response.encodeURL(request.getContextPath() +
      "/showcart?Clear=clear") +
      "\">" + messages.getString("ClearCart") +
      "</a></strong>");

上面的代码改写了三个URL,如果客户端关闭 cookies,则URL被改写为:

http://localhost:6888/bookstore1/cashier; jsessionid=wKgUUxroPN$HVmpTkhU6YPLTqyMA

如果客户端cookies未关闭,则URL不作任何改变:

http://localhost:6888/bookstore1/cashier

43.2.4.6. 访问Servlet环境(Servlet Context)

Web组件中的环境(context)是实现了ServletContext接口的对象。可以通过getServletContext方法获得Web环境(Web context)。通过一些方法可以访问Web环境(ServletContext),以得到如下一些信息或资源:

  • 初始化参数

  • 与Web环境相关的资源

  • 对象值属性

  • 记录日志

      //获得ServletContext
      ServletContext context =  getServletContext();

      Counter counter = (Counter)context.getAttribute("hitCounter");
      ...
      writer.println("The number of hits is: " +
         counter.incCounter());
      ...
      context.log(sw.getBuffer().toString());

43.2.4.7. 使用Apusic Services

在Apusic应用服务器的编程模型中,业务逻辑(包括数据库访问、事务和复杂计算等)被包装在EJB组件中。作为表现层,Servlet能够通过Java Naming Directory Interface (JNDI)访问EJB组件。首先访问JNDI获得EJB组件的代理,然后可以象引用普通对象一样访问EJB组件。所有这一切,都是由EJB容器进行管理。下面的代码展示了通过JNDI定位EJB:

InitialContext ic = new InitialContext();
Object objRef = ic.lookup("java:comp/env/ejb/BookDBEJB");
BookDBEJBHome home = (BookDBEJBHome)PortableRemoteObject.narrow(objRef,
                    	database.BookDBEJBHome.class);
bookDBEJB = home.create();

当编写Http Servlet时,可以使用许多Apusic应用服务器提供的系统服务:

43.2.4.8. 调用其他Web资源

Web组件可以通过两种方式调用其他Web资源:间接方式和直接方式。间接方式调用是指在Web组件的内容中包含了指向其他Web资源的URL。

Web组件也能够在它执行的时候调用其他的Web资源。有两种可能性:

  • 包含其他Web资源的内容

  • 传递请求(request)到其他Web资源

调用方式描述
间接方式包含了指向其他Web资源的URL
直接方式
  • 包含其他Web资源的内容

  • 传递请求(request)到其他Web资源

为了调用运行Web组件的服务器上的可用资源,必须首先通过getRequestDispatcher("URL") 方法获得RequestDispatcher对象。

开发人员可以从request或Web Context获得RequestDispatcher对象,然而这两种方式有细微的区别。

43.2.4.8.1. 包含其他Web资源

包含其他Web资源非常有用,例如从Web组件返回给客户端的响应(response)中可以包含banner或版权(copyright)信息。调用RequestDispatcher对象的相关方法来实现此功能:

  • include(request, response)

如果资源是静态的,include方法能够实现服务器端包含。如果资源是Web组件,那么执行include()方法的效果等同于:

1. 向被包含的Web组件发送请求

2. 执行Web组件

3. 把执行的结果返回给包含Web组件的Servlet

被包含的Web组件可以访问request对象,当访问response对象有一些限制:

  • 可以向response对象写入内容和提交这个response

  • 不能设置头信息或调用任何可能影响头信息的方法

以下BannerServlet用来产生Banner信息:

public class BannerServlet extends HttpServlet {

   public void doGet (HttpServletRequest request,
      HttpServletResponse response)
           throws ServletException, IOException {

      PrintWriter out = response.getWriter();

      out.println(" .... ");
   }

   public void doPost (HttpServletRequest request,
      HttpServletResponse response)
           throws ServletException, IOException {

      PrintWriter out = response.getWriter();
      out.println(" ... ");
   }
}

其他的servlet可以包含BannerServlet:

RequestDispatcher dispatcher =
   getServletContext().getRequestDispatcher("/banner");
if (dispatcher != null)
   dispatcher.include(request, response);
}
43.2.4.8.2. 传递控制给其他Web组件

在一些应用中,也许需要一个Web组件对请求(request)作预处理,然后利用另一个Web组件产生响应(response)。可以调用RequestDispatcher对象的forward()方法实现这个功能。当一个请求(request)被转移,request URL会被设置成新的页面。如果原先的URL还有用,可以把它作为request的属性保存。以下Dispatcher类,从request中获得RequestDispatcher对象,然后把控制权交给template.jsp:

public class Dispatcher extends HttpServlet {
   public void doGet(HttpServletRequest request,
                     HttpServletResponse response) {

      request.setAttribute("selectedScreen",request.getServletPath());

      //从 request 中获得 RequestDispatcher 对象
      RequestDispatcher dispatcher = request.getRequestDispatcher("/template.jsp");

      //传递控制到template.jsp
      if (dispatcher != null)
         dispatcher.forward(request, response);
   }

   public void doPost(HttpServletRequest request,
   ...
}

forward()方法使另外的资源来答复客户端的请求(request)。如果已经访问了ServletOutputStream或PrintWriter,那么将不能使用include()方法,否则会抛出IllegalStateException异常。

43.2.4.9. 结束一个Servlet

当Servlet容器决定结束一个Servlet时(例如,Servlet容器将要关闭,或Servlet容器回收内存资源),它会调用Servlet接口的destroy方法。在destroy方法中,Servlet释放所有使用的资源并保存所有持久性状态。例如下面的destroy()方法释放了初始化 Servlet时分配的数据库辅助处理对象:

public void destroy() {
   bookDB = null;
}

当需要结束一个Servlet时,必须完成此Servlet所有的service()方法。只有在处理完所有的客户请求(request)或超过特定的时间期限,服务器才会调用destroy()方法。

43.2.5. Apusic Http Servlets高级开发

43.2.5.1. Servlet Filtering

过滤器(filter)是Java类,可以改变请求(request)和响应(response)的头信息与内容信息。过滤器不同于其他Web组件的地方是它本身并不创建响应(response),然而它可以依附在任何类型的Web资源上。过滤器截取请求(request),检查和改变request对象、response对象,并可以执行一些其他的任务。过滤器提供的主要功能是:

  • 实现日志功能

  • 实现用户定义的安全功能

  • 调试功能

  • 加密

  • 数据压缩

  • 改变发送给客户端的响应(response)

过滤器截获对特定命名的一个资源和一组资源的请求(request),然后执行过滤器中的代码。对于特定的资源,可以指定按照一定顺序调用的一个和多个过滤器,这就组成了链(chain)。使用过滤器主要包括:

  • 编写过滤器类

  • 定制请求(request)和响应(response)

  • 为特定的Web资源指定过滤器链

43.2.5.1.1. 编写过滤器类

编写过滤器的API是javax.servlet包中Filter、FilterChain和FilterConfig接口中定义的一些方法。定义一个过滤器就是实现Filter接口。Filter接口中最主要的方法是doFilter()方法,它接收三个参数:request对象、response对象、filterchain对象。

void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

这个方法能够执行的动作包括:

  • 检查请求(request)的头信息

  • 定制request对象,改变请求(request)的头信息或数据

  • 定制response对象,改变响应(response)的头信息或数据

  • 调用在过滤器链中的下一个实体。如果当前过滤器是链中的最后一个过滤器,那么下一个实体就是客户请求(request)的资源;否则,链中的下一个过滤器会被调用。通过chain对象的doFilter()方法调用下一个实体,并传递request对象和response对象作为参数。另外,也可以不调用doFilter()方法阻塞请求(request),这样,过滤器应该负责填充对客户的响应(response)。

  • 检查响应的头信息

  • 抛出异常显示处理过程中的错误

除了doFilter()方法,开发人员也必须实现init()和destroy()方法。当容器创建过滤器实例时调用init()方法,

 void init(FilterConfig filterConfig)

可以从FilterConfig对象中获得初始化参数。

在doFilter()方法中,过滤器可以从FilterConfig对象获得ServletContext对象,那么就可以访问存储在ServletContext中的属性对象。当过滤器完成特定的处理过程后,调用chain对象的doFilter()方法。例如

public final class HitCounterFilter implements Filter {
   private FilterConfig filterConfig = null;

   // 初始化
   public void init(FilterConfig filterConfig)
      throws ServletException {
      this.filterConfig = filterConfig;
   }
   // 结束
   public void destroy() {
      this.filterConfig = null;
   }

   public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain)
           throws IOException, ServletException {

      if (filterConfig == null)
         return;

      StringWriter sw = new StringWriter();
      PrintWriter writer = new PrintWriter(sw);

      writer.println(" ... ");
        .
        .
        .
      writer.flush();

      // 输出日志
      filterConfig.getServletContext().
         log(sw.getBuffer().toString());
      ...

      //调用在过滤器链中的下一个实体
      chain.doFilter(request, wrapper);
      ...
   }
}
43.2.5.1.2. 定制请求和响应

有许多方法可以改变请求(request)或者响应(response)。例如过滤器能够给请求(request)增加属性或在响应(response)中插入数据。

过滤器如果需要改变响应(response)必须在响应(response)返回给客户端之前捕获它。要做到这一点需要传递“替身”流(stream)给Servlet,然后利用“替身”stream产生响应(response)。“替身”stream防止了当响应(response)结束后关闭了原始的响应(response)输出流,并且允许过滤器改变Servlet的响应(response)。

过滤器产生“替身”stream

为了给Servlet传递“替身”stream,过滤器需要创建response对象的包装类并且覆盖getWriter或getOutputStream方法。包装类被FilterChain对象的doFilter方法传递。创建请求(request)的包装类继承ServletRequestWrapper或HttpServletRequestWrapper,创建响应(response)的包装类继承ServletResponseWrapper或HttpServletResponseWrapper。

以下CharResponseWrapper类包装了响应(response):

public class CharResponseWrapper extends
   HttpServletResponseWrapper {

   private CharArrayWriter output;

   public String toString() {
      return output.toString();
   }

   public CharResponseWrapper(HttpServletResponse response){
      super(response);
      output = new CharArrayWriter();
   }

   public PrintWriter getWriter(){
      return new PrintWriter(output);
   }
}

包装类被传递给BookStoreServlet,BookStoreServlet把响应写入“替身”stream,当chain.doFilter返回,HitCounterFilter重新找回response把它写入缓冲,过滤器插入计数器值到缓冲中然后重新设置response的头信息,最后把缓冲中的内容写入response。

PrintWriter out = response.getWriter();
//构造包装类
CharResponseWrapper wrapper = new CharResponseWrapper(
   (HttpServletResponse)response);

//向 doFilter 传递包装类
chain.doFilter(request, wrapper);

CharArrayWriter caw = new CharArrayWriter();

caw.write(wrapper.toString().substring(0,
          wrapper.toString().indexOf("</body>")-1));
...

caw.write("\n</body></html>");

//重新设置响应长度
response.setContentLength(caw.toString().length());

out.write(caw.toString());
out.close();
43.2.5.1.3. 映射过滤器

Web容器使用过滤器映射来决定是否过滤Web资源。在Web应用的部署描述文件中映射过滤器到Servlet或URL模板。

  • 在部署描述文件中加入<filter>标记 ,此标记包括:

    • <filter-name>:过滤器名称

    • <filter-class>:过滤器的实现类

    • <init-params>:过滤器的初始参数

    <filter>
       <filter-name>Compression Filter</filter-name>
       <filter-class>CompressionFilter</filter-class>
       <init-param>
          <param-name>compressionThreshold</param-name>
          <param-value>10</param-value>
       </init-param>
    </filter>

  • 在部署描述文件中加入<filter-mapping>标记,映射过滤器到Servlet:

    <filter-mapping>
       <filter-name>Compression Filter</filter-name>
       <servlet-name>CompressionTest</servlet-name>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>FORWARD</dispatcher>
       <dispatcher>INCLUDE</dispatcher>
       <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    <servlet>
       <servlet-name>CompressionTest</servlet-name>
       <servlet-class>CompressionTest</servlet-class>
    </servlet>
    <servlet-mapping>
       <servlet-name>CompressionTest</servlet-name>
       <url-pattern>/CompressionTest</url-pattern>
    </servlet-mapping>
    

    映射过滤器到URL模板:

    <filter>
          <filter-name>HitCounterFilter</filter-name>
          <filter-class>HitCounterFilter</filter-class>
    </filter>
    <filter-mapping>
          <filter-name>HitCounterFilter</filter-name>
          <url-pattern>/*</url-pattern>
          <dispatcher>REQUEST</dispatcher>
          <dispatcher>FORWARD</dispatcher>
          <dispatcher>INCLUDE</dispatcher>
          <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    

    使用<url-pattern>/*<url-pattern>标记,此过滤器将使用于此web应用中的任何静态资源或Servlet内容,因为任何URL都可匹配"/*"的URL模式。

  • dispatcher元素的作用 :Servlet 2.4版的Web程序增强了filter和request dispatcher的配合功能,这样过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。

    • 只有当request直接来自客户,过滤器才生效,对应为REQUEST条件。

    • 只有当request被一个请求分发器使用forward()方法转到一个Web构件时,对应称为FORWARD条件。

    • 只有当request被一个请求分发器使用include()方法转到一个Web构件时,对应称为INCLUDE条件。

    • 只有当request被一个请求分发器使用“错误信息页”机制方法转到一个Web构件时,对应称为ERROR条件。

    • 第五种过滤器作用的条件可以是上面四种条件的组合。

    • 当不使用dispatcher元素时,客户的直接request会被用来过滤请求。如果请求是从一个request dispatcher转发过来的,这个过滤器不工作。

如下图,可以映射一个过滤器到一个或多个Servlet,或者可以映射一个 Servlet到多个过滤器。

过滤器的映射

过滤器F1映射到servlet S1,S2和S3,过滤器F2映射到servlet S2,过滤器F3 映射到servlet S1和S2。

43.2.5.2. Application Events

应用事件模型提供了当ServletContext,HttpSession,ServletRequest状态改变时的通知功能。可以编写事件监听类来响应这些状态的改变,并且可以配置和部署应用事件和监听类到Web应用。

对于ServletContext事件,当Web应用部署、卸载和对context增加属性时,事件监听类可以得到通知。下表列出了ServletContext的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。

事件类型接口方法
Servlet context被创建javax.servlet.ServletContextListenercontextInitialized()
Servlet context被注销javax.servlet.ServletContextListenercontextDestroyed()
增加属性javax.servlet. ServletContextAttributesListenerattributeAdded()
删除属性javax.servlet. ServletContextAttributesListenerattributeRemoved()
属性被替换javax.servlet. ServletContextAttributesListenerattributeReplaced()

对于HttpSession事件,当session激活、删除或者session属性的增加、删除和替换时,事件监听类得到通知。下表列出了HttpSession的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。

事件类型接口方法
session激活javax.servlet.http. HttpSessionListenersessionCreated()
session删除javax.servlet.http. HttpSessionListenersessionDestroyed()
增加属性javax.servlet.http. HttpSessionAttributesListenerattributeAdded()
删除属性javax.servlet.http. HttpSessionAttributesListenerattributeRemoved()
属性被替换javax.servlet.http. HttpSessionAttributesListenerattributeReplaced()

对于ServletRequest事件,当request初始化、销毁或者request属性的增加、删除和替换时,事件监听类得到通知。下表列出了ServletRequest的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。

事件类型接口方法
session初始化javax.servlet.ServletRequestListenerrequestInitialized()
session销毁javax.servlet.ServletRequestListenerrequestDestroyed()
增加属性javax.servlet.ServletRequestAttributeListenerattributeAdded()
删除属性javax.servlet.ServletRequestAttributeListenerattributeRemoved()
属性被替换javax.servlet.ServletRequestAttributeListenerattributeReplaced()
43.2.5.2.1. 配置事件监听类

配置事件监听类的步骤:

1. 打开Web应用的部署描述文件web.xml

2. 增加事件声明标记<listener>。事件声明定义的事件监听类在事件发生时被调用。<listener>标记必须在<filter>和<filter-mapping>标记之后和<servlet>标记之前。可以为每种事件定义多个事件监听类,Apusic应用服务器按照它们在部署描述文件声明的顺序调用。例如:

<listener>
  <listener-class>myApp.myContextListenerClass</listener-class>
</listener>
<listener>
  <listener-class>myApp.mySessionAttributeListenerClass</listener-class>
</listener>

3. 编写和部署监听类。

43.2.5.2.2. 编写事件监听类

编写事件监听类的步骤:

1. 创建新的类并实现事件对应的接口

2. 定义不接受参数、访问属性为public的构造函数

3. 实现接口的方法

4. 编译并拷贝到对应Web应用的WEB-INF/classes目录下,或者打包成jar文件拷贝到WEB-INF/lib目录下

43.2.5.2.3. 事件监听类模板

ServletContext 监听类例子:

import javax.servlet.*;
public final class myContextListenerClass
            implements   ServletContextListener {
    public void contextInitialized(ServletContextEvent event) {

      /*
         当 ServletContext 初始化时被调用,可以在这儿
         初始化 ServletContext 的相关数据
      */

    }
    public void contextDestroyed(ServletContextEvent event) {
      /*
         当 Web 应用被卸载或 Apusic 服务器关闭时被调用
      */
    }
}

HttpSession 属性监听类例子:

import javax.servlet.*;
public final class mySessionAttributeListenerClass
             implements   HttpSessionAttributesListener {
    public void attributeAdded(HttpSessionBindingEvent sbe) {
      /*
         增加session属性时被调用
      */
    }
    public void attributeRemoved(HttpSessionBindingEvent sbe) {
      /*
         删除session属性时被调用
      */
    }
    public void attributeReplaced(HttpSessionBindingEvent sbe) {
      /*
         替换session属性时被调用
     */
    }
}

ServletRequest 属性监听类例子:

import javax.servlet.*;
public final class myRequestAttributeListenerClass
             implements   ServletRequestAttributeListener {
    public void attributeAdded(HttpSessionBindingEvent sbe) {
      /*
         增加request属性时被调用
      */
    }
    public void attributeRemoved(HttpSessionBindingEvent sbe) {
      /*
         删除request属性时被调用
      */
    }
    public void attributeReplaced(HttpSessionBindingEvent sbe) {
      /*
         替换request属性时被调用
     */
    }
}