乐趣区

关于java:SpringMVC-中的参数还能这么传递涨姿势了

明天来聊一个 JavaWeb 中简略的话题,然而感觉却比拟稀奇,因为这个技能点,有的小伙伴们可能没听过!

1. 缘起

说到 Web 申请参数传递,大家能想到哪些参数传递形式?

参数能够放在地址栏中,不过地址栏参数的长度有限度,并且在有的场景下咱们可能不心愿参数暴漏在地址栏中。参数能够放在申请体中,这个没啥好说的。

小伙伴们试想这样一个场景:

在一个电商我的项目中,有一个提交订单的申请,这个申请是一个 POST 申请,申请参数都在申请体中。当用户提交胜利后,为了避免用户刷新浏览器页面造成订单申请反复提交,咱们个别会将用户重定向到一个显示订单的页面,这样即便用户刷新页面,也不会造成订单申请反复提交。

大略的代码就像上面这样:

@Controller
public class OrderController {@PostMapping("/order")
    public String order(OrderInfo orderInfo) {
        // 其余解决逻辑
        return "redirect:/orderlist";
    }
}

这段代码我置信大家都懂吧!如果不懂能够看看松哥录制的收费的 SpringMVC 入门教程(硬核!松哥又整了一套免费视频,搞起!)。

然而这里有一个问题:如果我想传递参数怎么办?

如果是服务器端跳转,咱们能够将参数放在 request 对象中,跳转实现后还能拿到参数,然而如果是客户端跳转咱们就只能将参数放在地址栏中了,像下面这个办法的返回值咱们能够写成:return "redirect:/orderlist?xxx=xxx";,这种传参形式有两个缺点:

  • 地址栏的长度是无限的,也就意味着可能放在地址栏中的参数是无限的。
  • 不想将一些非凡的参数放在地址栏中。

那该怎么办?还有方法传递参数吗?

有!这就是明天松哥要和大家介绍的 flashMap,专门用来解决重定向时参数的传递问题。

2.flashMap

在重定向时,如果须要传递参数,然而又不想放在地址栏中,咱们就能够通过 flashMap 来传递参数,松哥先来一个简略的例子大家看看成果:

首先咱们定义一个简略的页面,里边就一个 post 申请提交按钮,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/order" method="post">
    <input type="submit" value="提交">
</form>
</body>
</html>

而后在服务端接管该申请,并实现重定向:

@Controller
public class OrderController {@PostMapping("/order")
    public String order(HttpServletRequest req) {FlashMap flashMap = (FlashMap) req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
        flashMap.put("name", "江南一点雨");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {return (String) model.getAttribute("name");
    }
}

首先在 order 接口中,获取到 flashMap 属性,而后存入须要传递的参数,这些参数最终会被 SpringMVC 主动放入重定向接口的 Model 中,这样咱们在 orderlist 接口中,就能够获取到该属性了。

当然,这是一个比拟毛糙的写法,咱们还能够通过 RedirectAttributes 来简化这一步骤:

@Controller
public class OrderController {@PostMapping("/order")
    public String order(RedirectAttributes attr) {attr.addFlashAttribute("site", "www.javaboy.org");
        attr.addAttribute("name", "微信公众号:江南一点雨");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {return (String) model.getAttribute("site");
    }
}

RedirectAttributes 中有两种增加参数的形式:

  • addFlashAttribute:将参数放到 flashMap 中。
  • addAttribute:将参数放到 URL 地址中。

通过后面的解说,当初小伙伴们应该大抵明确了 flashMap 的作用了,就是在你进行重定向的时候,不通过地址栏传递参数。

很多小伙伴可能会有疑难,重定向其实就是浏览器发动了一个新的申请,这新的申请怎么就获取到上一个申请保留的参数呢?这咱们就要来看看 SpringMVC 的源码了。

3. 源码剖析

首先这里波及到一个要害类叫做 FlashMapManager,如下:

public interface FlashMapManager {
    @Nullable
    FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
    void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}

两个办法含意一眼就能看进去:

  • retrieveAndUpdate:这个办法用来复原参数,并将复原过的的参数和超时的参数从保留介质中删除。
  • saveOutputFlashMap:将参数保留保存起来。

FlashMapManager 的实现类如下:

从这个继承类中,咱们基本上就能确定默认的保留介质时 session。具体的保留逻辑则是在 AbstractFlashMapManager 类中。

整个参数传递的过程能够分为三大步:

第一步,首先咱们将参数设置到 outputFlashMap 中,有两种设置形式:咱们后面的代码 req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE) 就是间接获取 outputFlashMap 对象而后把参数放进去;第二种形式就是通过在接口中增加 RedirectAttributes 参数,而后把须要传递的参数放入 RedirectAttributes 中,这样当处理器处理完毕后,会主动将其设置到 outputFlashMap 中,具体逻辑在 RequestMappingHandlerAdapter#getModelAndView 办法中:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
        ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    // 省略...
    if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

能够看到,如果 model 是 RedirectAttributes 的实例的话,则通过 getOutputFlashMap 办法获取到 outputFlashMap 属性,而后相干的属性设置进去。

这是第一步,就是将须要传递的参数,先保留到 flashMap 中。

第二步,重定向对应的视图是 RedirectView,在它的 renderMergedOutputModel 办法中,会调用 FlashMapManager 的 saveOutputFlashMap 办法,将 outputFlashMap 保留到 session 中,如下:

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);
}

