How tomcat works——2 一简单的Servlet容器

概述

本章通过二个应用Demo讲解如何开发自己的servlet容器。应用Demo1尽可能简单的设计,以便于你更好地理解servlet容器是如何工作的。然后,应用Demo1慢慢演化为第二个servlet容器——稍微变的复杂一些。

注意:每一章的应用Servlet容器都是由上一章逐渐演化过来的,直到一个完整的Tomcat Servlet容器在第17章出现。

二个Servlet容器都可以处理简单的servlet和静态资源。你可以使用PrimitiveServlet来测试这个容器。更复杂的servlet处理,超过了这里容器的能力,但你将在后续章节中学到如何创造更高雅优秀的Servlet容器。PrimitiveServlet如下:

package ex02.pyrmont;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.*;

/**
 * @author : Ares
 * @createTime : Sep 12, 2012 9:37:49 PM
 * @version : 1.0
 * @description :
 */
public class PrimitiveServlet implements Servlet {

    /* (non-Javadoc)
     * @see javax.servlet.Servlet#destroy()
     */
    public void destroy() {
        System.out.println("destroy");
    }

    /* (non-Javadoc)
     * @see javax.servlet.Servlet#getServletConfig()
     */
    public ServletConfig getServletConfig() {
        return null;
    }

    /* (non-Javadoc)
     * @see javax.servlet.Servlet#getServletInfo()
     */
    public String getServletInfo() {
        return null;
    }

    /* (non-Javadoc)
     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init");
    }

    /* (non-Javadoc)
     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest
     * , javax.servlet.ServletResponse)
     */
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        System.out.println("from service"); 
        PrintWriter out = res.getWriter(); 
        out.println("Hello. Roses are red."); 
        out.print("Violets are blue."); 
    }
}

很好的理解本章应用Demo,你得熟悉javax.servlet.Servlet接口。刷新你的记忆,本章第一部分将介绍这个接口。然后,你将知道一个Servlet容器是如何服务于一个servlet的 HTTP请求。

2.1 javax.servlet.Servlet接口 

Servlet编程是通过javax.servlet 和 javax.servlet.http这2个包中的类和接口来实现的。在这些类和接口中,javax.servlet.Servlet接口至关重要。所有servlet都必须实现这个接口或继承一个类——此类已实现了javax.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()

Servlet 5个方法里,其中init()、service()、destroy()3个方法是servlet的生命周期方法。当servlet类被装载初始化后,servlet容器调用init()方法。servlet 容器只调用一次,以此表明servlet 已经被加载进服务中(The servlet container calls this method exactly once to indicate to the servlet that the servlet is being placed into service)。在servlet收到任何请求之前,init()必须成功执行完。一个servlet程序可以重写此方法——添加那些紧需要执行一次的初始化代码,比如:加载数据库驱动、初始值等等。另一种情况,通常此方法都空着,不写任何代码。

每当对此servlet发起请求时,Servlet容器都会调用其service()方法。Servlet容器传递javax.servlet.ServletRequest和javax.servlet.ServletResponse二个对象。ServletRequest对象包含客户端HTTP请求信息,ServletResponse对象封装servlet应答信息。在Servlet生命周期中,service()方法会被多次调用。

当从服务中移除该servlet实例之前,Servlet容器会调用其destroy()方法。这通常发生在当Servlet容器关闭或Servlet容器需要更多空闲内存时。仅仅在所有 servlet 线程 
的 service 方法已经退出或者超时淘汰时,destroy()方法才被调用(This method is called only after all threads within the servlet’s service method have exited or after a timeout period has 
passed)。当Servlet容器调用了destroy()后,在同一个servlet中不可再调用service()方法。destroy()方法给servlet提供了释放它当初占用资源的机会,比如:内存、文件句柄、线程,并且确保任何持久化数据状态和servlet当前内存中状态同步一致。

本章一个简单PrimitiveServlet类,可以方便你用来测试Servlet容器。PrimitiveServlet类实现javax.servlet.Servlet接口(所有servlet必须实现),并且也为servlet的5个方法也提供了实现。PrimitiveServlet处理的事情很简单。每当init()、service()、destroy()方法被调用时,servlet向控制台输出方法名称。另外,service()方法中包含从ServletResponse对象中获得java.io.PrintWriter对象,并且通过它向浏览器输出字符串响应信息。

