[How Tomcat Works]第2章 一个简单的Servlet容器

来源:互联网 发布:微商用什么软件 编辑:程序博客网 时间:2024/06/04 23:28

 

译者 jarfield

博客 http://jarfield.javaeye.com

  1. 概述
  2. javax.servlet.Servlet接口
  3. 第一个应用
    1. HttpServer1
    2. Request
    3. Response
    4. StaticResourceProcessor
    5. ServletProcessor1
    6. 运行应用
  4. 第二个应用
    1. 运行应用
  5. 总结

概述

    本章通过两个应用程序,介绍了如何开发你自己的Servlet容器。为了让你容易Servlet容器的工作原理,第一个应用程序被设计地尽可能简单,然后演化为稍微复杂一些的第二个Servlet容器。

    提示:每章的Servlet容器都是在前一章的基础上逐步演化的,到第17章就变成了一个羽翼丰满(full-functional)的Tomcat Servlet容器。

    本章的两个Servlet容器,既能够运行简单的Servlet,也能够处理静态资源。你可以使用PrimitiveServlet来测试本章的Servlet容器PrimitiveServlet类的代码在Listing 2.1中,源代码文件在webroot目录下。本章的Servlet容器还不能运行更加复杂的Servlet,不过你在下一章就可以学到如何构建更加成熟的Servlet容器。

Listing 2.1: PrimitiveServlet.java   
                                                                                   
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
 
public class PrimitiveServlet implements Servlet {
 
   public void init(ServletConfig config) throws ServletException {
     System.out.println("init");
   }
 
   public void service(ServletRequest request, ServletResponse response)
     throws ServletException, IOException {
     System.out.println("from service");
     PrintWriter out = response.getWriter();
     out.println("Hello. Roses are red.");
     out.print("Violets are blue.");
   }
 
    public void destroy() {
     System.out.println("destroy");
   }
    public String getServletInfo() {
     return null;
   }
   public ServletConfig getServletConfig() {
     return null;
   }
}
                                                                                   
 
    本章两个应用(Servlet容器)的类都在ex02.pyrmont包中。为了理解这两个应用是如何工作的,你需要熟悉javax.servlet.Servlet接口。因此,本章的第一节先讨论该接口。然后你将学习到,为了处理到ServletHTTP请求,Servlet容器必须做哪些事情。

javax.servlet.Servlet接口

    Servlet编程主要涉及到javax.servletjavax.servlet.http这两个包中的类和接口。在这些类和接口中,javax.servlet.Servlet接口是最重要的。所有的Servlet都必须实现该接口,或继承该接口的实现类。

    Servlet接口有5个方法,它们的原型如下所示。


public void init(ServletConfig config) throws ServletException
public void service(ServletRequest request, ServletResponse response)
   throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()

    在上面5个方法中,initservicedestroySerlvet的生命周期方法。
Serlvet容器初始化Servlet之后会调用init方法。Servlet容器只调用该方法一次,以表明Servlet可以投入使用了。init方法执行成功前,Servlet不可以接收任何请求。Servlet程序员可以重写(override)该方法,以添加仅需执行一次的代码,例如加载数据库驱动、初始化一些变量的值等等。其它情况下,该方法一般留空。

    接收到请求之后,Servlet容器以一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象为参数,调用Servletservice方法。ServletRequest对象包含了客户端的请求信息,而ServletResponse对象封装了Servlet的响应。service方法在Servlet的生命周期中可以被调用多次。
   
    在移除Servlet实例之前,Servlet容器会调用destroy方法。这通常发生在Servlet容器关闭或内存不够的时候。该方法只有在所有线程都退出service方法或超时之后,才能被调用。Serlvet容器调用了一个Servletdestroy方法之后,就不会调用该Servletservice方法。destroy方法给了Servlet一个机会,用来清理持有的资源,例如内存、文件句柄和线程,还可以用来持久化Servlet的状态。

    Listing 2.1PrimitiveServlet类的代码, 该Serlvet非常简单,我们用它来测试本章的Serlvet容器。PrimitiveServlet实现了javax.servlet.Servlet接口 (就像所有Servlet都必须做的那样),提供了所有Servlet接口所有5个方法的实现。每次调用initservicedestroy方法,PrimitiveServlet都把方法名打印到标准控制台。 另外,service方法从ServletResponse对象中获取了java.io.PrintWriter对象,用来向浏览器发送字符串。

