Servlet&JSP的那些事儿(四)

在本篇我们主要讲两个方面内容,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所示。

Servlet&JSP的那些事儿(四)_第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

该方法用于在响应中包含其他资源(Servlet、JSP页面或HTML文件)的内容。和forward()方法的区别在于:利用include()方法将请求转发给其他的Servlet,被调用的Servlet对该请求做出的响应将并入原先的响应对象中,原先的Servlet还可以继续输出响应信息。而利用forward()方法将请求转发给其他的Servlet,将由被调用的Servlet负责对请求做出响应,而原先Servlet的执行则终止。

获取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()方法之后的代码将不再有效。
我们还需要编写一个WelcomeServlet程序,代码如下:

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,然后将它输出。
程序编写好之后,我们需要编译程序,win+r,输入cmd,然后切换到工程所在目录。逐条输入以下语句:

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

你可能感兴趣的:(Servlet&JSP的那些事儿(四))