在Web服务器端处理用户请求的时候,会有需要多个Web组件配合才能完成的情况。一个Web组件(Servlet/JSP)将未完成的处理通过容器转交给另外一个Web组件继续完成,这个转交的过程叫做转发。
常见情况是Servlet负责获取数据,然后将数据转交给JSP进行展现。
实现转发过程可遵循以下三个步骤:
步骤一、绑定数据到request对象
在转交的过程中一定会有数据的传递,并且涉及到的Web组件都是针对同一个请求,所以利用request来保存共同处理的数据不仅仅能让数据共享,也能够随着请求响应的结束而销毁,不会占用服务器更多的资源。使用如下代码可以实现数据的绑定:
request.setAttribute(String name,Object obj);
setAttribute()方法实现数据绑定,与其对应的还有getAttribute()方法获取绑定的数据,removeAttribute()方法移除绑定的数据。
步骤二、获得转发器
使用如下代码可以获取到转发器,用于说明转交的下一个组件的路径:
RequestDispatcher rd = request.getRequestDispatcher(String uri);
步骤三、实现转发
使用转发器完成转发的动作,因下一个Web组件要针对同一个请求和响应继续完成后续的工作,所以在转发时要将本次的请求和响应对象作为参数传给下一个Web组件。实现代码如下所示:
rd.forward(request,response);
其中步骤二和步骤三可以合并为一行代码:
request.getRequestDispatcher(String uri).forward(request,response);
图 – 1
如图- 1所示,是客户端向服务器请求查看员工列表信息页面时服务器端的处理过程。
1.请求到达服务器(2.根据请求信息创建request和response对象(3.根据请求资源路径找到对应的Servlet执行处理(4.Servlet在处理过程中从数据库获取到结果信息(5.Servlet将结果信息绑定在request对象中(6.Servlet通知容器将request和response对象转交给list.jsp页面继续执行对请求的响应(7. list.jsp页面被调用并执行时从传递过来的request对象中获取绑定的数据生成结果页面(8.服务器将list.jsp页面的执行结果返回给客户端。
在整个的处理过程中,从ActionServlet到list.jsp的这个转换过程就是转发,也就是图示中的6这个步骤。控制这个过程的是服务器,同时ActionServlet和list.jsp共享了同一组请求和响应,两个Web组件共同协作完成了客户端的一次请求。对于客户端来讲,只发了一次请求,并且客户端浏览器也并不知道在服务器端是由两个组件配合提供的响应。
转发过程发生在服务器端,客户端只发送了一个请求,虽然请求到达服务器的指定位置后被容器控制着传到了第二个组件继续完成工作,但浏览器并不知道这个过程,所以转发之后地址栏地址不会发生变化。
转发的目的地必须是同一个应用内部的某个地址,决不能跳出应用。毕竟这个转交过程由容器实现,容器只有访问本应用的权限,而不能控制请求到达应用以外的位置。
转发过程中涉及到的所有Web组件共享同一个request对象和response对象,数据的传递和共享就依赖request对象。
注意:在forward之后的其他语句还是会继续执行完的,只要不报异常。
重定向:浏览器发送请求到容器访问A,A可以发送一个状态码302和一个Location消息头到浏览器,于是浏览器会立即向Location发新的请求。
转发:浏览器发送请求到容器访问A,A可以通知容器去调用B。转发所涉及的各个Web组件会共享同一个request和response对象;而重定向不行。
说明:当请求到达容器,容器会创建request对象和response对象。当响应发送完毕,容器会立即删除request对象和response对象。即,request对象和response对象的生存时间是一次请求与响应期间。
转发之后,浏览器地址栏的地址不变,重定向会变。转发的地址必须是同一个应用内部某个地址,而重定向没有这个限制。转发是一件事情未做完,调用另外一个组件继续做;而重定向是一件事情已经做完,再做另外一件事情。
转发和重定向的执行过程如图- 2所示:
图 - 2
在编写Web类型的应用时,如果程序运行发生了异常就会返回该异常的信息到客户端的浏览器上显示出来,这些信息往往暴露了服务器的一些重要结构,所以要避免异常信息输出到客户端。一般有两种方式来处理异常,编程式的异常处理和声明式的处理。
编程式的异常处理就是在程序中捕获到异常时,使用转发跳转到指定页面进行提示说明。
代码结构如下所示:
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String uri = request.getRequestURI(); String action = uri.substring( uri.lastIndexOf("/"),uri.lastIndexOf(".")); if (action.equals("/list")) { try { // 使用dao访问数据库 //交给jsp来完成 request.getRequestDispatcher("list3.jsp").forward(request, response); } catch (Exception e) { e.printStackTrace(); //使用转发的方式来处理异常。 request.setAttribute("error_msg","系统繁忙,稍后重试"); request.getRequestDispatcher("error.jsp").forward(request, response); } } }
声明式的处理主要依靠容器自己来完成,即产生异常时抛出给容器,但不能让容器将这些底层信息返回给客户端,所以需要在web.xml文件中添加配置说明,通知容器在捕获到异常时应该将什么样的页面返回给客户端。一旦使用声明式处理方式,则该项目下的任意一个文件错误或异常,都会跳到指定的错误处理页面。
具体的实现步骤如下:
步骤一、在代码中捕获到异常直接抛出(注意异常类型,必须是ServletException)
代码结构如下图所示:
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String uri = request.getRequestURI(); String action = uri.substring( uri.lastIndexOf("/"),uri.lastIndexOf(".")); if (action.equals("/list")) { try { // 使用dao访问数据库 //交给jsp来完成 request.getRequestDispatcher("list3.jsp").forward(request, response); } catch (Exception e) { e.printStackTrace(); //将异常抛给容器来处理 throw new ServletException(e); } } } <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>ActionServlet</servlet-name> <servlet-class>web.ActionServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ActionServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- 配置错误处理页面 --> <error-page> <exception-type>javax.servlet.ServletException</exception-type> <location>/error.jsp</location> </error-page> </web-app>
程序级的异常最好选择编程式的处理方法,利用转发到达指定的错误处理页面。
系统级别的异常最好选择声明式的处理方法。
在JSP页面或Servlet中需要从一个组件到另一个组件的跳转,通常以链接、表单提交、重定向、转发的形式来完成,其中对目标位置的标识即路径。
相对路径指的是相对于当前位置,为了到达目标文件要经过的路径。书写格式上不以“/“开头,如果为了退至上一级目录可以”../“开头。
图 – 3
在图-3中,从index.jsp访问a2.jsp,使用相对路径,具体值为“a/a2.jsp“。
从a1.jsp访问index.jsp,使用相对路径,具体值为“../index.jsp“。
绝对路径指的是,不管当前文档所在的位置在应用的哪里,都会从一个固定的点出发,然后构建到达目标文件所需要经过的路径。通常绝对路径在书写格式上以“/”开头。这个固定的点可能是应用名,也可能是应用名之后。
图 - 4
在图-4中,从index.jsp访问a2.jsp,使用绝对路径,非转发的情况下具体值为“/appName/a/a2.jsp“。
从a1.jsp访问index.jsp,使用绝对路径,非转发的情况下具体值为“/appName/index.jsp“。
对于以下四种常用的需要写路径的代码,在编写绝对路径时起始点是不同的,具体区别如下:
链接地址,表单提交,重定向由于都是浏览器发出的请求,为了到达指定的应用内的资源,所以斜杠后面从应用名开始写;转发位于服务器端,已经在具体的应用内部了,所以斜杠后面从应用名之后开始写。
由于Web应用的真实部署名称和测试时的名称未必一样,所以使用绝对路径时如果应用名写成定值则会带来变更的麻烦。为了获取当前应用的实际部署名称可以使用如下代码来动态获取。
String request.getContextPath();