第一个应用

    现在,让我们从Servlet容器的视角来看看Servlet编程。简而言之,一个功能完全的Serlvet容器需要为每个HTTP请求做以下的事情:

  • Servlet第一次被调用时,加载Serlvet类,并调用Serlvetinit方法(只调用一次)。
  • 为每个请求,创建一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象。
  • ServletRequest对象和ServletResponse为参数,调用Servletservice方法。
  • Servlet被关闭时,调用Servletdestroy方法,卸载(unloadServlet类。


    本章的第一个Servlet容器不是功能完全。因此,它只能运行简单的Servlet,没有调用initdestroy方法。相反,它做了以下事情:

  • 等待HTTP请求。
  • 创建一个ServletRequest对象和一个ServletResponse对象。
  • 如果请求的是静态资源,就以ServletRequest对象和ServletResposne对象为参数,调用StaticResourceProcessor对象的process方法。
  • 如果请求的是Servlet,就加载Servlet类,并以ServletRequest对象和ServletResposne对象为参数,调用Servletservice方法。


    提示:在这个Servlet容器中,每次请求ServletServlet类都会被加载。

    第一个Servlet容器共包含6个类:

  • HttpServer1
  • Request
  • Response
  • StaticResourceProcessor
  • ServletProcessor1
  • Constants


    Figure 2.1 描绘了第一个Servlet容器的UML

   该应用的入口(静态main方法)在HttpServer1类中。main方法创建了HttpServer1的一个实例,并且调用了它的await方法。await方法在等待HTTP请求,并为每个请求创建一个Request对象和Response对象,然后将它们派发(dispatch)到StaticResourceProcessor对象或ServletProcessor对象,派发的依据是:请求的内容是静态资源还是Servlet

                    Figure 2.1: The UML diagram of the first servlet container  

    Constants类包含了被其他类引用的静态常量WEB_ROOT。容器可以提供PrimitiveServlet和静态资源,WEB_ROOT就指定了PrimitiveServlet和静态资源所在的目录。

     HttpServer1对象一直等待HTTP请求,直到接收到shutdown命令位置。你可以用1中的方法发送shutdowm命令。

    本应用的每个类会在以下各小节中进行讨论。

HttpServer1

    本应用的HttpServer1类,和1简单Web服务器中的HttpServer类很类似。不过,HttpServer1类既可以处理静态资源,又可以处理Servlet。要请求一个静态资源,你可以在浏览器地址栏中敲入下面的URL:

    http://machineName:port/staticResource  

    这就是你在1中请求静态资源的方法。

    要请求Servlet,你可以使用下面的URL

    http://machineName:port/servlet/servletClass  

    因此,如果你使用本地浏览器请求名为PrimitiveServletSerlvet, 你可以在浏览器地址栏中敲入下面的URL

    http://localhost:8080/servlet/PrimitiveServlet  

    Servlet容器可以提供PrimitiveServlet。但是,如果你调用其他的Servlet,比如ModernServletServlet容器就会抛出异常。下一章,你将会构建同时提供这两个Servlet的应用。

    HttpServer1类的代码在Listing 2.2中。
 
Listing 2.2: The HttpServer1 Class's await method   
                                                                                   
package ex02.pyrmont;
 
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress; import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
 
public class HttpServer1 {
 
   /** WEB_ROOT is the directory where our HTML and other files reside.
    *  For this package, WEB_ROOT is the "webroot" directory under the
    *  working directory.
    *  The working directory is the location in the file system
    *  from where the java command was invoked.
    */
   // shutdown command
   private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
 
   // the shutdown command received
   private boolean shutdown = false;
 
   public static void main(String[] args) {
     HttpServer1 server = new HttpServer1();
     server.await();
   }
 
   public void await() {
     ServerSocket serverSocket = null;
     int port = 8080;
     try {
      serverSocket =  new ServerSocket(port, 1,
        InetAddress.getByName("127.0.0.1"));
     }
 
     catch (IOException e) {
       e.printStackTrace();
       System.exit(1);
     }
 
     // Loop waiting for a request
     while (!shutdown) {
       Socket socket = null;
       InputStream input = null;
       OutputStream output = null;
       try {
         socket = serverSocket.accept();
         input = socket.getInputstream();         
         output = socket.getOutputStream();

 
         // create Request object and parse
         Request request = new Request(input);
         request.parse();
 
         // create Response object
         Response response = new Response(output);
         response.setRequest(request);
 
         // check if this is a request for a servlet or
         // a static resource
         // a request for a servlet begins with "/servlet/"
         if (request.getUri().startsWith("/servlet/")) {
           ServletProcessor1 processor = new ServletProcessor1();
           processor.process(request, response);
         }
         else {
           StaticResoureProcessor processor =
             new StaticResourceProcessor();
           processor.process(request, response);
         }
 
          // Close the socket
         socket.close();
         //check if the previous URI is a shutdown command
         shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
       }
       catch (Exception e) {
         e.printStackTrace();
         System.exit(1);
       }
     }
   }
}
                                                                                   
 
    await方法等待HTTP请求,直到接收到shutdown命令为止,这会让你想起1await方法。Listing 2.2中的await方法和1的区别在于,Listing 2.2的请求既可以派发到StaticResourceProcessor对象,又可以派发都ServletProcessor对象。如果请求的URI/servlet/开头,那么就会被派发到ServletProcessor对象。否则,请求将被派发到StaticResourceProcessor对象。注意Listing 2.2中灰色的部分。

Request

    Servletservice方法从容器接收一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象作为参数。这也就是说,对每个HTTP请求,Servlet容器都必须创建一个ServletRequest对象和ServletResponse对象,并将它们传递给处理该请求的Servletservice方法。
   
    ex02.pyrmont.Request类代表了传递给Servletservice方法的请求对象。因此,它必须实现javax.servlet.ServletRequest接口。Request类必须提供ServletRequest接口所有方法的实现。不过,我们想让Reuqest尽量简单,只实现其中一些方法,在后面章节再实现所有的方法。为了通过编译,你必须提供这些方法的“空”实现。从Listing 2.3可以看出,所有返回值类型为Object的方法都返回null

Listing 2.3: The Request class   
                                                                                   
package ex02.pyrmont;
 
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
 
 
public class Request implements ServletRequest {
 
   private InputStream input;
   private String uri;
 
   public Request(InputStream input){
     this.input = input;    }
 
   public String getUri() {
     return uri;
   }
 
   private String parseUri(String requestString) {
     int index1, index2;
     index1 = requestString.indexOf(' ');
     if (index1 != -1) {
       index2 = requestString.indexOf(' ', index1 + 1);
       if (index2 > index1)
         return requestString.substring(index1 + 1, index2);
     }
     return null;
   }
 
   public void parse() {
     // Read a set of characters from the socket
     StringBuffer request = new StringBuffer(2048);
     int i;
     byte[] buffer = new byte[2048];
     try {
       i = input.read(buffer);
     }
     catch (IOException e) {
       e.printStackTrace();
       i = -1;
     }
     for (int j=0; j<i; j++) {
       request.append((char) buffer(j));
     }
     System.out.print(request.toString());
     uri = parseUri(request.toString());
   }
 
   /* implementation of ServletRequest */
   public Object getAttribute(String attribute) {
     return null;
   }
   public Enumeration getAttributeNames() {
     return null;
   }
   public String getRealPath(String path) {      return null;
   }
   public RequestDispatcher getRequestDispatcher(String path) {
     return null;
   }
   public boolean isSecure() {
     return false;
   }
   public String getCharacterEncoding() {
     return null;
   }
   public int getContentLength() {
     return 0;
   }
 
   public String getContentType() {
     return null;
   }
   public ServletInputStream getInputStream() throws IOException {
     return null;
   }
   public Locale getLocale() {
     return null;
   }
   public Enumeration getLocales() {
     return null;
   }
   public String getParameter(String name) {
     return null;
   }
   public Map getParameterMap() {
     return null;
   }
   public Enumeration getParameterNames() {
     return null;
   }
   public String[] getParameterValues(String parameter) {
     return null;
   }
   public String getProtocol() {
     return null;
   }
   public BufferedReader getReader() throws IOException {
     return null;    }
   public String getRemoteAddr() {
     return null;
   }
   public String getRemoteHost() {
     return null;
   }
   public String getScheme() {
     return null;
   }
   public String getServerName() {
     return null;
   }
   public int getServerPort() {
     return 0;
   }
   public void removeAttribute(String attribute) {  }
   public void setAttribute(String key, Object value) {  }
   public void setCharacterEncoding(String encoding)
     throws UnsupportedEncodingException {  }
}
                                                                                   
 
    另外,Request类仍包括1讨论过的parse方法和getUri方法。

Response

    Listing 2.4中的ex02.pyrmont.Response类,实现了javax.servlet.ServletResponse接口。因此,Response类必须提供ServletResponse接口所有方法的实现。和Request类一样,除了getWriter,我们将所有方法的实现“留空”。

Listing 2.4: The Response class   
                                                                                   
package ex02.pyrmont;
 
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.PrintWriter;
import java.util.Locale;

import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;
 
public class Response implements ServletResponse {
 
   private static final int BUFFER_SIZE = 1024;
   Request request;
   OutputStream output;
   PrintWriter writer;
 
   public Response(OutputStream output) {
     this.output = output;
   }
 
    public void setRequest(Request request) {
     this.request = request;
   }
 
   /* This method is used to serve static pages */
   public void sendStaticResource() throws IOException {
     byte[] bytes = new byte[BUFFER_SIZE];
     FileInputstream fis = null;
     try {
       /* request.getUri has been replaced by request.getRequestURI */
       File file = new File(Constants.WEB_ROOT, request.getUri());
       fis = new FileInputstream(file);
       /*
       HTTP Response = Status-Line
         *(( general-header | response-header | entity-header ) CRLF)
         CRLF
         [ message-body ]
         Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
       */
       int ch = fis.read(bytes, 0, BUFFER_SIZE);
       while (ch!=-1) {
 
         output.write(bytes, 0, ch);
         ch = fis.read(bytes, 0, BUFFER_SIZE);
       }
     }
     catch (FileNotFoundException e) {
       String errorMessage = "HTTP/1.1 404 File Not Found/r/n" +
         "Content-Type: text/html/r/n" +          "Content-Length: 23/r/n" +
          "/r/n" +
         "<h1>File Not Found</h1>";
       output.write(errorMessage.getBytes());
     }
     finally {
       if (fis!=null)
         fis.close();
     }
   }
 
 
   /** implementation of ServletResponse  */
   public void flushBuffer() throws IOException (   }
   public int getBufferSize() {
     return 0;
   }
   public String getCharacterEncoding() {
     return null;
   }
   public Locale getLocale() {
     return null;
   }
   public ServletOutputStream getOutputStream() throws IOException {
     return null;
   }
   public PrintWriter getWriter() throws IOException {
     // autoflush is true, println() will flush,
     // but print() will not.
     writer = new PrintWriter(output, true);
     return writer;
   }
   public boolean isCommitted() {
     return false;
   }
   public void reset() {  }
   public void resetBuffer() {  }
   public void setBufferSize(int size) {  }
   public void setContentLength(int length) {  }
   public void setContentType(String type) {  }
   public void setLocale(Locale locale) {  }
}
                                                                                   

    getWriter方法中,PrintWriter构造函数的第二个参数是布尔类型,表明是否启用自动刷新(autoflush)功能。第二个参数如果为true,所有的println调用均会刷新到输出流中。不过,调用print方法是不会刷新的。

    因此,如果Servletservice方法的最后一行恰好调用了print方法,那么print输出是不会被发送到浏览器的。本节的应用就有这个问题,我们将在下一章修复。

    Response类仍包括第1章中讨论过的sendStaticResource方法。

StaticResourceProcessor

    ex02.pyrmont.StaticResourceProcessor类提供静态资源服务。该类只有一个process方法,代码见Listing 2.5。

Listing 2.5: The StaticResourceProcessor class   
                                                                                   
package ex02.pyrmont;
 
import java.io.IOException;
 
public class StaticResourceProcessor {
 
   public void process(Request request, Response response) {
     try {
       response.sendStaticResource();
     }
     catch (IOException e) {
       e.printStackTrace();
     }
   }
}
                                                                                   
 
    process方法接收两个参数:ex02.pyrmont.Request对象和ex02.pyrmont.Response对象。该方法只是简单地调用了Response对象的sendStaticResource方法

ServletProcessor1

    Listing 2.6中的ex02.pyrmont.ServletProcessor1类负责处理对Servlet的HTTP请求。

Listing 2.6: The ServletProcessor1 class   
                                                                                   
package ex02.pyrmont;
 
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
 
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class ServletProcessor1 {
 
   public void process(Request request, Response response) {
     String uri = request.getUri();
     String servletName = uri.substring(uri.lastIndexOf("/") + 1);
     URLClassLoader loader = null;
     try {
       // create a URLClassLoader
       URL[] urls = new URL[1];
       URLStreamHandler streamHandler = null;
       File classPath = new File(Constants.WEB_ROOT);
       // the forming of repository is taken from the
       // createClassLoader method in
       // org.apache.catalina.startup.ClassLoaderFactory
       String repository =
        (new URL("file", null, classPath.getCanonicalPath() +
        File.separator)).toString() ;
       // the code for forming the URL is taken from
       // the addRepository method in
       // org.apache.catalina.loader.StandardClassLoader.
       urls[0] = new URL(null, repository, streamHandler);
       loader = new URLClassLoader(urls);
     }
     catch (IOException e) {        System.out.println(e.toString() );
     }
     Class myClass = null;
     try {
       myClass = loader.loadClass(servletName);
     }
     catch (ClassNotFoundException e) {
       System.out.println(e.toString());
     }
 
     Servlet servlet = null;
 
     try {
       servlet = (Servlet) myClass.newInstance();
       servlet.service((ServletRequest) request,
        (ServletResponse) response);
     }
     catch (Exception e) {
       System.out.println(e.toString());
     }
     catch (Throwable e) {
       System.out.println(e.toString());
     }
 
   }
}
                                                                                   
 
    ServletProcessor1类出奇地简单,只包括一个方法:process。该方法接收两个参数:一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象。从ServletRequest对象中,该方法通过调用geUri方法获得请求URI

    String uri = request.getUri();

    记住,URI的形式如下:
   
    /servlet/servletName

    其中servletNameServlet类的名称。要加载该Serlvet类,我们需要从URI中知道Servlet的名称。我们可以用process方法的下一行来获取Servlet的名称:

    String servletName = uri.substring(uri.lastIndexOf("/") + 1);

    接下来,process方法开始加载servlet。为此,你需要创建一个类加载器(class loader),并且告诉它,要加载的类所在的位置。对于这个Servlet容器,类加载器直接查找Constants.WEB_ROOT指向的位置,也就是工作目录下的webroot目录。

    提示:我们将在8详细讨论类加载器。

    要加载servet,你需要使用java.net.URLClassLoader类,它是java.lang.ClassLoader类的直接子类。 一旦有了URLClassLoader对象,你就可以调用它的loadClass来加载servlet类。实例化URLClassLoader类是直观易懂的。该类有3个构造函数,其中最简单的是:

    public URLClassLoader(URL[] urls);

    其中urlsjava.net.URL对象的数组,这些URL指向了类搜索路径。我们假设,任何以/结束的URL都指向目录,其它URL都指向,需要时可以下载和打开的JAR文件。

    提示:在servlet容器中,类加载器搜索servlet类文件的位置被称为仓库(repository)。

    在我们的应用中,类加载器只需要查找一个路径,即工作目录下的webroot目录。因此,我们先创建了一个长度仅为1URL数组。URL提供了很多构造函数,所以有多种方式创建URL对象。对于本应用,我们和Tomcat使用相同的构造函数。该构造函数的签名如下所示:

    public URL(URL context, java.lang.String spec, URLStreamHandler hander) throws MalformedURLException

    你可以这样使用上述构造函数,第一个参数传null,第二个参数传一个有效的spec,第三个值传null。但是,这里还有另一个接收三个参数的构造函数:

    public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException

    因此,如果你以如下方式创建URL对象,编译器将不知道调用哪个版本的构造函数。

    new URL(null, aString, null);

    要避免这个问题,你可以告诉编译器第三个参数的类型,就像这样:

    URLStreamHandler streamHandler = null;
    new URL(null, aString, streamHandler);

    对于第二个参数,你传递了一个代表仓库(servlet类文件所在的目录)的String对象,代码如下:

    String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();

    把这些代码片段组合在一起,就是process方法中创建URLClassLoader对象的代码:

    // create a URLClassLoader
    URL[] urls = new URL[1];
    URLStreamHandler streamHandler = null;
    File classPath = new File(Constants.WEB_ROOT);
    String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
    urls[0] = new URL(null, repository, streamHandler);
    loader = new URLClassLoader(urls);
 
    提示:组装仓库的代码来自org.apache.catalina.startup.ClassLoaderFactorycreateClassLoader方法,组装URL的代码来自org.apache.catalina.loader.StandardClassLoaderaddRepository方法。不过,在本章你还不用关注这些类。

    有了类加载器,你可以使用其loadClass方法来加载servlet类:

    Class myClass = null;
    try {
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }

    下一步,process方法创建了servlet类的一个实例,并向下转型(downcast)到javax.servlet.Servlet,然后调用其service方法:

    Servlet servlet = null;
    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request,
        (ServletResponse) response);
    }
    catch (Exception e) {
      System.out.println(e.toString());
    }
    catch (Throwable e) {
      System.out.println(e.toString());
    }

