1 转发 & 重定向

在 SpringMVC 中,如果当处理器对请求处理完毕后,在不是返回 JSON 数据的情况下,一般都会跳转到其它的页面,此时有两种跳转方式:请求转发与重定向。在 SpringMVC 中分别对应 forward 和 redirect 这两个关键字。

关键字描述SpringMVC 实现原生 servlet 实现
forward表示转发forward:“目标页面”(例如 forward:“/WEB_INF/success.jsp”)request.getRequestDispatcher(“xx.jsp”).forward()
redirect表示重定向redirect:“目标页面” | redirect:“目标请求”response.sendRedirect(“xxx.jsp”)

转发
页面可以是 WEB-INF 中页面;不经过视图解析器;页面要写视图完整路径(webapp 下的路径)。

ForwardServlet 在收到请求后,它并不自己发送响应,而是把请求和响应都转发给路径为 /hello 的 Servlet。
后续请求的处理实际上是由 HelloServlet 完成的。这种处理方式称为转发(Forward),我们用流程图画出来如下:

                          ┌────────────────────────┐
                          │      ┌───────────────┐ │
                          │ ────>│ForwardServlet │ │
┌───────┐  GET /morning   │      └───────────────┘ │
│Browser│ ──────────────> │              │         │
│       │ <────────────── │              ▼         │
└───────┘    200 <html>   │      ┌───────────────┐ │
                          │ <────│ HelloServlet  │ │
                          │      └───────────────┘ │
                          │       Web Server       │
                          └────────────────────────┘

转发和重定向的区别在于,转发是在 Web 服务器内部完成的,对浏览器来说,它只发出了一个 HTTP 请求。

重定向
页面不可以是 WEB-INF 中页面;不经过视图解析器;页面要写视图完整路径(webapp 下的路径)。

重定向是指当浏览器请求一个 URL 时,服务器返回一个重定向指令,告诉浏览器地址已经变了,麻烦使用新的 URL 再重新发送新请求。

如果浏览器发送 GET /hi 请求,RedirectServlet 将处理此请求。由于 RedirectServlet 在内部又发送了重定向响应,因此,浏览器会收到如下响应:

HTTP/1.1 302 Found
Location: /hello

当浏览器收到302响应后,它会立刻根据 Location 的指示发送一个新的 GET /hello 请求,这个过程就是重定向:

┌───────┐   GET /hi     ┌───────────────┐
│Browser│ ────────────> │RedirectServlet│
│       │ <──────────── │               │
└───────┘   302         └───────────────┘
 
 
┌───────┐  GET /hello   ┌───────────────┐
│Browser│ ────────────> │ HelloServlet  │
│       │ <──────────── │               │
└───────┘   200 <html>  └───────────────┘

观察 Chrome 浏览器的网络请求,可以看到两次 HTTP 请求。

2 SpringMVC 转发指令

处理器方法返回 String。在视图路径前面加入 forward: 视图完整路径,下面表示执行完成后转发到 /target.jsp

@RequestMapping("/forward")
public String forward() {
    return "forward:/target.jsp";
}

处理器方法返回 ModelAndView 时,需在 setViewName() 指定的视图前添加 forward:。且此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。视图页面必须写出相对于项目根(WEBAPP)的路径。

3 SpringMVC 重定向指令

@RequestMapping("/redirect")
public String redirect() {
    return "redirect:/target.jsp";
}

表示方法执行完成后重定向到 /target.jsp

注意:

  1. ModelAndView 对象存在于 request 作用域中。但是由于重定向第一次 request 和第二次 request 不同,因此前一个 request 中的 Model 数据不共享。Java 四大域总结
  2. 重定向时,框架会把 Model 中的简单类型的数据,转为 string 使用,作为重定向的 get 请求参数使用。目的是在前后两次请求之间传递数据。
  3. 新的请求页面中,可以使用参数集合对象 ${param} 获取请求参数值 ${param.myname}。或者 <%=request.getParameter("myname")%> 语句。
  4. 重定向不能访问 WEB-INF 内部数据,redirect:/WEB-INF/view/show.jsp 会报 404 错误。
