今天來聊一個 JavaWeb 中簡單的話題,可是感受卻比較稀罕,由於這個技能點,有的小夥伴們可能沒聽過!html
說到 Web 請求參數傳遞,你們能想到哪些參數傳遞方式?java
參數能夠放在地址欄中,不過地址欄參數的長度有限制,而且在有的場景下咱們可能不但願參數暴漏在地址欄中。參數能夠放在請求體中,這個沒啥好說的。web
小夥伴們試想這樣一個場景:segmentfault
在一個電商項目中,有一個提交訂單的請求,這個請求是一個 POST 請求,請求參數都在請求體中。當用戶提交成功後,爲了防止用戶刷新瀏覽器頁面形成訂單請求重複提交,咱們通常會將用戶重定向到一個顯示訂單的頁面,這樣即便用戶刷新頁面,也不會形成訂單請求重複提交。瀏覽器
大概的代碼就像下面這樣:服務器
@Controller public class OrderController { @PostMapping("/order") public String order(OrderInfo orderInfo) { //其餘處理邏輯 return "redirect:/orderlist"; } }
這段代碼我相信你們都懂吧!若是不懂能夠看看鬆哥錄製的免費的 SpringMVC 入門教程(硬核!鬆哥又整了一套免費視頻,搞起!)。微信
可是這裏有一個問題:若是我想傳遞參數怎麼辦?session
若是是服務器端跳轉,咱們能夠將參數放在 request 對象中,跳轉完成後還能拿到參數,可是若是是客戶端跳轉咱們就只能將參數放在地址欄中了,像上面這個方法的返回值咱們能夠寫成:return "redirect:/orderlist?xxx=xxx";
,這種傳參方式有兩個缺陷:app
那該怎麼辦?還有辦法傳遞參數嗎?源碼分析
有!這就是今天鬆哥要和你們介紹的 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 中有兩種添加參數的方式:
通過前面的講解,如今小夥伴們應該大體明白了 flashMap 的做用了,就是在你進行重定向的時候,不經過地址欄傳遞參數。
不少小夥伴可能會有疑問,重定向其實就是瀏覽器發起了一個新的請求,這新的請求怎麼就獲取到上一個請求保存的參數呢?這咱們就要來看看 SpringMVC 的源碼了。
首先這裏涉及到一個關鍵類叫作 FlashMapManager,以下:
public interface FlashMapManager { @Nullable FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response); void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response); }
兩個方法含義一眼就能看出來:
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; }
flashMap.addTargetRequestParam("aa", "bb");
,而後在重定向的地址欄也加上這個參數:return "redirect:/orderlist?aa=bb";
便可。這就是整個獲取 flashMap 的方法,總體來看仍是很是 easy 的,並無什麼難點。
好啦,今天就和小夥伴們分享了一下 SpringMVC 中的 flashMap,不知道你們有沒有在工做中用到這個東西?若是恰好碰到鬆哥前面所說的需求,用 FlashMap 真的仍是蠻方便的。若是須要下載本文案例,小夥伴們能夠在公衆號【江南一點雨】後臺回覆 20210302
,好啦,今天就和你們聊這麼多~