2.2 应用Demo 1

现在让我们通过一个Servlet容器透视图审视下servlet程序。简而言之,一个功能完整的Servlet容器会为一servlet的每次HTTP请求做如下工作:

1》当servlet被第一次请求时,加载servlet类,并调其init()方法(仅此一次); 
2》为每次请求,构造avax.servlet.ServletRequest和javax.servlet.ServletResponse实例; 
3》调用servlet的service()方法,传递ServletRequest和ServletResponse对象; 
4》当关闭servlet类时,调用destroy()方法,并卸载servlet类

本章此简单的Servlet容器,功能并不完整。因此,它只可运行简单的servlet,并且不调用servlet的init()和destroy()方法。代替的,它会处理如下工作:

1》等待HTTP请求; 
2》构造ServletRequest和ServletResponse对象; 
3》如果请求静态资源,则将调用StaticResourceProcessor实例的处理方法,传入ServletRequest和ServletResponse对象; 
4》如果请求一个servlet,则加载servler,并调用其service()方法,传入ServletRequest和ServletResponse对象;

注意,在此Servlet容器里,当servlet被请求时,每次都会重新加载此servlet。

这个应用Demo包含了如下6个类:

1》HttpServer1 
2》Request 
3》Response 
4》StaticResourceProcessor 
5》ServletProcessor1 
6》Constants

如下展现了第一个Servlet容器的UML图: 
这里写图片描述

此应用Demo的入口(静态main()方法)在HttpServer1中。此main()方法创建了HttpServer1一个实例,并调用其await()方法。await()等待HTTP请求,为每次请求创建Request和Response对象,并且分发给StaticResourceProcessor实例或ServletProcessor实例——取决于请求的是静态资源还是servlet。

其中Constants类定义了其他类引用了的常量WEB_ROOT。WEB_ROOT标明了可被此Servlet容器使用的PrimitiveServlet和静态资源的位置地址。

HttpServer1实例保持等待接收HTTP请求直到接收到shutdown命令。发起shutdow命令和你第一章中操作一样。

本应用Demo所涉及的每一个类,如下各个小节。

2.2.1 HttpServer1类

本应用Demo中的HttpServer1类似于第一章中的HttpServer类。然而,本HttpServer1可以服务于静态资源和servlet。当请求静态资源时,可在你的浏览器上输入如下类似URL:

http://machineName:port/staticResource

就像是在第 1 章提到的,你可以请求一个静态资源。

请求一个servlet时,使用如下类似URL:

http://machineName:port/servlet/servletClass

因此,假如你在本地请求一个名为 PrimitiveServlet 的 servlet,你在浏览器的地址栏或 
者网址框中敲入:

http://localhost:8080/servlet/PrimitiveServlet

本Servlet容器可以服务于PrimitiveServlet。如果你调用其他的servlet,如ModernServlet,那么此Servlet容器将会抛出异常。在后面的章节中,我们将会改造此应用,使其服务于更多的servlet。

HttpServer1类的代码如下:

