3.9 Servlet间协作

当Web容器接收到客户端的请求后,它负责创建HttpRequest对象和HttpResponse对象,然后将这两个对象以参数的形式传递给与请求URL地址相关联的Servlet的service方法进行处理。但对于复杂的处理过程,仅仅通过一个Servlet来实现对于请求的处理往往比较困难,这时经常需要几个Servlet间共同协作完成对于请求的处理,也就是说,在一个Servlet处理过程中或处理完毕后,将客户端的请求传递到另外一个Servlet来处理,这种像接力赛似的过程称为请求指派。为实现请求指派,Servlet规范定义了一个接口:javax.servlet.requestdispatcher。

Requestdispatcher封装了到同一Web应用内的另外一个资源的引用。可以通过调用Requestdispatcher的forword方法将请求传递到其他资源,或者调用Requestdispatcher的include方法将其他资源对此请求的响应包含进来。

下面通过一个简单的登录系统来演示如何利用Requestdispatcher对象来实现Servlet间的协作。系统后台功能主要由三个Servlet来实现。Main为主控Servlet,用来实现登录验证功能,并根据验证结果将请求转发到LoginSuccess或LoginFail, LoginSuccess处理登录成功条件下的请求处理,LoginFail处理登录失败条件下的请求处理。

首先生成登录信息提交页面。代码如程序3-29所示。

程序3-29:dl.html

    <html lang='zh'>
      <head>
    <title>登录</title>
    </head>
    <body bgcolor="#FFFFFF">
    <center>欢迎登录系统</center>
    <form name="login" method="post"  action="Main">
    <label>用户名:</label>
    <input type=text name="userID" value="">
    <label>密码:</label>
    <input type=password name="password" value="">
    <input type="submit" name="tj" value ="提交" ></input>
    <input type="reset" name="reset" ></input>
    </form>
    </body>
    </html>

程序说明:页面模拟一个系统登录页面,用户名和密码信息通过表单提交到后台的Servlet处理。

下面生成主控Servlet Main。代码如程序3-30所示。

程序3-30:Main.java

    package example.servlet;
    …
    @WebServlet(name=" Main ", urlPatterns={"/ Main "})
    public class Main extends HttpServlet {
        protected void processRequest(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
            String userID=request.getParameter("userID");
            if(userID==null)userID="";
            String password=request.getParameter("password");
            if(password==null)password="";
            if((userID.equals("guest")&&password.equals("guest"))){
              RequestDispatcher dispatcher =
                      request.getRequestDispatcher("LoginSuccess");
              dispatcher.forward(request, response);
            } else{
              RequestDispatcher dispatcher =
                      request.getRequestDispatcher("LoginFail");
              dispatcher.forward(request, response);
            }
          }
        …
       }

程序说明:首先调用Request对象的getParameter方法来获取登录页面提交的信息,然后根据提交的信息进行登录验证(为表述简单,这里只是用它来与固定值guest进行对比)。通过调用HttpServletRequest对象的getRequestDispatcher方法来得到其他Web组件对应的RequestDispatcher对象,其中getRequestDispatcher方法的参数为被请求指派资源在部署描述文件中的URL地址。最后调用RequestDispatcher对象的forward方法,将请求导向其他Servlet组件。

下面生成登录成功条件下的指派资源Servlet LoginSuccess和登录失败条件下的指派资源Servlet LoginFail,代码如程序3-31和程序3-32所示。

程序3-31:LoginSuccess.java

    package example.servlet;
    …
    @WebServlet(name=" LoginSuccess ", urlPatterns={"/ LoginSuccess "})
    public class LoginSuccess extends HttpServlet {
        protected void processRequest(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
          response.setContentType("text/html; charset=UTF-8");
          PrintWriter out = response.getWriter();
          String name=request.getParameter("userID");
          out.println("<html>");
          out.println("<head>");
          out.println("<title>登录成功</title>");
          out.println("</head>");
          out.println("<body>");
          out.println("<h1>欢迎!"+name+"您已成功登录系统...</h1>");
            out.println("</body>");
            out.println("</html>");
            out.close();
        }
        …
        }