RequestContextUtils.saveOutputFlashMap 办法最终就会调用到 FlashMapManager 的 saveOutputFlashMap 办法,将 outputFlashMap 保留下来。咱们来大略看一下保留逻辑:

public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {if (CollectionUtils.isEmpty(flashMap)) {return;}
    String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
    flashMap.setTargetRequestPath(path);
    flashMap.startExpirationPeriod(getFlashMapTimeout());
    Object mutex = getFlashMapsMutex(request);
    if (mutex != null) {synchronized (mutex) {List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
            allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>());
            allFlashMaps.add(flashMap);
            updateFlashMaps(allFlashMaps, request, response);
        }
    }
    else {List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
        allFlashMaps = (allFlashMaps != null ? allFlashMaps : new ArrayList<>(1));
        allFlashMaps.add(flashMap);
        updateFlashMaps(allFlashMaps, request, response);
    }
}

其实这里的逻辑也很简略,保留之前会给 flashMap 设置两个属性,一个是重定向的 url 地址,另一个则是过期工夫,过期工夫默认 180 秒,这两个属性在第三步加载 flashMap 的时候会用到。而后将 flashMap 放入汇合中,并调用 updateFlashMaps 办法存入 session 中。

第三步,当重定向申请达到 DispatcherServlet#doService 办法后,此时会调用 FlashMapManager#retrieveAndUpdate 办法从 Session 中获取 outputFlashMap 并设置到 Request 属性中备用(最终会被转化到 Model 中的属性),相干代码如下:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 省略...
    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);
    }
    // 省略...
}

留神这里获取进去的 outputFlashMap 换了一个名字,变成了 inputFlashMap,其实是同一个货色。

咱们能够大略看一下获取的逻辑 AbstractFlashMapManager#retrieveAndUpdate:

public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
    if (CollectionUtils.isEmpty(allFlashMaps)) {return null;}
    List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
    FlashMap match = getMatchingFlashMap(allFlashMaps, request);
    if (match != null) {mapsToRemove.add(match);
    }
    if (!mapsToRemove.isEmpty()) {Object mutex = getFlashMapsMutex(request);
        if (mutex != null) {synchronized (mutex) {allFlashMaps = retrieveFlashMaps(request);
                if (allFlashMaps != null) {allFlashMaps.removeAll(mapsToRemove);
                    updateFlashMaps(allFlashMaps, request, response);
                }
            }
        }
        else {allFlashMaps.removeAll(mapsToRemove);
            updateFlashMaps(allFlashMaps, request, response);
        }
    }
    return match;
}
  • 首先调用 retrieveFlashMaps 办法从 session 中获取到所有的 FlashMap。
  • 调用 getExpiredFlashMaps 办法获取所有过期的 FlashMap,FlashMap 默认的过期工夫是 180s。
  • 获取和以后申请匹配的 getMatchingFlashMap,具体的匹配逻辑就两点:重定向地址要和以后申请地址雷同;预设参数要雷同。一般来说咱们不须要配置预设参数,所以这一条能够疏忽。如果想要设置,则首先给 flashMap 设置,像这样:flashMap.addTargetRequestParam("aa", "bb");,而后在重定向的地址栏也加上这个参数:return "redirect:/orderlist?aa=bb"; 即可。
  • 将获取到的匹配的 FlashMap 对象放入 mapsToRemove 汇合中(这个匹配到的 FlashMap 行将生效,放入汇合中一会被清空)。
  • 将 allFlashMaps 汇合中的所有 mapsToRemove 数据清空,同时调用 updateFlashMaps 办法更新 session 中的 FlashMap。
  • 最终将匹配到的 flashMap 返回。

这就是整个获取 flashMap 的办法,整体来看还是十分 easy 的,并没有什么难点。

4. 小结

好啦,明天就和小伙伴们分享了一下 SpringMVC 中的 flashMap,不晓得大家有没有在工作中用到这个货色?如果刚好碰到松哥后面所说的需要,用 FlashMap 真的还是蛮不便的。如果须要下载本文案例,小伙伴们能够在公众号【江南一点雨】后盾回复 20210302,好啦,明天就和大家聊这么多~

退出移动版