Servlet中的重定向

Servlet规范定义了一个接口用于请求转发,即RequestDispatcher,该接口有Servlet引擎提供实现,它从客户端接受请求并把该请求转发到容器的任何资源上(包括Servlet,JSP,,HTML等)。虽然它可以包装任何资源,但是Servlet规范建议尽量只用来包装Servlet。RequestDispatcher接口规定了两个方法:

void forward(ServletRequest request, ServletResponse response)
将一个servlet的请求转发到其他资源(servlet,JSP,HTML)
void include(ServletRequest request, ServletResponse response)
在同一个响应中,将其他资源包含进来
有一点必须强调:一个RequestDispatcher只能转发或者包含同一个Web应用中的资源。以Tomcat的容器模型为例(图是许令波画的,在此借用一下,希望不要介意):

真正管理Servlet的是Context容器,一个Context容器对应着一个Web应用。前面说了,RequestDispatcher是有Servlet容器创建的,那么这个RequestDispatcher对象只能在创建它的Context返回内实现转发。需要与这种情况区分开来:比如上图有两个Context,假设现在在左边的Context,然后获得了右边Context的ServletContext对象contextR,通过调用contextR.getRequestDispatcher()获得RequestDispatcher对象,重定向到右边的Context,貌似使用RequestDispatcher从左边的Context重定向到了右边的Cotnext上了。但是请注意,这个RequestDispatcher对象是右边的Context创建的,它能且只能转发到此Context,并不是左边的Context创建的RequestDispatcher重定向到了右边的Context上。

获得RequestDispatcher

RequestDispatcher是容器创建的,程序员无法创建,ServletContext和ServletRequest中都定义了获得RequestDispatcher的方法,先来看ServletContext接口中定义的两个用于获取的方法:

/**
 * 返回包装了某个路径所指定的资源的RequestDispatcher对象
 * 传递给该方法的路径必须以“/”开头,"/"代表当前Web应用的根
 * 目录,WEB-INF中的内容对RequestDispatcher对象可见
 */
RequestDispatcher getRequestDispatcher(java.lang.String path);
/**
 * 返回包装了某个Servlet或者JSP的RequestDispatcher对象
 * 传递给该方法的参数是在Web应用中指定的Servlet或JSP的名称
 * 可以通过调用ServletConfig.getSerlvetName()确定
 */
RequestDispatcher getNamedDispatcher(java.lang.String name)
注意,getNamedDispatcher()方法只能获得Serlvet或JSP的包装对象。ServletRequest接口中也提供了获取的方法:
/**
 * 与ServletContext中的getRequestDispatcher(String)的区别:
 * 可以使用"/","/"代表当前Web应用的根目录外,这个和前面相同
 * 出了使用"/"开头的外,还可以使用不以"/"开头的的相对路径
 */
RequestDispatcher getRequestDispatcher(java.lang.String path);

有了RequestDispatcher对象,那就来看看它可以做写什么吧。

include实现资源包含

include方法用于将RequestDispatcher对象封装的资源内容作为当前相应的一部分包含进来。其实就是相当于组装,比如一个页面包含几个部分,每个部分放到不同的页面去实现,然后使用include方法将几个页面整合到到一个页面里,在调用include方法之前和之后的输出都是有效的。它们共享同一个request和response,不过被包含的页面不能改变响应消息的状态码和状态头,如果它存在这样的语句,这也设置将被忽略。下图展示了include的结构:


下面给出一个示例:

/**
 * 包含文件和被包含文件是同一个文件,请求和处理都是相同的
 * 只不过在被包含文件中设置响应消息的状态吗和响应头全部是小
 */
@WebServlet("/dispatch/Including")
public class Including extends HttpServlet {
	private static final long serialVersionUID = 1L;
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		String value = "我在冰封的深海找寻希望的缺口,却在午夜惊醒时,蓦然瞥见绝美的月光";
		
		//给request添加属性,看看被包含的页面能否获得此属性
		request.setAttribute("content", value);
		out.println("-------------------------<br/>");
		//获得被包装资源的RequestDispatcher对象
		RequestDispatcher dispatch = getServletContext().getRequestDispatcher("/dispatch/Included");
		//包含进来
		dispatch.include(request, response);
		out.println("<br/>----------end------------");
	}
}
---------------------------------------------------------------
/**
 * 被包含的页面
 */
@WebServlet("/dispatch/Included")
public class Included extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//这里的设置将被忽略
		response.setCharacterEncoding("UTF-8");
		
		PrintWriter out = response.getWriter();
		//获得request中的属性
		out.println(request.getAttribute("content"));
		/*这里的PrintWriter和包含页面中的PrintWriter是同一个对象
		 * 如果在这里把输出流关闭了,则包含页面中位于include方法的
		 * 内容将不会被输出了
		 */
		//out.close();
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request,response);
	}
}
在包含页面并没有设置编码方式,但是在被包含页面中设置了使用UTF-8编码,但是输出结果却是这样的:
-------------------------
???????????????????????????????? 
--------------end----------
这样说了在被包含页面中设置的响应头被忽略了。

forward实现请求转发

forward方法用于将一个请求转发到RequestDispatcher对象封装的资源,Servlet程序在调用这个方法进行转发之前可以对请求进行一些前期预处理,在调用forward方法时需要注意一下几点:

(1)在调用forward方法之前,实现转发的Serlvet不能有内容输出到客户端。如果在调用forward之前向Servlet引擎缓冲区中写入了内容,只要写入到缓冲区中的内容还没有被真正输出到客户端,forward方法执行后,原来被输出到缓冲区中的内容将被清空;如果之前写入缓冲区的内容已经输出到客户端,那么调用forward时会抛出IllegalStateException异常;