运行应用

    要在Windows运行该应用,在工作目录运行以下命令:

    java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1

    在Linux上,你可以使用冒号分隔两个库:

    java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1

    要测试该应用,你可以在浏览器地址栏中敲入以下命令:

    http://localhost:8080/index.html   
 
    或者

    http://localhost:8080/servlet/PrimitiveServlet

    如果请求的是PrimitiveServlet, 你会在浏览器中看到一些文本:
    Hello. Roses are red.
 
    提示:在浏览器中,你并不能看到第二个字符串“Violets are blue”, 这是因为只有第一个字符串被flush到浏览器。不过,我们将在第3章修复这个问题。

第二个应用

    第一个应用有个严重的问题。在ServletProcessor1类的process方法中,你将ex02.pyrmont.Request对象向上转型(upcast)到javax.servlet.ServletRequest,并将它作为servletservice方法的第一个参数。同样,你也将ex02.pyrmont.Response对象向上转型到javax.servlet.ServletResponse,并将它作为servletservice方法的第二个参数。

    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    }

    这是对安全性的一种妥协(compromise)。了解Servlet容器内部细节的Servlet程序员,可以将ServletRequest对象和ServletResponse对象分别转回到ex02.pyrmont.Requestex02.pyrmont.Response,并调用它们的public方法。有了Request对象,他们(指 Servlet程序员)就可以它的parse方法。有了Resposne对象,他们就可以调用它的sendStaticResource方法。

    你不能将parsesendStaticResource方法设置成private,否则它们就不能被其他类调用了。但是,这两个方法对servlet应该是不可见的。我们的解决方案是让RequestResponse类都具有默认访问修饰符,这样它们就不能在ex02.pyrmont包外被使用。不过,这里有一个更优雅的方法:使用门面(facade)类。请看Figure 2.2中的UML图。

                          Figure 2.2: Façade classes 

    在第二个应用中,我们增加了两个facade类:RequestFacadeResponseFacadeRequestFacade实现了ServletRequest接口,并通过接受一个Request对象的构造函数进行实例化,其中Request对象将赋值给一个类型为ServletRequest的成员变量。但是,该成员变量的访问类型是,因此不能在RequestFacade类的外部被访问。我们构造了一个RequestFacade对象传递给service方法,而不是将Request对象向上转型成ServletRequest对象再传递给service方法。Servlet程序员仍可以将ServletRequest对象向下转型回RequestFacade,但是他们只能访问SerlvetRequest接口中的方法。现在,parseUri方法就安全了。

    Listing 2.7展现了RequestFacade类的部分代码。

