<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringMVC 中的參數(shù)還能這么傳遞?漲姿勢了!

          共 10793字,需瀏覽 22分鐘

           ·

          2021-03-26 15:21

          松哥原創(chuàng)的 Spring Boot 視頻教程已經(jīng)殺青,感興趣的小伙伴戳這里-->Spring Boot+Vue+微人事視頻教程


          今天來聊一個 JavaWeb 中簡單的話題,但是感覺卻比較稀罕,因?yàn)檫@個技能點(diǎn),有的小伙伴們可能沒聽過!

          1.緣起

          說到 Web 請求參數(shù)傳遞,大家能想到哪些參數(shù)傳遞方式?

          參數(shù)可以放在地址欄中,不過地址欄參數(shù)的長度有限制,并且在有的場景下我們可能不希望參數(shù)暴漏在地址欄中。參數(shù)可以放在請求體中,這個沒啥好說的。

          小伙伴們試想這樣一個場景:

          在一個電商項(xiàng)目中,有一個提交訂單的請求,這個請求是一個 POST 請求,請求參數(shù)都在請求體中。當(dāng)用戶提交成功后,為了防止用戶刷新瀏覽器頁面造成訂單請求重復(fù)提交,我們一般會將用戶重定向到一個顯示訂單的頁面,這樣即使用戶刷新頁面,也不會造成訂單請求重復(fù)提交。

          大概的代碼就像下面這樣:

          @Controller
          public class OrderController {
              @PostMapping("/order")
              public String order(OrderInfo orderInfo) {
                  //其他處理邏輯
                  return "redirect:/orderlist";
              }
          }

          這段代碼我相信大家都懂吧!如果不懂可以看看松哥錄制的免費(fèi)的 SpringMVC 入門教程(硬核!松哥又整了一套免費(fèi)視頻,搞起!)。

          但是這里有一個問題:如果我想傳遞參數(shù)怎么辦?

          如果是服務(wù)器端跳轉(zhuǎn),我們可以將參數(shù)放在 request 對象中,跳轉(zhuǎn)完成后還能拿到參數(shù),但是如果是客戶端跳轉(zhuǎn)我們就只能將參數(shù)放在地址欄中了,像上面這個方法的返回值我們可以寫成:return "redirect:/orderlist?xxx=xxx";,這種傳參方式有兩個缺陷:

          • 地址欄的長度是有限的,也就意味著能夠放在地址欄中的參數(shù)是有限的。
          • 不想將一些特殊的參數(shù)放在地址欄中。

          那該怎么辦?還有辦法傳遞參數(shù)嗎?

          有!這就是今天松哥要和大家介紹的 flashMap,專門用來解決重定向時參數(shù)的傳遞問題。

          2.flashMap

          在重定向時,如果需要傳遞參數(shù),但是又不想放在地址欄中,我們就可以通過 flashMap 來傳遞參數(shù),松哥先來一個簡單的例子大家看看效果:

          首先我們定義一個簡單的頁面,里邊就一個 post 請求提交按鈕,如下:

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

          然后在服務(wù)端接收該請求,并完成重定向:

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

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

          首先在 order 接口中,獲取到 flashMap 屬性,然后存入需要傳遞的參數(shù),這些參數(shù)最終會被 SpringMVC 自動放入重定向接口的 Model 中,這樣我們在 orderlist 接口中,就可以獲取到該屬性了。

          當(dāng)然,這是一個比較粗糙的寫法,我們還可以通過 RedirectAttributes 來簡化這一步驟:

          @Controller
          public class OrderController {
              @PostMapping("/order")
              public String order(RedirectAttributes attr) {
                  attr.addFlashAttribute("site""www.javaboy.org");
                  attr.addAttribute("name""微信公眾號:江南一點(diǎn)雨");
                  return "redirect:/orderlist";
              }

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

          RedirectAttributes 中有兩種添加參數(shù)的方式:

          • addFlashAttribute:將參數(shù)放到 flashMap 中。
          • addAttribute:將參數(shù)放到 URL 地址中。

          經(jīng)過前面的講解,現(xiàn)在小伙伴們應(yīng)該大致明白了 flashMap 的作用了,就是在你進(jìn)行重定向的時候,不通過地址欄傳遞參數(shù)。

          很多小伙伴可能會有疑問,重定向其實(shí)就是瀏覽器發(fā)起了一個新的請求,這新的請求怎么就獲取到上一個請求保存的參數(shù)呢?這我們就要來看看 SpringMVC 的源碼了。

          3.源碼分析

          首先這里涉及到一個關(guān)鍵類叫做 FlashMapManager,如下:

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

          兩個方法含義一眼就能看出來:

          • retrieveAndUpdate:這個方法用來恢復(fù)參數(shù),并將恢復(fù)過的的參數(shù)和超時的參數(shù)從保存介質(zhì)中刪除。
          • saveOutputFlashMap:將參數(shù)保存保存起來。

          FlashMapManager 的實(shí)現(xiàn)類如下:

          從這個繼承類中,我們基本上就能確定默認(rèn)的保存介質(zhì)時 session。具體的保存邏輯則是在 AbstractFlashMapManager 類中。

          整個參數(shù)傳遞的過程可以分為三大步:

          第一步,首先我們將參數(shù)設(shè)置到 outputFlashMap 中,有兩種設(shè)置方式:我們前面的代碼 req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE) 就是直接獲取 outputFlashMap 對象然后把參數(shù)放進(jìn)去;第二種方式就是通過在接口中添加 RedirectAttributes 參數(shù),然后把需要傳遞的參數(shù)放入 RedirectAttributes 中,這樣當(dāng)處理器處理完畢后,會自動將其設(shè)置到 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 的實(shí)例的話,則通過 getOutputFlashMap 方法獲取到 outputFlashMap 屬性,然后相關(guān)的屬性設(shè)置進(jìn)去。

          這是第一步,就是將需要傳遞的參數(shù),先保存到 flashMap 中。

          第二步,重定向?qū)?yīng)的視圖是 RedirectView,在它的 renderMergedOutputModel 方法中,會調(diào)用 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 方法最終就會調(diào)用到 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);
           }
          }

          其實(shí)這里的邏輯也很簡單,保存之前會給 flashMap 設(shè)置兩個屬性,一個是重定向的 url 地址,另一個則是過期時間,過期時間默認(rèn) 180 秒,這兩個屬性在第三步加載 flashMap 的時候會用到。然后將 flashMap 放入集合中,并調(diào)用 updateFlashMaps 方法存入 session 中。

          第三步,當(dāng)重定向請求到達(dá) DispatcherServlet#doService 方法后,此時會調(diào)用 FlashMapManager#retrieveAndUpdate 方法從 Session 中獲取 outputFlashMap 并設(shè)置到 Request 屬性中備用(最終會被轉(zhuǎn)化到 Model 中的屬性),相關(guān)代碼如下:

          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,其實(shí)是同一個東西。

          我們可以大概看一下獲取的邏輯 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;
          }
          • 首先調(diào)用 retrieveFlashMaps 方法從 session 中獲取到所有的 FlashMap。
          • 調(diào)用 getExpiredFlashMaps 方法獲取所有過期的 FlashMap,F(xiàn)lashMap 默認(rèn)的過期時間是 180s。
          • 獲取和當(dāng)前請求匹配的 getMatchingFlashMap,具體的匹配邏輯就兩點(diǎn):重定向地址要和當(dāng)前請求地址相同;預(yù)設(shè)參數(shù)要相同。一般來說我們不需要配置預(yù)設(shè)參數(shù),所以這一條可以忽略。如果想要設(shè)置,則首先給 flashMap 設(shè)置,像這樣:flashMap.addTargetRequestParam("aa", "bb");,然后在重定向的地址欄也加上這個參數(shù):return "redirect:/orderlist?aa=bb"; 即可。
          • 將獲取到的匹配的 FlashMap 對象放入 mapsToRemove 集合中(這個匹配到的 FlashMap 即將失效,放入集合中一會被清空)。
          • 將 allFlashMaps 集合中的所有 mapsToRemove 數(shù)據(jù)清空,同時調(diào)用 updateFlashMaps 方法更新 session 中的 FlashMap。
          • 最終將匹配到的 flashMap 返回。

          這就是整個獲取 flashMap 的方法,整體來看還是非常 easy 的,并沒有什么難點(diǎn)。

          4.小結(jié)

          好啦,今天就和小伙伴們分享了一下 SpringMVC 中的 flashMap,不知道大家有沒有在工作中用到這個東西?如果剛好碰到松哥前面所說的需求,用 FlashMap 真的還是蠻方便的。

          瀏覽 152
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  波多野结衣在线网站 | 欧美中文字幕 | 性爱黄色视频 | 一级欧美一级日韩片 | 大香蕉视频更新资源 |