标签:
Servlet对象由Servlet容器创建,并且Servlet对象的service()方法也由容器调用。一个Servlet对象可否直接调用另一个Servlet对象的service()方法呢?答案是否定的,因为一个Servlet对象无法获得另一个Servlet对象的引用。
在旧版的Servlet API中,ServletContext接口中的getServlet(Stringname)方法能根据参数给定的名字返回相应的Servlet对象的引用。从Servlet API2.1开始,该方法被放弃。对于支持Servlet API2.1或者以上版本的Servlet容器,会使得ServletContext实现类的getServlet(String name)方法总是返回null。因此,一个Servlet对象无法再获得另一个Servlet对象的引用。
Web应用在响应客户端的一个请求时,有可能响应过程很复杂,需要多个Web组件共同协作,才能生成响应结果。尽管一个Servlet对象无法直接调用另一个Servlet对象的service()方法,但Servlet规范为Web组件之间的协作提供了两种途径。
· 请求转发:Servlet(源组件)先对客户请求做一些预处理操作,然后把请求转发给其他Web组件(目标组件)来完成包括生成响应结果在内的后续操作。
· 包含:Servlet(源组件)把其他Web组件(目标组件)生成的响应结果包含到资自身的响应结果中。
请求转发与包含具有以下共同特点:
· 源组件和目标组件处理的都是同一个客户请求,源组件和目标组件共享同一个ServletRequest对象和ServletResponse对象。
· 目标组件都可以为Servlet、JSP或HTML文档。
· 都依赖javax.servlet.RequestDispatcher接口。
javax.servlet.RequestDispatcher接口表示请求分发器,它有两个方法。
public void forward(ServletRequest request,ServletResponse response)
throws ServletException, IOException;
把请求转发给目标组件。
public void include(ServletRequest request,ServletResponse response)
throws ServletException, IOException;
包含目标组件的响应结果。
Servlet组件调用RequestDispatcher的forword()或include()方法时,都要把当前的ServletRequest对象和ServletResponse对象作为参数传给forward()或include()方法,这使得源组件和目标组件共享同一个ServletRequest对象和ServletResponse对象。
Servlet可通过两种方式得到ResquestDispatcher对象:
调用ServletContext的getRequestDispatcher(Stringpath)方法,path参数指定目标组件的路径。
调用ServletRequest的getRequestDispatcher(Stringpath)方法,path参数指定目标组件的路径。
以上两种方式的区别在于,前者的path路径必须为绝对路径,而后者的path参数既可以为绝对路径,也可以为相对路径。所谓绝对路径,就是指以符号“/”开头的路径,“/”表示当前Web应用的URL入口。所谓相对路径,就是相对于当前源Servlet组件的路径,不以符号“/”开头。
请求转发的处理流程:
(1) 清空用于存放响应正文数据的缓冲区。
(2) 如果目标组件为Servlet或JSP,就调用它们的service()方法,把该方法产生的响应结果发送到客户端;如果目标组件为文件系统中的静态HTML文档,就读取文档中的数据把它发送到客户端。
请求转发特点:
(1) 由于forword()方法先清空用于存放响应正文的缓冲区,因此Servlet源组件生成的响应结果不会被发送到客户端,只有目标组件生成的响应结果才会被发送到客户端。
(2) 如果源组件在进行请求转发之前,已经提交了响应结果(例如调用ServletResponse的flushBuffer()方法,或者调用与ServletResponse关联的输出流的close()方法),那么forward()方法抛出IllegalStateException。为了避免该异常,不应该在源组件中提交响应结果。
表现:
地址不会变,转发前对request对象的设置能够传递到转发后的Web组件。请求转发是一个链式的,中间无论转发过多少次始终是同一个请求对象。
RequestDispatcher对象的include()方法的处理流程如下:
(1) 如果目标组件为Servlet或JSP,就调用他们的相应的service()方法,把该方法产生的响应正文添加到源组件的响应结果中;如果目标组件为HTML文档,就直接把文档的内容添加到源组件的响应结果中。
(2) 返回到源组件的服务方法中,继续执行后续代码块。
包含与请求转发相比,包含有以下特点:
(1) 源组件与被包含的目标组件的输出数据都会被添加到响应结果中。
(2) 在目标组件中对响应状态码或者响应头所做的修改都会被忽略。
HTTP协议规定了一种重定向机制,重定向的运作流程如下:
(1) 用户在浏览器端输入特定URL,请求访问服务器端的某个组件。
(2) 服务器端的组件返回一个状态码为302的响应结果,该响应结果的含义为:
让浏览器端再请求访问另一个Web组件,在响应结果中提供了另一个Web组件的URL。另一个Web组件有可能在同一个Web服务器上,也有可能不再同一个Web服务器上。
(3) 当浏览器端接收到这种响应结果后,再立即自动请求访问另一个Web组件。
(4) 浏览器端接收到另一个Web组件的响应结果。
重定向使用的方法是sendRedirect(String location),该方法具有以下特点:
(1) Servlet源组件生成的响应结果不会被发送到客户端。response.sendRedirect(String location)方法一律返回状态码为302的响应结果。浏览器端接收到这种响应结果后,再立即请求房屋内重定向的目标Web组件,客户端最后接收到的目标Web组件的响应结果。
(2) 如果源组件在进行重定向之前,已经提交了响应结果(例如调用ServletResponse的flushBuffer()方法,或者调用与ServletResponse关联的输出流的close()方法),那么sendRedirect()方法会抛出IllegalStateException。为了避免该异常,不应该在源组件中提交响应结果。
(3) 在Servlet源组件中调用response.sendRedirect()方法之后的代码块也会被执行。
(4) 源组件和目标组件不共享同一个ServletRequest对象,因此不共享请求范围内的共享数据。
(5) 对于response.sendRedirect(String location)方法中的参数location,如果以“/”开头,表示相对于当前服务器根路径的URL,如果以http://开头,表示一个完整的URL。
(6) 目标组件不必是同一个服务器上的同一个Web应用中的组件,它可以是Internet上的任意一个有效的网页。
在一个Servlet容器进程内可以同时运行多个Web应用,那么在这些Web应用之间可否进行通信呢?答案是肯定的。每个Web应用都有一个ServletContext大总管。对于Web应用A中的Servlet,只要得到了Web应用B的ServletContext对象,就能访问到Web应用B的各种资源。
ServletContext接口中的getContext(Stringuripath)方法用于得到其他Web应用的ServletContext对象。参数uripath指定其他Web应用的URL入口。
一个Web应用随意访问另一个Web应用的各种资源,可能会导致安全问题。因此,为了安全起见,多数Servlet容器实现可以让用户设置是否允许Web应用得到其他Web应用的ServletContext对象。在Tomcat中,<Context>元素的crossContext属性用于设置该选项:
如果crossContext属性为false,那么<Context>元素对应的Web应用无法得到其他Web应用的ServletContext对象。当这个Web应用中的Servlet调用getContext(Stringuripath)方法时,该方法总是返回null。crossContext属性的值默认为false。
如果crossContext属性为true,那么<Context>元素对应的Web应用可以得到其他Web应用的ServletContext对象。当这个Web应用中的Servlet调用getContext(String uripath)方法时,该方法返回参数uripath对应的其他Web应用的ServletContext对象。
public class MyServlet1 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { req.setAttribute("username", "Jack"); PrintWriter out = res.getWriter(); out.println("hello"); // out.close(); // res.flushBuffer(); req.getRequestDispatcher("MyServlet2").forward(req, res); // res.sendRedirect("MyServlet2"); // res.sendRedirect("http://blog.csdn.net/goskalrie");//ok // req.getRequestDispatcher("http://blog.csdn.net/goskalrie").forward(req, res);//HTTP Status 404 - /webdemo/http://blog.csdn.net/goskalrie } }
public class MyServlet2 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html;charset=utf-8"); PrintWriter out = res.getWriter(); out.println("<html><head><title>MyServlet2</title></head><body>" + "<h1>" + req.getParameter("username") + "</h1><br/>" + "<h1>" + req.getAttribute("username") + "</h1>" + "</body></html>"); out.close(); } }
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form name = "loginForm" method="GET" action="MyServlet1"> <table> <tr> <td><div align="right">用户名:</div></td> <td><input type="text" name="username"></td> </tr> <tr> <td><input type="submit" name="submit" value="登录"></td> <td><input type="reset" name="reset" value="重置"></td> </tr> </table> </form> </body> </html>
按照以下步骤进行实验
(1) 启动服务器,访问登录login.html,填写用户名后登录,可以看到浏览器地址栏地址变为http://localhost:8080/webdemo/MyServlet2,页面中打印出两个null,并没有读取到原始的请求中username属性,也没有读取到在源组件中处理的request中setAttribute(“username” , “Jack”)的值。
(2) 将MyServlet1中的res.sendRedirect("MyServlet2");代码注释掉,并将req.getRequestDispatcher("MyServlet2").forward(req,res);解开注释,重复步骤一,页面中打印出xiaoming和Jack两个名字,且地址栏的地址没有发生变化,只是在后面出现了请求的参数,但是实际的地址并没有改变。
重定向地址栏发生变化,在浏览器调试界面出现两个请求:
其中第一个请求的响应中包含了重定向的地址,如下图的最后:
在重定向方式中,请求是页面重新发送的,所以在请求中获取不到原始要提交的数据。即使在重定向前对原始请求做了处理,也是徒劳的。原因是请求对象和响应的对象的生命周期只存在于一次请求和响应中。客户端第一次发出请求,此时的请求为请求A,服务器接收到请求,生成对应的响应,然后程序遇到重定向语句,无论此时的请求和响应是什么,响应都会变成固定的:状态码为302,响应数据中包含重定向的组件地址。在浏览器响应之后,请求A和响应都消失了,然后浏览器自动的发出新的请求——请求B。在测试代码中含有两句代码以不同的方式进行请求外域名:
// res.sendRedirect("http://blog.csdn.net/goskalrie");//ok
// req.getRequestDispatcher("http://blog.csdn.net/goskalrie").forward(req,res);//HTTP Status 404 - /webdemo/http://blog.csdn.net/goskalrie
正如注释显示的结果一样,使用重定向成功访问到,而使用请求转发则发生了异常。
在实际的项目中可能有这样的情形:公司A和公司B共用一个服务器中的一个Web应用,但是各自有自己的数据库,公司A的员工A与公司B的员工B在登录该Web应用时,就需要从不同的数据库进行加载信息。假如,Web应用webdemo是共用的应用,包含了登录,信息展示等功能。Web应用webdemoA是公司A与数据库交互的Web应用。Web应用webdemoB是公司B与数据库交互的Web应用。上面的Web应用分布的模型应用应该说不少,比如,在某网站进行维护时,客户访问该网站,会收到“网站维护中”的页面信息提示,同样在下面的示例中将模拟该功能。按照分析所述,webdemo、webdemoA、webdemoB都在一个服务器中,使用重定向实现需求。
webdemo中包含登录页面:<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form name = "loginForm" method="GET" action="MyServlet1"> <table> <tr> <td><div align="right">用户名:</div></td> <td><input type="text" name="username"></td> </tr> <tr> <td><div align="right">公司:</div></td> <td><input type="text" name="company"></td> </tr> <tr> <td><input type="submit" name="submit" value="登录"></td> <td><input type="reset" name="reset" value="重置"></td> </tr> </table> </form> </body> </html>
登录页面同样请求MyServlet1,在MyServlet1中进行判断当前登录员工是A公司的还是B公司的,如果是公司A中的员工,重定向到webdemoA中的Web组件MyServlet2,如果是公司B中的员工,重定向到webdemoB中的Web组件MyServlet3,在webdemo中尝试连接webdemoA或webdemoB,连接失败则说明webdemoA或webdemoB在进行维护,无法访问,此时将请求转发至webdemo中的Web组件error.jsp。在实际的项目中不应该使用try-catch控制程序流程,在该实验中使用try-catch代替webservice,ajax等跨域请求的操作。实际的项目中,系统维护提示的实现方式有多种,这里仅是简单的模拟其中的一种情况。
在webdemo中判断员工所属公司并控制重定向的MyServlet1:public class MyServlet1 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String company = req.getParameter("company"); String username = req.getParameter("username"); URL urlA = new URL("http:localhost:8080/webdemoA"); try { URLConnection conA = urlA.openConnection(); conA.connect(); } catch (Exception e) { req.getRequestDispatcher("error.jsp").forward(req, res); } if (company.equals("companyA")) { res.sendRedirect("/webdemoA/MyServlet2?username=" + username + "&company=" + company); } else if (company.equals("companyB")) { res.sendRedirect("/webdemoB/MyServlet3?username=" + username + "&company=" + company); } } }在webdemoA及webdemoB中作为重定向的目标组件,从对应公司的数据库中读取数据并进行验证,处理的Web组件MyServlet2、MyServlet3:
public class MyServlet2 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html;charset=utf-8"); PrintWriter out = res.getWriter(); out.println("<html><head><title>webdemoA/MyServlet2</title></head><body>" + "<h1>" + req.getParameter("company") + "</h1><br/>" + "<h1>" + req.getParameter("username") + "</h1>该值为后台从数据库加载数据后校验后的结果" + "</body></html>"); out.close(); } }
public class MyServlet3 extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html;charset=utf-8"); PrintWriter out = res.getWriter(); out.println("<html><head><title>webdemoB/MyServlet3</title></head><body>" + "<h1>" + req.getParameter("company") + "</h1><br/>" + "<h1>" + req.getParameter("username") + "</h1>该值为后台从数据库加载数据后校验后的结果" + "</body></html>"); out.close(); } }以及提示服务器维护的error.jsp:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>error</title> </head> <body> <h1><%=request.getParameter("username") %>,Sorry. . . 系统维护中。</h1> </body> </html>
该试验的代码下载[测试代码.7z],下载后解压将得到三个*.war文件,直接将这三个文件放到tomcat安装目录下的webapps文件夹中即可按下面的步骤进行实验:
(1)将webdemo、webdemoA、webdemoB三个项目的打包文件放到tomcat安装目录下的webapps文件夹下,启动服务器访问localhost:8080/webdemo/login.html,在用户名中输入xiaoming,在公司中输入companyA(必须),点击登录,登录成功,观察地址栏,请求webdemoA/MyServlet2成功,地址栏显示http://localhost:8080/webdemoA/MyServlet2?username=xiaoming&company=companyA,页面显示:(2)访问localhost:8080/webdemo/login.html,在用户名中输入zhanghua,在公司中输入companyB(必须),点击登录,登录成功,观察地址栏,请求webdemoB/MyServlet3成功,地址栏显示http://localhost:8080/webdemoB/MyServlet3?username=zhanghua&company=companyB,页面显示:
(3)访问tomcat管理页面http://localhost:8080/manager/html,将webdemoA应用手动停止:
(4)再次访问localhost:8080/webdemo/login.html,输入用户名和公司后登录,此时将会出现系统维护中的提示:
在javax.servlet.RequestDispatcher接口中还有一个include()方法,此方法也可以将请求和响应进行转发,只不过如前面介绍中所述,include()方法向客户端返回的响应包含了请求链中所有的响应,如login.html向MyServlet1发出请求,includeMyservlet2,include MyServlet3,include hello.jsp。其中MyServlet1、MyServlet2、MyServlet3中简单的打印Servlet的名字,hello.jsp打印hello,页面显示结果:
MyServlet1
MyServlet2
MyServlet3
hello
无论是请求转发还是重定向,亦或是包含,都是将请求交给其他Web组件进行处理,有些组件是控制流程的,有些组件是数据处理的,在程序设计时,为了让组件的功能专一,就可能在控制流程类型的Web组件中不对客户端产生响应,而是让数据处理类的Web组件进行响应客户请求,而在控制流程类Web组件中仅仅判断当前请求该由哪一个数据处理类的Web组件进行处理。还有些情况,如上面的示例代码,在当前应用中,需要访问其他Web应用的资源。总的来说,当一个Web组件不适合、不想或不能处理请求时,需要使用重定向或请求转发让其他Web组件来响应。
标签:
原文地址:http://blog.csdn.net/goskalrie/article/details/51217630