SpringMVC的转发与重定向

重定向与转发

SpringMVC为请求在控制器处理方法之间跳转的情况提供了重定向与转发机制,也就是说在SpringMVC中可以使用forward和redirect关键字在Controller中对原请求进行转发或重定向到其他的Controller。

在一般情况下,控制器处理方法返回字符串类型的值会被当成逻辑视图名处理,但如果字符串带”forward:”或”redirect:”前缀,则SpringMVC将当前请求转到另一个控制器进行处理:将”forward:”或”redirect:”当成指示符,其后的字符作为URL处理,区别在于redirect会让浏览器发起一个新的请求但属于同一个Session,而forwar请求与当前请求同属一个请求。

转发

forward操作发生在服务器内部,在前一个控制器处理完毕后,直接进入下一个控制器处理, 并将最后的response发给浏览器。这种方式的特点是:

  • 转发前后是同一个request,后一个控制器处理方法可共享前一个控制器处理方法的参数与属性;
  • 因为是同一个request,拦截器只会拦截前一个URL,如果前一个URL在映射时未配置到拦截器拦截,则拦截后一个URL,即只拦截一次;
  • 最后返回到浏览器后,因为地址栏显示的是转发前的URL,所以刷新页面时会依次执行前后两个控制器;

举个栗子:
控制器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping(value="/a", method=RequestMethod.GET)
public String inputData(){
return "a"; //Spring框架找到对应的View并渲染
}
@RequestMapping(value="/a", method=RequestMethod.POST)
public String outputData(Model model , String name){
model.addAttribute("name", name);
//转发到 /b 的Controller方法(即outputDataX)上
return "forward:/test/b";
}
@RequestMapping(value="/b", method=RequestMethod.POST)
public String outputDataX(){
return "b";
}

前端界面a.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%--
Created by IntelliJ IDEA.
User: qiu
Date: 18-1-26
Time: 上午9:59
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/test/a" method="post">
<input type="text" name="name"/><button>get</button>
</form>
</body>
</html>

前端界面b.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%--
Created by IntelliJ IDEA.
User: qiu
Date: 18-1-26
Time: 上午10:00
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${name}
</body>
</html>

在后面的例子中这2个前端界面是没有变化的,就不再重复说明了。

运行服务器访问结果:

我们发现请求参数是可以通过转发操作共享的,并且浏览器的URL没有变化。

重定向

redirect操作大致流程是这样的:服务器端会首先发一个response给浏览器, 然后浏览器收到这个response后再发一个requeset给服务器, 然后服务器发新的response给浏览器。这种方式的特点是:

  • 在转发前后有两个不同的request对象,转发前后的两个控制器在request上的参数(request.getParameter())和request属性(request.getAttribute())不能共享;
  • 如果转发前后的两个控制器都配置在spring拦截器范围内,这样拦截器会拦截前后两个request,即会拦截两次;
  • 最后返回到浏览器后,因为地址栏显示的是转发后的URL,所以刷新页面时只会执行后面的URL映射的控制器处理方法;

我们来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value="/a", method=RequestMethod.GET)
public String inputData(){
return "a"; //Spring框架找到对应的View并渲染
}
@RequestMapping(value="/a", method=RequestMethod.POST)
public String outputData(HttpServletRequest request, String name){
request.setAttribute("name", name);
//转发到 /b 的Controller方法(即outputDataX)上
return "redirect:/test/b";
}
@RequestMapping(value="/b", method=RequestMethod.GET)
public String outputDataX(){
return "b";
}

运行结果:

说明redirect操作是无法将原请求中的参数传递到重定向后的控制器方法中的,并且浏览器URL会变化。

如何才能在重定向操作中传递请求的参数呢?一种方法是拼接URL方法来传递,另外还可以使用HttpSession来传递参数。

RedirectAttributes

RedirectAttributes工具类来帮助我们传递参数,其本质是拼接URL来传递的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping(value="/a", method=RequestMethod.GET)
public String inputData(){
return "a"; //Spring框架找到对应的View并渲染
}
@RequestMapping(value="/a", method=RequestMethod.POST)
public String outputData(RedirectAttributes redirectAttributes, String name){
redirectAttributes.addAttribute("name", name);
//转发到 /b 的Controller方法(即outputDataX)上
return "redirect:/test/b";
}
@RequestMapping(value="/b", method=RequestMethod.GET)
public String outputDataX(Model model,String name){
model.addAttribute("name", name);
return "b";
}

运行结果:

通过RedirectAttributes我们得到了上次请求的参数,观察URL发现,其实RedirectAttributes类是帮助我们将原请求的参数写在了URL后面了。这里需要说明的是,使用RedirectAttributes类的addAttribute方法传递参数会将参数写在URL后面;使用RedirectAttributes类的addFlashAttribute方法传递参数不会体现在URL上面,会把该参数值暂时保存于session,待重定向URL获取该参数后从session中移除。

HttpSession

通过Session来传递重定向请求参数,请求参数不会出现在URL中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value="/a", method=RequestMethod.GET)
public String inputData(){
return "a"; //Spring框架找到对应的View并渲染
}
@RequestMapping(value="/a", method=RequestMethod.POST)
public String outputData(HttpSession session, String name){
session.setAttribute("name", name);
//转发到 /b 的Controller方法(即outputDataX)上
return "redirect:/test/b";
}
@RequestMapping(value="/b", method=RequestMethod.GET)
public String outputDataX(){
return "b";
}

运行结果:

在实际编程的时候,直接在控制处理方法入参使用HttpSession类型的参数的话不利于调试,我们可以在类定义处使用@SessionAttributes注解来替代,将参数存入session会话中。
结合之前那篇讲SpringMVC的参数传递的博客来讲讲SpringMVC在处理@ModelAttributes与@SessionAttributes同时存在的情况下,流程是这样的:

  1. SpringMVC在调用控制器处理方法前,在请求线程中自动创建一个隐含的模型对象
  2. 调用所有标注了@ModelAttribute的方法,并将方法返回值添加到隐含模型中
  3. 查看Session中是否存在@SessionAttributes(“xxx”)所指定的xxx属性,如果有,则将其添加到隐含模型。如果隐含模型中已经有xxx属性,则该步操作会覆盖模型中已有的属性值
  4. 对标注了@ModelAttribute(“xxx”)处理方法的入参按如下流程处理:
    4.1 如果隐含模型拥有名为xxx的属性,则将其赋给该入参,再用请求消息填充该入参对象直接返回,否则转到4.2
    4.2 如果xxx是会话属性,即在处理类定义处标注了@SessionAttributes(“xxx”),则尝试从会话中获取该属性,并将其赋给该入参,然后再用请求消息填充该入参对象。如果在会话中找不到对应的属性,则抛出HttpSessionRequiredException异常。否则转到4.3
    4.3 如果隐含模型中不存在xxx属性,且xxx也不是会话属性,则创建入参的对象实例,然后再用请求消息填充该入参

总结

Stack Overflow上有一个关于重定向与转发的回答,我觉得讲的挺概括的,这里我就直接搬运过来作为总结吧:

Forward: is faster, the client browser is not involved, the browser displays the original URL, the request is transfered do the forwarded URL.
Redirect: is slower, the client browser is involved, the browser displays the redirected URL, it creates a new request to the redirected URL.

0%