程序说明:作为登录验证成功的响应,显示一条欢迎信息。由于RequestDispatcher对象forward方法将前端的请求对象request传递到本Servlet,因此,依然可以调用request对象的getParameter("userID")方法来获取用户的登录ID。用户请求对象的生命周期直到服务器端向客户端返回响应时才宣告结束。

程序3-32:LoginFail.java

    package example.servlet;
    …
    @WebServlet(name=" LoginFail ", urlPatterns={"/ LoginFail "})
    public class LoginFail extends HttpServlet {
     protected void processRequest(HttpServletRequest request, HttpServletResponse
     response)
        throws ServletException, IOException {
          response.setContentType("text/html; charset=UTF-8");
          RequestDispatcher dispatcher = request.getRequestDispatcher
          ("login.html");
          dispatcher.include(request, response);
            }
      …
        }

程序说明:作为登录验证失败的响应,调用RequestDispatcher对象的include方法,将登录页面作为响应的一部分输出到客户端显示。

保存程序并重新发布Web应用,打开IE浏览器,在地址栏中输入http:/localhost:8080/Chapter3/dl.html,得到如图3-34所示的运行结果页面。在“用户名”文本框输入guest,在“密码”文本框输入guest,单击“提交”按钮,则得到如图3-35所示的页面,可以看到请求被指派给了Servlet LoginSuccess。单击浏览器上的“后退”按钮,分别在“用户名”文本框和“密码”文本框输入其他数据并再次提交,则得到如图3-36所示的运行结果页面,可以看到请求被指派给了Servlet LoginFail。仔细观察图3-35和图3-36中浏览器的地址栏显示,可以看到地址栏中显示的是同一个地址,这是因为请求指派是在服务器端进行的,因此在客户端的浏览器上觉察不到。

图3-34 登录页面

图3-35 登录成功页面

图3-36 登录失败页面

在3.5节中了解到可以通过HttpServletResponse的sendRedirect实现请求重定向,那么它与调用RequestDispatcher的forward方法有什么区别呢?

首先,从操作的本质上,RequestDispatcher的forward是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;而HttpServletResponse的sendRedirect是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求连接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。其次,从性能上,前者仍旧是在同一次请求处理过程中,后者是结束第一次请求,由浏览器发起一次新的请求,因此,RequestDispatcher的forward更加高效。在条件许可时,开发人员尽量使用RequestDispatcher的forward方法。

RequestDispatcher的forward也有局限,它只能转到Web应用内部的资源,而在有些情况下,比如,需要跳转到其他服务器上的某个资源时,则必须使用HttpServletResponse的sendRedirect方法。

Servlet中有两种方式获得转发对象(RequestDispatcher):一种是通过HttpServletRequest的getRequestDispatcher方法获得,一种是通过ServletContext的getRequestDispatcher方法获得。

重定向的方法只有一种:HttpServletResponse的sendRedirect方法。

这三个方法的参数都是一个URL形式的字符串,但在使用相对路径或绝对路径上有所区别。

(1)HttpServletResponse.sendRedirect(String)

参数可以指定为相对路径、绝对路径或其他Web应用。假设以http://localhost/myApp/cool/from.do作为起点。

若采用相对路径,代码为response.sendRedirect(" foo/to.do "),则容器相对于原来请求URL的目录加上sendRedirect参数来生成完整的URL:http://localhost/myApp/cool/foo/to.do

若采用绝对路径,代码为response.sendRedirect(" / foo/to.do "),容器相对于Web服务器本身加sendRedirect参数生成完整的URL:http://localhost/foo/to.do

若参数为其他Web应用资源,如response.sendRedirect("http://www.javaeye.com"),则容器直接定向到该URL。

(2)HttpServletRequest.getRequestDispatcher(String)

参数可以指定为相对路径或绝对路径。相对路径情况下生成的完整URL与重定向方法相同。绝对路径与重定向不同,容器将相对于Web应用的根目录加参数生成完整的URL,即:request.getRequestDispatcher("/foo/to.do")生成的URL是http://localhost/myApp/foo/to.do

(3)ServletContext.getRequestDispatcher(String)

参数只能指定为绝对路径,生成的完整URL与HttpServletRequest.getRequestDispatcher (String)相同。