Spring RediectAttributes的简单流程

假设有如下代码:

public String update(@PathVariable("id") Integer id, RedirectAttributes ra, Model model) {
  Optional<Article> optional = articleService.getArticleForEdit(id);
  if (!optional.isPresent()) {
      ra.addFlashAttribute(Constants.ERROR, new Message("article.notExists", "文章不存在"));
      return "redirect:/mgr/article/index";
  }
  ...
}

RedirectAttributes是由什么实现的?

RedirectAttributes ra

RedirectAttributes用于重定向传参,那么写下这个参数的时候,spring做了什么?如果知道HandlerMethodArgumentResolver这玩意,那么很容易就能找到RedirectAttributesMethodArgumentResolver,通过观察它的源码,RedirectAttributes的实现类其实是RedirectAttributesModelMap,它既是ModelMap,也是RedirectAttributes

ra.addFlashAttribute(Constants.ERROR, new Message("article.notExists", "文章不存在"));

将error变量存储在了一个额外的Map中(RedirectAttributesModelMap的flashAttributes)。

"redirect:/mgr/article/index"

返回一个viewName.由于它是String类型的,所以会被ViewNameMethodReturnValueHandler处理(可以查看RequestMappingHandlerAdapter的invokeHandlerMethod方法),最终会将ModelAndViewContainer的redirectModelScenario属性设置为true(这将会影响到getModel方法的判断)

String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
    mavContainer.setRedirectModelScenario(true);
}

flashAttributes是如何存储的?

RedirectAttributesModelMap被生成之后,RedirectAttributesMethodArgumentResolver又将它设置为了ModelAndViewContainer的redirectModel属性,在RequestMappingHandlerAdapter处理请求时,会通过getModelAndView方法返回一个ModelAndView,而该方法有如下代码:

if (model instanceof RedirectAttributes) {
    Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    if (request != null) {
        RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
    }
}

这里的model就是ModelAndViewContainer的getModel的返回值,ModelAndViewContainer的getModel方法如下:

public ModelMap getModel() {
  if (useDefaultModel()) {
      return this.defaultModel;
  }
  else {
      if (this.redirectModel == null) {
          this.redirectModel = new ModelMap();
      }
      return this.redirectModel;
  }

至此,flashAttributes被转移到了FlashMap中。

这里的FlashMap是从哪里来的?
FlashMap存储在request的名为DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE的属性中,在DispaterServlet的doService方法中,有如下代码:

if (this.flashMapManager != null) {
    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}

这里可以看到FlashMapFlashMapManager的存储情况,上面的代码每个请求都会被执行一边,此时才明白了RequestContextUtils.getOutputFlashMap注释的缘由。

再通过查看DispatherServlet的源码,可以定位到

view.render(mv.getModelInternal(), request, response);

这行渲染视图的代码,而这里面的View,就是RedirectView,因此需要查看它的render方法:

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
        HttpServletResponse response) throws IOException {

    String targetUrl = createTargetUrl(model, request);
    targetUrl = updateTargetUrl(targetUrl, model, request, response);

    // Save flash attributes
    RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

    // Redirect
    sendRedirect(request, response, targetUrl, this.http10Compatible);
}

createTargetUrl是将model的变量拼接在跳转的地址中,而

RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

这个就是存储flashAttributes的方法,源码如下:

FlashMap flashMap = getOutputFlashMap(request);
if (CollectionUtils.isEmpty(flashMap)) {
    return;
}

UriComponents uriComponents = UriComponentsBuilder.fromUriString(location).build();
flashMap.setTargetRequestPath(uriComponents.getPath());
flashMap.addTargetRequestParams(uriComponents.getQueryParams());

FlashMapManager manager = getFlashMapManager(request);
Assert.state(manager != null, "No FlashMapManager. Is this a DispatcherServlet handled request?");
manager.saveOutputFlashMap(flashMap, request, response);

最终存储是通过FlashMapManager的saveOutputFlashManager方法来存储的,而根据FlashMapManager的实现不同,最终的存储方式也有所不通,默认(似乎也是唯一实现)是SessionFlashMapManager,它负责将flashAttributes存储在session中,key为SessionFlashMapManager.class.getName() + ".FLASH_MAPS".

FlashMap是如何被移除的?

同样是在DispaterServlet的doService方法中,有

FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}

这行代码,它的注释说的很明确了:

Find a FlashMap saved by a previous request that matches to the current request, remove it from underlying storage, and also remove other expired FlashMap instances.

将前一个请求存储的FlashMap去除,并且从底层的存储删除,同时删除其他过期的FlashMap。 在不考虑过期的情况下,重定向之前带过来存储在session中的flashmap在这里就被移除了,同时,移除之后,会将该flashmap存储在名为INPUT_FLASH_MAP_ATTRIBUTE的request attributes中。

页面是如何读取到FlashMap的?

回到RequestMappingHandlerAdapter的invokeHandlerMethod方法可以发现:

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

这段代码,而它的作用就是将FlashMap放入到ModelAndViewContainer的defaultModel中,而 RequestMappingHandlerAdapter的getModelAndView方法又将ModelAndViewContainer的defaultModel放入ModelAndView中。

上述流程中,并非在上面几个方法来回跳转。由于重定向的关系,几个ModelAndView是不同的,重定向之前有一个,重定向之后,又会重新生成一个,debug更能帮助了解一个跳转的流程。