Listing 2.7: The RequestFacade class   
                                                                                     
package ex02.pyrmont;
public class RequestFacade implements ServletRequest {
   private ServletRequest request = null;
 
   public RequestFacade(Request request) {
     this.request = request;
   }
 
   /* implementation of the ServletRequest*/
   public Object getAttribute(String attribute) {
     return request.getAttribute(attribute);
   }
 
   public Enumeration getAttributeNames() {
     return request.getAttributeNames();
   }
 
   ...
}
                                                                                     
 
    请注意Request的构造函数。它接受了一个Request对象但是立即将其赋值给privateServletRequest对象。同样也请注意RequestFacade类的每个方法都调用了ServletRequest对象的相应方法。

    ResponseFacade也是这么做的。

    第二个应用使用了下面的类:

  • HttpServer2
  • Request
  • Response
  • StaticResourceProcessor
  • ServletProcessor2
  • Constants


    HttpServer2类和HttpServer1基本相同,只是它的await方法使用了ServletProcessor2类,而不是ServletProcessor1:

    if (request.getUri().startWith("/servlet/")) {
      servletProcessor2 processor = new ServletProcessor2();
      processor.process(request, response);
    }
    else {
      ...
    }
   
    ServletProcessor2类和ServletProcessor1基本相同,除了下面列出的process方法的部分代码代码:

    Servlet servlet = null;
    RequestFacade requestFacade = new RequestFacade(request);
    ResponseFacade responseFacade = new ResponseFacade(response);
    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
    }

运行应用

    要在Windows运行该应用,在工作目录运行以下命令:

    java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer2

    在Linux上,你可以使用冒号分隔两个库:

    java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer2

    你可以使用与第一个应用相同的URLs,并且将会看到相同的结果。

总结

   本章讨论了两个简单的Servlet容器,它们即可以处理静态资源,也可以处理PrimitiveServlet这样简单的servlet。同时,本章也给出了javax.servlet.Servlet接口和相关类型的背景信息。