package org.how.tomcat.works.ex02;

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 {
          StaticResourceProcessor 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命令被发起,这让你想起第一章中的await()方法。它们的不同在于本类中的await()方法可以分发请求到StaticResourceProcessor或ServletProcessor。当请求URL中含字符串“/servlet/”时,请求将被分发到ServletProcessor。否则的话,请求将分发到StaticResourceProcessor。注意上面代码被标灰色的地方。

2.2.2 Request类

一个servlet的service()方法从servlet容器中接收javax.servlet.ServletRequest和javax.servlet.ServletResponse实例。也就是说,对于每一个HTTP请求,servlet容器必须创建ServletRequest和ServletResponse对象,并且把它们传递给servlet的service()方法。

本Request类代表着一个请求对象被传递给servlet的service()方法。照此,它必须实现javax.servlet.ServletRequest 接口。本类实现了接口提供的所有方法。不过,我们想要让它非常简单,所以仅仅提供实现其中一些方法,我们在以下各章中再实现全部的方法。要编译此Request 类,你需要把这些方法的实现留空。查看本Request 类,你将会看到那些需要返回一个对象的方法返回了 null。

Request类代码如下:

package org.how.tomcat.works.ex02;

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 the 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类中依然只有parse()和getUri()方法,如第一章讨论的一样。

2.2.3 Response类

本Response类实现了javax.servlet.ServletResponse。同样,此类必须实现接口提供的所有方法。 类似于Request类,我们除了getWriter()方法外留白了其它暂未具体实现的方法。

Response类代码如下:

package org.how.tomcat.works.ex02;

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 a static page */
  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 类的构造方法的第二个参数是一个布尔值表明是否允许自动刷新。传递 true 作为第二个参数将会使任何 println 方法的调用都会刷新输出(output)。不过,print()方法不会刷新输出。

因此,如果print()方法的调用发生在 servlet 的 service() 方法的最后一行,那么输出将不会被发送到浏览器。这个缺点将会在下一个应用程序中修复。

本Response 类中还拥有在第 1 章中谈到的 sendStaticResource()方法。

2.2.4 StaticResourceProcessor类

本StaticResourceProcessor类服务于静态资源请求。 它只有一个process()方法。

StaticResourceProcessor类代码如下:

package org.how.tomcat.works.ex02;

import java.io.IOException;

public class StaticResourceProcessor {

  public void process(Request request, Response response) {
    try {
      response.sendStaticResource();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
}

可见process()方法接收二个参数:org.how.tomcat.works.ex02.Request实例和org.how.tomcat.works.ex02.Response实例。此方法只是简单调用Response对象的sendStaticResource()。

2.2.5 ServletProcessor1类

本ServletProcessor1类用于处理servlet 的HTTP请求。

ServletProcessor1类代码如下:

package org.how.tomcat.works.ex02;

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 class.
      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()一个方法。此方法接收2个参数:javax.servlet.ServletRequest实例和javax.servlet.ServletResponse实例。该方法从ServletRequest 中通过调用 getRequestUri 方法获得 URI:

String uri = request.getUri();
  • 1

请记住 URI 是以下形式的:

/servlet/servletName

在此 servletName是servlet类的名字。

要加载 servlet 类,我们需要从 URI 中知道 servlet 的名称。我们可以使用下一行代码来获得 servlet 的名字:

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

接下去,process()方法加载 servlet。要完成这个,你需要创建一个类加载器并告诉这个类加载器要加载的类的位置。对于这个 servlet 容器,类加载器直接在 Constants.WEB_ROOT 指向的目录里边查找。Constants.WEB_ROOT就是指向工作目录下面的 webroot目录。

注意: 类加载器将在第 8 章详细讨论。

要加载 servlet,你可以使用 java.net.URLClassLoader 类,它是 java.lang.ClassLoader类的一个直接子类。当你拥有一个 URLClassLoader 实例,你可以使用它的loadClass()方法去加载一个 servlet 类。实例化URLClassLoader是简单的(Instantiating the URLClassLoader class is straightforward)。这个类有三个构造方法,其中最简单的是:

public URLClassLoader(URL[] urls);
  • 1

这里 urls 是一个 java.net.URL 的对象数组,这些对象指向了加载类时候要查找的位置。任何以/结尾的 URL 都假设是一个目录。否则,会假定是一个将被下载并在需要的时候打开的 JAR 文件。

注意:在一个 servlet 容器里边,一个类加载器可以找到 servlet 的地方被称为资源库(repository)。

在我们的应用Demo里边,类加载器必须查找的地方只有一个,如工作目录下面的 webroot目录。因此,我们首先创建一单个 URL 组成的数组。URL 类提供了一系列的构造方法,所以有很多种方式构造一个 URL 对象。对于这个Demo来说,我们使用了和Tomcat 中另一个类的相同的构造方法。这个构造方法如下所示:

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

你可以使用这个构造方法,并为第二个参数传递一个值,为第一个和第三个参数都传递null。不过,这里还有另外一个接受三个参数的构造方法:

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

因此,假如你使用下面的代码时,编译器将不会知道你指的是哪个构造方法:

new URL(null, aString, null);

你可以通过告诉编译器第三个参数的类型来避开这个问题,例如:

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

对于第二个参数,你可以使用如下面的代码组成一个包含资源库(servlet 类可以被找到的地方)的字符串:

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

把所有的片段组合在一起,就是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.ClassLoaderFactory 
的 createClassLoader()方 法 来 的 , 而 生 成 URL 的 代 码 是 从 
org.apache.catalina.loader.StandardClassLoader 的 addRepository()方法来的。不过,在以下各章之前你不必担心这些类。

当有了一个类加载器,你可以使用loadClass()方法加载一个 servlet:

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

然后,process()方法创建一个 servlet 类的实例, 并把它向下转换为javax.servlet.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());
}

2.2.6 运行Demo

Windows 上运行该应用程序,在工作目录下面敲入以下命令:

java -classpath ./lib/servlet.jar;./ org.how.tomcat.works.ex02.HttpServer1
  • 1

Linux 下,你使用一个冒号来分隔两个库:

java -classpath ./lib/servlet.jar:./ org.how.tomcat.works.ex02.HttpServer1
  • 1

要测试该应用程序,在浏览器的地址栏或者网址框中敲入:

http://localhost:8080/index.html

或者

http://localhost:8080/servlet/PrimitiveServlet

当调用 PrimitiveServlet 时,你将会在浏览器看到下面的文本:

Hello. Roses are red.

请注意,因为只是第一个字符串被刷新到浏览器,所以你不能看到第二个字符串 “Violets are 
blue”。我们将在第 3 章修复这个问题。

2.3 应用Demo 2

Demo1中有严重的问题。在ServletProcessor1类process()方法中,你向上强制转换org.how.tomcat.works.ex02.Request 实例到javax.servlet.ServletRequest,并且作为第一个参数传给servlet的service()方法。你也向上强制转换org.how.tomcat.works.ex02. Response实例到javax.servlet.Servlet Response,并且作为第二个参数传给servlet的service()方法:

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

这破坏了安全性。知道这个Servlet容器内部运作机制的servlet程序员可以分别反向向下强制转换ServletRequest和ServletResponse到org.how.tomcat.works.ex02.Request和org.how.tomcat.works.ex02. Response,并且调用它们的公共方法(public修饰的方法)。有了Request实例,他们可以调其pares()方法;有了Response实例,他们可以调其sendStaticResource()方法。

你不可以设置pares()和sendStaticResource()方法为私有方法(private修饰),因为它们要被其他类使用。不过,这有两个方法可以做到外部不应该可以从servlet 内部获取访问(However, these two methods are not supposed to be available from inside a servlet)。其中一个解决办法就是让 Request 和Response 类拥有默认访问修饰,所以它们不能在 org.how.tomcat.works.ex02 包的外部使用。不过,这里有一个更优雅的解决办法:通过使用 facade 类(门面设计模式)。请看 UML 图: 
这里写图片描述

在此应用Demo2中,我们添加了二个门面类:RequestFacade和ResponseFacade。RequestFacade 实现了ServletRequest 接口并通过在构造方法中传递 一个引用了ServletRequest 对象的 Request 实例作为参数来实例化。ServletRequest 接口中每个方法的实现都是对应调用 Request 对象中方法。然而 ServletRequest 对象本身是私有的,并不能在类的外部访问。我们构造了一个 RequestFacade 对象并把它传递给 service() 方法,而不是向下强制转换Request 对象为 ServletRequest 对象并传递给 service() 方法。Servlet 程序员仍然可以向下强制转换ServletRequest 实例为 RequestFacade,不过它们只可以访问 ServletRequest 接口里边的公共方法。现在 parseUri()是安全的了。

public class RequestFacade implements ServletRequest {
private ServleLRequest 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();
}
...
}

请注意 RequestFacade 的构造方法。它接受一个 Request 对象并马上赋值给私有的servletRequest 对象。还请注意,RequestFacade 类的每个方法调用 ServletRequest 对象的相 
应的方法。

这同样适应于ResponseFacade类。

这里是应用Demo2 中使用的类: 
• 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);
}

2.3.1运行Demo

Windows 上运行该应用程序,在工作目录下面敲入以下命令:

java -classpath ./lib/servlet.jar;./ org.how.tomcat.works.ex02.HttpServer2
  • 1

Linux 下,你使用一个冒号来分隔两个库:

java -classpath ./lib/servlet.jar:./ org.how.tomcat.works.ex02.HttpServer2
  • 1

你可以使用与应用Demo1一样的地址,并得到相同的结果。

2.4 小结

本章讨论了两个简单的servlet 容器,它可以用来处理静态资源和像 PrimitiveServlet 这么简单servlet。同时也提供了关于 javax.servlet.Servlet 接口以及相关类的背景信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值