Elim的博客

用来记录一些原创性的总结


Elim

RedirectAttributes传递参数到重定向后的页面

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所写)