用来记录一些原创性的总结
Servlet容器在页面跳转时有两种方式,forward和redirect的,其中forward时应用的是在服务端的跳转,应用的是同一个request。而redirect是服务端通过响应301和对应的新地址告诉浏览器让浏览器重新请求新的地址。第一次请求和第二次请求使用的不是同一个request的。所以这种情况下,不能直接通过request传递参数到新的页面。SpringMVC默认使用的是forward请求,如果需要使用redirect跳转新页面,可以使用redirect:
前缀。forward请求时添加到Model中的参数都可以被传递到新的页面,而通过redirect:
跳转到新页面时,默认会自动把当前Model中包含的原始类型的属性及原始类型的Collection/Array作为查询参数传递下去。比如对于下面的处理器映射,如果我们请求/redirect/abc
,则会自动跳转到/abc
,并且会附上查询参数?a=1&b=2&c=1&c=2&c=3
,所以浏览器会自动请求/abc?a=1&b=2&c=1&c=2&c=3
。
@RequestMapping("/redirect/{target}")
public String redirectTo(@PathVariable("target") String redirectTo, Map<String, Object> model) {
model.put("a", "1");
model.put("b", 2);
model.put("c", Arrays.asList(1, 2, 3));
return "redirect:/" + redirectTo;
}
对于复杂的对象,通过查询参数的方式就不能自动传递了。这个时候我们就可以使用RedirectAttributes了,它是一种特殊的Model。它的定义如下:
public interface RedirectAttributes extends Model {
@Override
RedirectAttributes addAttribute(String attributeName, Object attributeValue);
@Override
RedirectAttributes addAttribute(Object attributeValue);
@Override
RedirectAttributes addAllAttributes(Collection<?> attributeValues);
@Override
RedirectAttributes mergeAttributes(Map<String, ?> attributes);
/**
* Add the given flash attribute.
* @param attributeName the attribute name; never {@code null}
* @param attributeValue the attribute value; may be {@code null}
*/
RedirectAttributes addFlashAttribute(String attributeName, Object attributeValue);
/**
* Add the given flash storage using a
* {@link org.springframework.core.Conventions#getVariableName generated name}.
* @param attributeValue the flash attribute value; never {@code null}
*/
RedirectAttributes addFlashAttribute(Object attributeValue);
/**
* Return the attributes candidate for flash storage or an empty Map.
*/
Map<String, ?> getFlashAttributes();
}
通过继承自Model的addAttribute等方法添加的属性,在重定向时会丢失或者以查询参数的方式传递。通过addFlashAttribute添加的属性则可以自动传递到新的页面的Model中,其内部会把它放到一个FlashMap中,区分input和output。由FlashMapManager管理,默认实现是基于Session的实现,即SessionFlashMapManager。redirect需要传参数到新页面时我们可以像如下这样,为处理器方法声明一个RedirectAttributes类型的参数,然后往其中通过addFlashAttribute添加需要传递的参数。在如下示例中我们通过addAttribute传递的参数key1和key2将通过附加为查询参数的方式传递,而通过addFlashAttribute传递的modelAttr1、modelAttr2和listAttr则会通过FlashMap传递。
@RequestMapping("/src")
public String index(RedirectAttributes redirectAttributes) {
/**
* addAttribute中的内容会作为redirect的URL的查询参数传递,即会以
* /redirectattributes/target?key1=value1&key2=value2的形式传递,
* 其中的value是以String的形式传递的,添加进去时会把它转换为String,如果内部没有对应的转换器支持则将
* 抛出异常。具体可以参考RedirectAttributesModelMap中的对应实现
*/
redirectAttributes.addAttribute("key1", "value1")
.addAttribute("key2", "value2");
/**
* addFlashAttribute中的内容会存放到Session中,且在一次页面跳转后失效。
*/
redirectAttributes.addFlashAttribute("modelAttr1", "modelAttr1Value1")
.addFlashAttribute("modelAttr2", "modelAttr1Value2")
.addFlashAttribute("listAttr", Arrays.asList(1, 2, 3));
return "redirect:/redirectattributes/target";
}
除了通过RedirectAttributes传递参数外,我们也可以直接获取角色为output的FlashMap,通过往其中添加属性来传递。其实使用RedirectAttributes时底层也是通过角色为Output的FlashMap来传递的。示例如下。
@RequestMapping("/src")
public String index(HttpServletRequest request) {
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
flashMap.put("abc", 111);
return "redirect:/redirectattributes/target";
}
在重定向之后通过FlashMap传递的参数会自动到Model中,所以我们可以从Model中获取对应的参数,如果是在页面上,我们可以直接从request中获取。如果是在Controller方法中,则可以通过在方法参数上使用@ModelAttribute获取,也可以直接定义Model或Map类型的方法参数,然后从Model或Map中获取。在如下示例中的方法参数modelAttr1就是从Model中获取参数modelAttr1,而方法体里面也定义了从model参数中获取参数modelAttr1。
@RequestMapping("/target")
public String target(@ModelAttribute("modelAttr1") String modelAttr1, @RequestParam("key1") String key1, Map<String, Object> model) {
//获取通过FlashMap传递过来的modelAttr1的值
Object attr1 = model.get("modelAttr1");
return "redirect_attributes_target";
}
我们也可以直接从Input角色的FlashMap中获取这些信息,其实底层也是从FlashMap中获取到的redirect传递过来的参数,然后把它们丢到Model中。示例如下:
@RequestMapping("/target")
public String target(HttpServletRequest request) {
Map<String, ?> map = RequestContextUtils.getInputFlashMap(request);
Object attr1 = map.get("modelAttr1");
return "redirect_attributes_target";
}
需要说明的是redirect传递的参数在Session中只存在一会,会在请求到达重定向后的页面后从session中清除。
(本文基于Spring4.1.0所写)