@RequestMapping(value = "/doRedirect.do")  
public ModelAndView doWithRedirect(String name,Integer age){  
	ModelAndView mv  = new ModelAndView();  
	//数据放入到 request作用域  
	mv.addObject("myname",name);  
	mv.addObject("myage",age);  
	mv.setViewName("redirect:/hello.jsp");  
	// 相当于重定向连接为 http://localhost:8080/ch08_forard_redirect/hello.jsp?myname=lisi&myage=22  
	return mv;  
}
<h3>/WEB-INF/view/hello.jsp从request作用域获取数据</h3><br/>  
<h3>myname数据:${param.myname}</h3><br/>  
<h3>myage数据:${param.myage}</h3>  
<h3>取参数数据: <%=request.getParameter("myname")%> </h3>

4 使用原生对象完成转发

@RequestMapping("/forward")
public void forward2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.getRequestDispatcher("/target.jsp").forward(request, response);
}

注意:使用原生 request 对象执行转发后,handler 方法的返回值就必须是 void,意思是我们自己指定了响应方式,不需要 SpringMVC 再进行处理了。一个请求只能有一个响应,不能在 handler 方法里面给一个,然后 SpringMVC 框架再给一个。

5 使用原生对象完成重定向

@RequestMapping("/redirect")
public void redirect2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.sendRedirect(request.getContextPath()+"/target.jsp");
}

使用原生 response 对象执行重定向后,handler 方法的返回值同样需要设置为 void,原因同上。

6 转发和跳转的小结

  1. (原生)转发使用的是 getRequestDispatcher() 方法;重定向使用的是 sendRedirect();
  2. 转发:浏览器 URL 的地址栏不变。重定向:浏览器 URL 的地址栏改变;
  3. 转发是服务器行为,重定向是客户端行为;
  4. 转发是浏览器只做了一次访问请求。重定向是浏览器做了至少两次的访问请求;
  5. 转发2次跳转之间传输的信息不会丢失,重定向2次跳转之间传输的信息会丢失(request 范围)。

7 转发和重定向的选择

  1. 重定向的速度比转发慢,因为浏览器还得发出一个新的请求,如果在使用转发和重定向都无所谓的时候建议使用转发。
  2. 因为转发只能访问当前 WEB 的应用程序,所以不同 WEB 应用程序之间的访问,特别是要访问到另外一个 WEB 站点上的资源的情况,这个时候就只能使用重定向了。

8 应用场景

重定向:重定向到另外一个外部网站;避免在用户重新加载页面时两次调用相同的动作。

例如,当提交产品表单的时候,执行保存的方法将会被调用,并执行相应的动作;这在一个真实的应用程序中,很有可能将表单中的所有产品信息加入到数据库中。但是如果在提交表单后,重新加载页面,执行保存的方法就很有可能再次被调用。同样的产品信息就将可能再次被添加,为了避免这种情况,提交表单后,你可以将用户重定向到一个不同的页面,这样的话,这个网页任意重新加载都没有副作用;

但是,使用重定向不太方便的地方是,使用它无法将值轻松地传递给目标页面。而采用转发,则可以简单地将属性添加到 Model,使得目标视图可以轻松访问。由于重定向经过客户端,所以 Model 中的一切都会在重定向时丢失。但幸运的是,在 Spring3.1 版本以后,我们可以通过 Flash 属性,解决重定向时传值丢失的问题。

要使用 Flash 属性,必须在 Spring MVC 的配置文件中添加一个 <annotation-driven/>。然后,还必须再方法上添加一个新的参数类型:org.springframework.web.servlet.mvc.support.RedirectAttributes

如下所示:

@RequestMapping(value="saveProduct",method=RequestMethod.POST)
public String saveProduct(ProductForm productForm,RedirectAttributes redirectAttributes){
 
     //执行产品保存的业务逻辑等
  
     //传递参数
       redirectAttributes.addFlashAttribute("message","The product is saved successfully");
   
     //执行重定向
      return "redirect:/……";
}