(2)在调用forward方法之后,实现转发的Servlet会继续执行,直到它的逻辑完成,但是它的任何向客户端的输出都将被忽略;

(3)在调用者和被调用者程序中设置的响应状态吗和响应头都有效;

(4)如果调用者与被调用者访问的URL不属于同一目录,当被调用这输出到内容中包含使用相对URL的访问路径时,原来相对被调用者的URL将变成相对于调用者的URL。这是因为浏览器只直到当前访问的是调用者的URL,所以浏览器都已调用者作为参考,后面会使用图解释这种请求过程。

下面是一个示例,演示会演示上面提到的其中情况:

/**
 * 调用者
 */
@WebServlet("/dispatch/ForwardDemo")
public class ForwardDemo extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		//在forward方法调用之前不能有内容输出到客户端
		//如果在缓冲区中,则会被忽略
		out.println("在调用forward方法之前");
		//如果将缓冲区的内容强制输出到客户端,则会抛异常
		//response.flushBuffer();
		
		//设置属性,证明在被调用者中能获取到
		request.setAttribute("content", "那时我们有梦");
		
		RequestDispatcher dispatch = getServletContext().getRequestDispatcher("/Forwarded");
		dispatch.forward(request, response);
		
		//在forward方法之后的输出都将被忽略
		out.println("在forward方法之后");
		
		//但是调用者的逻辑也会正常执行,直到结束
		System.out.println("虽然在forward之后,还是得到了执行");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request,response);
	}

}
-----------------------------------------------------------
/**
 * 被调用者程序
 */
@WebServlet("/Forwarded")
public class Forwarded extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter out = response.getWriter();
		
		//获得在调用者程序中设置的属性
		out.println(request.getAttribute("content") + "<br/>");
		
		out.println("关于文学<br>关于爱情<br/>关于穿越世界的旅行<br/>"
				+ "如今我们深夜饮酒<br/>杯子碰到一起都是梦破碎的声音<br/>");
		
		RequestDispatcher dispatch = getServletContext().getRequestDispatcher("/html/dispatch.html");
		dispatch.forward(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request,response);
	}

}
在上面的实例中,其实被调用者也充当了调用者,所以它的内容实际上也不会被输出。注意关于相对路径的问题,现在在html路径下有两个文件:dispatch.html和Refresh.html,如果要在dispatch.html中使用链接指向Refresh.html,由于它们在同一个目录下那么应该这样写:

<a href="Refresh.html">刷新</a>

但是经过forward转发后,相对路径就不是dispatch.html所在的目录,而是以ForwardDemo(第一个调用者)的路径为相当目录了,如果想上面这样调用的话,浏览器会认为它访问的是这个路径:

http://localhost:8080/ServletJSP/dispatch/Refresh.html

所以应该这么调用:

<a href="../html/Refresh.html">必须以调用者的目录为相对路径</a>

运行上面的示例后,查看后台会发现有一条输出语句:“虽然在forward之后,还是得到了执行”,这说明调用者的逻辑还是会被正常执行。

HttpServletResponse.sendRedirect

sendRedirect(String url)也可以实现重定向,它会生成302状态码和Location相应头,通知客户端去重新访问Location响应头中指定的URL,可以是相对的URL也可以是绝对的URL,如果URL以"/"开头,则代表整个Web站点的根目录,不是Web应用的根目录,比如以Tomcat为例,"/"在这里表示:http://localhost:8080;如果不以“/”开头,则表示相对于当前请求的URL。sendRedirect与forward不同时,它可以重定向到其他站点。示例代码如下,跳转到Google主页:

/**
 * ServletResponse.sendRedirect
 */
@WebServlet("/dispatch/RedirectServlet")
public class RedirectServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter out = response.getWriter();
		//同样不能有输出,否则抛异常
		out.println("before sendRedirect");
		//response.flushBuffer();
		
		//如果是Web应用中跳转,需要加上Web应用的路径
		//response.sendRedirect(getServletContext().getContextPath() + "/html/Refresh.html");
		//可以定位到其他站点
		response.sendRedirect("http://www.google.com");
		
		//后面的输出会被忽略,但是逻辑会完成
		out.println("after sendRedirect");
		System.out.println("after sendRedirect");
	}
}
区别

RequestDispatcher.forward(resquest,response)和ServletRequest.sendRedirect(url)的区别:

(1)sendRedirect可以重定向到其他站点;

(2)最重要的区别是,他们的运行原理完全,首先看forward的运行过程:

(1)客户端发送请求,处理请求的是页面1;

(2)页面1发现需要跳转到页面2,于是在服务器端直接转发到页面2;

(3)页面2响应请求;

在整个过程中,浏览器地址栏不会发生改变,页面1和页面2是同一个请求和响应,也就是说reqeust和response是同一个。以上的过程如下图所示:


而sendRedirect的请求过程则是这样的:

(1)浏览器请求页面1;

(2)服务器发现需要转发到页面2,于是给浏览器发送302状态码,并然浏览器去访问Location头中的URL;

(3)浏览器请求页面2;

(4)页面2做出响应;

在上面的过程中,浏览器发生了两次请求,所以地址栏中的地址会发生改变,两次请求不会共享request,response,服务器会生成两对request和response,以上过程如下图所示:

以上介绍了Servlet中的重定向,JSP中有一套几乎相同的机制,其实JSP本身就是Servlet嘛。

转载请注明出处:喻红叶《Servlet中的重定向》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值