本章通过介绍两个应用来解释如何开发自己的servlet容器。第一个应用程序被设计得尽可能简单,让你很容易理解servlet容器是如何工作的。然后,它将演化成第二个servlet容器,这个容器略显复杂。
注意,每一章中的每一个servlet容器应用都是从上一章的应用逐渐演化出来的,直到第17章构建出一个功能齐全的Tomcat servlet容器。
这两个servlet容器都可以处理简单的servlet以及静态资源。你可以使用PrimitiveServlet来测试这个容器。在清单2.1中给出了PrimitiveServlet,它的类文件可以在webroot目录下找到。更复杂的servlet超出了这些容器的能力,但你将在接下来的章节中学习如何构建更复杂的servlet容器。
清单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; } } 复制代码
这两个应用程序的类都是 ex02.pyrmont
包的一部分。为了了解这两个应用程序的工作方式,你需要熟悉 javax.servlet.Servlet
接口。为了刷新你的记忆,本章第一节将讨论这个接口。在这之后,你将了解一个servlet容器要为servlet服务的HTTP请求做什么。
Servlet编程是通过两个包中的类和接口实现的: javax.servlet
和 javax.servlet.http
。在这些类和接口中, javax.servlet.Servlet
接口是最重要的。所有的servlet都必须实现这个接口,或者扩展一个能实现这个接口的类。
Servlet接口有五个方法,其签名如下。
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的五个方法中, init
、 service
和 destroy
方法是Servlet的生命周期方法。实例化servlet类后,servlet容器将调用 init
方法。servlet容器只调用一次此方法,以指示该servlet已投入使用。 init
方法必须成功完成,然后servlet才能接收任何请求。Servlet程序员可以重写此方法以编写仅需要运行一次的初始化代码,例如加载数据库驱动程序,初始化值等。在其它情况下,此方法通常留空。
每当有对Servlet的请求时,Servlet容器就会调用Servlet的 service
方法。Servlet容器传递一个 javax.servlet.ServletRequest
对象和一个 javax.servlet.ServletResponse
对象。 ServletRequest
对象包含客户端的HTTP请求信息,而 ServletResponse
对象则封装了Servlet的响应。在Servlet的生命周期内,多次调用 service
方法。
servlet容器在将servlet实例从服务中移除之前,会调用 destroy
方法。这通常发生在servlet容器被关闭或者servlet容器需要一些空闲内存的时候。仅在servlet的 service
方法中的所有线程都已退出或经过超时时间后,才调用此方法。Servlet容器调用 destroy
方法之后,它将不再在同一Servlet上再次调用 service
方法。 destroy
方法使servlet有机会清除所有保持的资源,例如内存,文件句柄和线程,并确保任何持久状态都与servlet在内存中的当前状态同步。
清单2.1介绍了一个名为PrimitiveServlet的servlet的代码,这是一个非常简单的servlet,你可以用它来测试本章中的servlet容器应用。PrimitiveServlet类实现了 javax.servlet.Servlet
(正如所有的servlet必须实现的那样),并为Servlet的所有5个方法提供了实现。PrimitiveServlet所做的事情非常简单。每次调用 init
、 service
或 destroy
方法中的任何一个方法,servlet都会将该方法的名称写到标准控制台中。此外, service
方法从 ServletResponse
对象中获取 java.io.PrintWriter
对象,并向浏览器发送字符串。
现在,让我们从servlet容器的角度检查servlet编程。简而言之,功能齐全的servlet容器针对servlet的每个HTTP请求执行以下操作:
init
方法(仅一次)。 javax.servlet.ServletRequest
实例和一个 javax.servlet.ServletResponse
实例。 service
方法,并传递 ServletRequest
和 ServletResponse
对象。 destroy
方法并卸载servlet类。
本章的第一个servlet容器的功能不全。因此,除了非常简单的servlet之外,它不能运行其它的servlet,也不调用servlet的 init
和 destroy
方法。相反,它做了以下工作:
ServletRequest
对象和一个 ServletResponse
对象。 ServletRequest
和 ServletResponse
对象。 service
方法,并传递 ServletRequest
和 ServletResponse
对象。 注意,在此servlet容器中,每次请求servlet时都会加载servlet类。
第一个应用程序包含六个类:
该应用程序的入口点(静态 main
方法)在HttpServer1类中。 main
方法创建HttpServer1的实例并调用其 await
方法。 await
方法等待HTTP请求,为每个请求创建一个Request对象和一个Response对象,然后根据请求是针对静态资源还是Servlet将它们分派到StaticResourceProcessor实例或ServletProcessor实例。
Constants类包含从其它类引用的 static final WEB_ROOT
。WEB_ROOT指示PrimitiveServlet的位置以及此容器可以服务的静态资源。
HttpServer1实例一直等待HTTP请求,直到收到关闭命令为止。发出关闭命令的方式与第1章中的相同。
以下各节将讨论应用程序中的每个类。
这个应用程序中的HttpServer1类与第1章中的简单Web服务器应用程序中的HttpServer类类似。但是,在这个应用程序中,HttpServer1类可以服务于静态资源和servlets。要请求一个静态资源,你可以在浏览器的地址或URL框中输入以下格式的URL。
http://machineName:port/staticResource 复制代码
这正是你在第1章中在Web服务器应用程序中请求静态资源的方式。
要请求servlet,请使用以下URL:
http://machineName:port/servlet/servletClass 复制代码
因此,如果你在本地使用浏览器来请求名为PrimitiveServlet的servlet,请在浏览器的“地址”或“URL”框中输入以下URL:
http://localhost:8080/servlet/PrimitiveServlet 复制代码
这个servlet容器可以服务于PrimitiveServlet。但是,如果你调用另一个servlet,即ModernServlet,这个servlet容器会抛出一个异常。在后面的章节中,你将会构建可以处理这两者的应用程序。
清单2.2中显示了HttpServer1类。
清单2.2:HttpServer1类的 await
方法
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请求,直到发出关机命令,让人想起了第1章中的 await
方法。清单2.2中的 await
方法和第一章中的 await
方法的区别在于,在清单2.2中,请求可以被分派到StaticResourceProcessor或者ServletProcessor。如果URI包含字符串/servlet/,则请求被转发到后者。否则,该请求将传递到StaticResourceProcessor实例。
一个servlet的 service
方法会从servlet容器中接收一个 javax.servlet.ServletRequest
实例和一个 javax.servlet.ServletResponse
实例。也就是说,对于每个HTTP请求,servlet容器必须构造一个 ServletRequest
对象和一个 ServletResponse
对象,并将它们传递给它所服务的servlet的 service
方法。