在本篇我们主要讲两个方面内容,Servlet生命周期和Servlet请求转发。其实在之前我们已经大致介绍过Servlet生命周期的整个过程,只不过没有系统化提及,而在本篇我们会系统的阐述。
Servlet的生命周期
Servlet运行在Servlet容器中,其生命周期由容器来管理。Servlet的生命周期通过javax.servlet.Servlet接口中的init()、service()和destroy()方法来表示。Servlet的生命周期包含了下面4个阶段:
(1)加载和实例化
Servlet容器负责加载和实例化Servlet。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类,成功加载后,容器创建Servlet的实例。因为容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。
(2)初始化
在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常来通知容器。ServletException异常用于指明一般的初始化失败,例如没有找到初始化参数;而UnavailableException异常用于通知容器该Servlet实例不可用。例如,数据库服务器没有启动,数据库连接无法建立,Servlet就可以抛出UnavailableException异常向容器指出它暂时或永久不可用。
(3)请求处理
Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常。如果UnavailableException异常指示了该实例永久不可用,Servlet容器将调用实例的destroy()方法,释放该实例。此后对该实例的任何请求,都将收到容器发送的HTTP 404(请求的资源不可用)响应。如果UnavailableException异常指示了该实例暂时不可用,那么在暂时不可用的时间段内,对该实例的任何请求,都将收到容器发送的HTTP 503(服务器暂时忙,不能处理请求)响应。
(4)服务终止
当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。
在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。下面给出Servlet整个生命周期过程的UML序列图,如图1所示。
图1 Servlet生命周期的UML序列图
如果需要让Servlet容器在启动时即加载Servlet,可以在web.xml文件中配置<load-on-startup>元素。具体配置方法后文会详细介绍。
请求转发
生活中,110报警中心收到群众报警电话,根据报警的内容(报警地点、事情紧急程度),将报警请求交由不同的派出所进行处理。在这里,110报警中心充当了一个调度员的角色,它负责将各种报警请求转发给实际的处理单位。这种处理模型的好处是:
1)给人们提供了统一的报警方式(拨打110)。
2)报警中心可以根据报案人所处的位置、派出所的地理位置与人员状况,合理调度资源,安排就近的派出所及时出警。
3)报警中心并不处理具体的案件,缩短了对报警请求的响应时间。
在Web应用中,这种处理模型也得到了广泛的应用,这种调度员的角色通常由Servlet来充当,这样的Servlet叫做控制器(Controller)。在控制器中,可以将请求转发(request dispatching)给另外一个Servlet或者JSP页面,甚至是静态的HTML页面,然后由它们进行处理并产生对请求的响应。要完成请求转发,就要用到javax.servlet.RequestDispatcher接口。
RequestDispatcher接口
RequestDispatcher对象由Servlet容器创建,用于封装一个由路径所标识的服务器资源。利用RequestDispatcher对象,可以把请求转发给其他的Servlet或JSP页面。在RequestDispatcher接口中定义了两种方法。
public void forward(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
该方法用于将请求从一个Servlet传递给服务器上的另外的Servlet、JSP页面或者是HTML文件。在Servlet中,可以对请求做一个初步的处理,然后调用这个方法,将请求传递给其他的资源来输出响应。要注意的是,这个方法必须在响应被提交给客户端之前调用,否则的话,它将抛出IllegalStateException异常。在forward()方法调用之后,原先在响应缓存中的没有提交的内容将被自动清除。
public void include(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
获取RequestDispatcher对象
有三种方法可以用来获取RequestDispatcher对象。一是利用ServletRequest接口中的getRequestDispatcher()方法:
public RequestDispatcher getRequestDispatcher(java.lang.String path)
另外两种是利用ServletContext接口中的getNamedDispatcher()和getRequestDispatcher()方法:
public RequestDispatcher getRequestDispatcher(java.lang.String path) public RequestDispatcher getNamedDispatcher(java.lang.String name)可以看到ServletRequest接口和ServletContext接口各自提供了一个同名的方法getRequestDispatcher(),那么这两个方法有什么区别呢?
两个getRequestDispatcher()方法的参数都是资源的路径名,不过ServletContext接口中的getRequestDispatcher()方法的参数必须以斜杠(/)开始,被解释为相对于当前上下文根(context root)的路径。例如:/myservlet是合法的路径,而../myservlet是不合法的路径;而ServletRequest接口中的getRequestDispatcher()方法的参数不但可以是相对于上下文根的路径,而且可以是相对于当前Servlet的路径。例如:/myservlet和myservlet都是合法的路径,如果路径以斜杠(/)开始,则被解释为相对于当前上下文根的路径;如果路径没有以斜杠(/)开始,则被解释为相对于当前Servlet的路径。ServletContext接口中的getNamedDispatcher()方法则是以在部署描述符中给出的Servlet(或JSP页面)的名字作为参数。
调用ServletContext对象的getContext()方法可以获取另一个Web应用程序的上下文对象,利用该上下文对象调用getRequestDispatcher()方法得到的RequestDispatcher对象,可以将请求转发到另一个Web应用程序中的资源。但要注意的是,要跨Web应用程序访问资源,需要在当前Web应用程序的<context>元素的设置中,指定crossContext属性的值为true。
一个请求转发的实例
好了,我们继续来看一个例子,我们编写一个PortalServlet,该Servlet首先判断用户是否已经登录,如果未登录,则调用RequestDispatcher接口的include()方法,将请求转发给LoginServlet,LoginServlet在响应中发送登录表单;如果用户已经登录,则调用RequestDispatcher接口的forward()方法,将请求转发给WelcomeServlet,对用户显示响应信息。和上篇文章一样,我们还是在MyDemo这个Web项目中编写程序。下面是PortalServlet的代码:
package com.shan.web; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class PortalServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out=response.getWriter(); out.println("<html><head><title>"); out.println("登录页面"); out.println("</title></head><body>"); String name=request.getParameter("username"); String pwd=request.getParameter("passwd"); if("shan".equals(name) && "1234".equals(pwd)) { ServletContext context=getServletContext(); RequestDispatcher rd=context.getRequestDispatcher("/welcome.do"); rd.forward(request,response); } else { RequestDispatcher rd=request.getRequestDispatcher("login.do"); rd.include(request,response); } out.println("</body></html>"); out.close(); } public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { doGet(request,response); } }doGet()方法中,首先判断用户名和密码,如果存在,则利用上下文对象的getRequestDispatcher()方法得到RequestDispatcher对象,传入的路径参数必须以斜杠(/)开始,然后利用forward()方法将请求转发给welcome.do这个Servlet处理。(注:在forward()方法调用之后,我们之后输出的HTML代码将自动被清除,执行的控制权将交给welcome,在doGet()方法中剩余的代码也不再执行。)
如果如果用户没有登录或者输入了错误的用户名或密码,则利用请求对象的getRequestDispatcher()方法得到RequestDispatcher对象,传入的路径参数没有以斜杠(/)开始,表示相对于当前Servlet的路径,然后调用include()方法将请求转发给login.do这个Servlet处理,当login.do对请求处理完毕后,执行的控制权回到PortalServlet,将继续执行之后的代码。
LoginServlet代码如下:
package com.shan.web; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { response.setContentType("text/html;charset=gb2312"); PrintWriter out=response.getWriter(); out.println("<form method=post action=portal.do>"); out.println("<table>"); out.println("<tr>"); out.println("<td>请输入用户名:</td>"); out.println("<td><input type=text name=username></td>"); out.println("</tr>"); out.println("<tr>"); out.println("<td>请输入密码:</td>"); out.println("<td><input type=password name=passwd></td>"); out.println("</tr>"); out.println("<tr>"); out.println("<td><input type=reset value=重填></td>"); out.println("<td><input type=submit value=登录></td>"); out.println("</tr>"); out.println("</table>"); out.println("</form>"); } public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { doGet(request,response); } }在PortalServlet中,已经输出了<html>、<head>、<title>和<body>元素,所以在这里就不需要再输出这些元素了,这里输出的表单将嵌入到<body>元素的开始标签和结束标签之间。要注意,在doGet()方法的最后,不要调用out.close()关闭输出流对象,因为一旦关闭,响应将被提交,那么在PortalServlet中调用include()方法之后的代码将不再有效。
package com.shan.web; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class WelcomeServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { response.setContentType("text/html;charset=gb2312"); PrintWriter out=response.getWriter(); String username=request.getParameter("username"); String welcomeInfo="Hello, "+ username; out.println("<html><head><title>"); out.println("Welcome Page"); out.println("</title></head>"); out.println("<body>"); out.println(welcomeInfo); out.println("</body></html>"); out.close(); } public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { doGet(request,response); } }程序很简单,就是取出username,然后将它输出。
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar -d classes src\com\shan\web\PortalServlet.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar -d classes src\com\shan\web\LoginServlet.java
javac -classpath D:\apache-tomcat-7.0.33\lib\servlet-api.jar -d classes src\com\shan\web\WelcomeServlet.java编译完成后,将三个.class文件复制到D:\apache-tomcat-7.0.33\webapps\MyDemo目录下的WEB-INF文件夹中对应的目录中去(即classes\com\shan\web文件夹中)。之后编辑web.xml文件,修改后如下:
<?xml version='1.0' encoding='utf-8'?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> <description> My Demo. </description> <servlet> <servlet-name>selectServlet</servlet-name> <servlet-class>com.shan.web.MyDemoServlet</servlet-class> </servlet> <servlet> <servlet-name>countServlet</servlet-name> <servlet-class>com.shan.web.CounterServlet</servlet-class> </servlet> <servlet> <servlet-name>PortalServlet</servlet-name> <servlet-class>com.shan.web.PortalServlet</servlet-class> </servlet> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.shan.web.LoginServlet</servlet-class> </servlet> <servlet> <servlet-name>WelcomeServlet</servlet-name> <servlet-class>com.shan.web.WelcomeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>selectServlet</servlet-name> <url-pattern>/SelectColor.do</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>countServlet</servlet-name> <url-pattern>/count.do</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>PortalServlet</servlet-name> <url-pattern>/portal.do</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login.do</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>WelcomeServlet</servlet-name> <url-pattern>/welcome.do</url-pattern> </servlet-mapping> </web-app>在浏览器中输入http://localhost:8080/MyDemo/portal.do,输入用户名和密码(shan和1234),就可以看到欢迎信息Hello, shan。
sendRedirect()和forward()方法的区别
HttpServletResponse接口的sendRedirect()方法和RequestDispatcher接口的forward()方法都可以利用另外的资源(Servlet、JSP页面或HTLM文件)来为客户端进行服务,但是这两种方法有着本质上的区别。下面分别给出sendRedirectt()方法和forward()方法的工作原理。
sendRedirect()方法的工作原理/交互过程如下:
1)浏览器访问Servlet1。
2)Servlet1想让Servlet2为客户端服务。
3)Servlet1调用sendRedirect()方法,将客户端的请求重定向到Servlet2。
4)浏览器访问Servlet2。
5)Servlet2对客户端的请求做出响应。
从上述交互过程可以看出,调用sendRedirect()方法,实际上是告诉浏览器Servlet2所在的位置,让浏览器重新访问Servlet2。调用sendRedirect()方法,会在响应中设置Location响应报头。要注意的是,这个过程对于用户来说是透明的,浏览器会自动完成新的访问。浏览器网址栏显示的URL是重定向之后的URL。
forward()方法的工作原理/交互过程如下:
1)浏览器访问Servlet1。
2)Servlet1想让Servlet2对客户端的请求进行响应,于是调用forward()方法,将请求转发给Servlet2进行处理。
3)Servlet2对请求做出响应。
从上述交互过程可以看出,调用forward()方法,对浏览器来说是透明的,浏览器并不知道为其服务的Servlet已经换成Servlet2了,它只知道发出了一个请求,获得了一个响应。浏览器网址栏显示的URL始终是原始请求的URL。
sendRedirect()方法和forward()方法还有一个区别,那就是sendRedirect()方法不但可以在位于同一主机上的不同Web应用程序之间进行重定向,而且可以将客户端重定向到其他服务器上的Web应用程序资源。
转载请注明出处:http://blog.csdn.net/iAm333