整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          SpringMVC 中的參數還能這么傳遞?漲姿勢了

          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,好啦,今天就和大家聊這么多~

          標題:了解如何使用 Spring MVC 和 Thymeleaf 創建網頁(來源:Spring中國教育管理中心)

          本指南將引導您完成使用 Spring 創建“Hello, World”網站的過程。

          你將建造什么

          您將構建一個具有靜態主頁并且還將接受 HTTP GET 請求的應用程序:http://localhost:8080/greeting

          它將響應一個顯示 HTML 的網頁。HTML 的正文將包含一個問候語:“Hello, World!”

          name您可以使用查詢字符串中的可選參數自定義問候語。該 URL 可能是http://localhost:8080/greeting?name=User.

          name參數值覆蓋默認值,并通過World內容更改為“Hello,User!”反映在響應中

          你需要什么

          • 約15分鐘
          • 最喜歡的文本編輯器或 IDE
          • JDK 1.8或更高版本
          • Gradle 4+或Maven 3.2+
          • 您還可以將代碼直接導入 IDE:
            • 彈簧工具套件 (STS)
            • IntelliJ IDEA

          如何完成本指南

          像大多數 Spring入門指南一樣,您可以從頭開始并完成每個步驟,也可以繞過您已經熟悉的基本設置步驟。無論哪種方式,您最終都會得到工作代碼。

          從頭開始,請繼續從 Spring Initializr 開始。

          跳過基礎知識,請執行以下操作:

          • 下載并解壓縮本指南的源存儲庫,或使用Git克隆它:git clone https://github.com/spring-guides/gs-serving-web-content.git
          • 光盤進入gs-serving-web-content/initial
          • 跳轉到創建 Web 控制器。

          完成后,您可以對照中的代碼檢查結果gs-serving-web-content/complete

          從 Spring Initializr 開始

          您可以使用這個預先初始化的項目并單擊 Generate 下載 ZIP 文件。此項目配置為適合本教程中的示例。

          手動初始化項目:

          1. 導航到https://start.spring.io。該服務提取應用程序所需的所有依賴項,并為您完成大部分設置。
          2. 選擇 Gradle 或 Maven 以及您要使用的語言。本指南假定您選擇了 Java。
          3. 單擊Dependencies并選擇Spring WebThymeleafSpring Boot DevTools
          4. 單擊生成
          5. 下載生成的 ZIP 文件,該文件是根據您的選擇配置的 Web 應用程序的存檔。

          如果您的 IDE 具有 Spring Initializr 集成,您可以從您的 IDE 完成此過程。

          你也可以從 Github 上 fork 項目并在你的 IDE 或其他編輯器中打開它。

          創建 Web 控制器

          在 Spring 構建網站的方法中,HTTP 請求由控制器處理。@Controller您可以通過注釋輕松識別控制器。在以下示例中,通過返回 a 的名稱(在本例中GreetingController為 )來處理 GET 請求。A負責呈現 HTML 內容。以下清單(來自)顯示了控制器:/greetingViewgreetingViewsrc/main/java/com/example/servingwebcontent/GreetingController.java

          package com.example.servingwebcontent;
          
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RequestParam;
          
          @Controller
          public class GreetingController {
          
          	@GetMapping("/greeting")
          	public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
          		model.addAttribute("name", name);
          		return "greeting";
          	}
          
          }復制

          這個控制器簡潔明了,但是有很多事情要做。我們一步一步分解。

          @GetMapping注釋確保 HTTP GET 請求/greeting映射到greeting()方法。

          @RequestParam將查詢字符串參數的值綁定name到方法的name參數中greeting()。此查詢字符串參數不是required。如果請求中不存在,則使用defaultValueof World。參數的值name被添加到Model對象中,最終使視圖模板可以訪問它。

          方法體的實現依賴于視圖技術(在本例中為Thymeleaf)來執行 HTML 的服務器端呈現。Thymeleaf 解析greeting.html模板并評估th:text表達式以呈現${name}在控制器中設置的參數值。以下清單(來自src/main/resources/templates/greeting.html)顯示了greeting.html模板:

          <!DOCTYPE HTML>
          <html xmlns:th="http://www.thymeleaf.org">
          <head> 
              <title>Getting Started: Serving Web Content</title> 
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
          </head>
          <body>
              <p th:text="'Hello, ' + ${name} + '!'" />
          </body>
          </html>

          確保你的類路徑上有 Thymeleaf(工件坐標:)org.springframework.boot:spring-boot-starter-thymeleaf。它已經存在于 Github 的“初始”和“完整”示例中。

          Spring Boot 開發工具

          開發 Web 應用程序的一個常見功能是編寫更改代碼、重新啟動應用程序并刷新瀏覽器以查看更改。整個過程會消耗大量時間。為了加快這個刷新周期,Spring Boot 提供了一個方便的模塊,稱為spring-boot-devtools。Spring Boot 開發工具:

          • 啟用熱插拔。
          • 切換模板引擎以禁用緩存。
          • 啟用 LiveReload 以自動刷新瀏覽器。
          • 基于開發而不是生產的其他合理默認值。

          運行應用程序

          Spring Initializr 為您創建了一個應用程序類。在這種情況下,您不需要進一步修改 Spring Initializr 提供的類。以下清單(來自src/main/java/com/example/servingwebcontent/ServingWebContentApplication.java)顯示了應用程序類:

          package com.example.servingwebcontent;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          
          @SpringBootApplication
          public class ServingWebContentApplication {
          
              public static void main(String[] args) {
                  SpringApplication.run(ServingWebContentApplication.class, args);
              }
          
          }復制

          @SpringBootApplication是一個方便的注釋,它添加了以下所有內容:

          • @Configuration: 將類標記為應用程序上下文的 bean 定義源。
          • @EnableAutoConfiguration:告訴 Spring Boot 根據類路徑設置、其他 bean 和各種屬性設置開始添加 bean。例如,如果spring-webmvc位于類路徑上,則此注釋將應用程序標記為 Web 應用程序并激活關鍵行為,例如設置DispatcherServlet.
          • @ComponentScan: 告訴 Spring 在包中查找其他組件、配置和服務com/example,讓它找到控制器。

          main()方法使用 Spring Boot 的SpringApplication.run()方法來啟動應用程序。您是否注意到沒有一行 XML?也沒有web.xml文件。這個 Web 應用程序是 100% 純 Java,您不必處理任何管道或基礎設施的配置。

          構建一個可執行的 JAR

          您可以使用 Gradle 或 Maven 從命令行運行應用程序。您還可以構建一個包含所有必要依賴項、類和資源的單個可執行 JAR 文件并運行它。構建可執行 jar 可以在整個開發生命周期、跨不同環境等中輕松地將服務作為應用程序交付、版本化和部署。

          如果您使用 Gradle,則可以使用./gradlew bootRun. 或者,您可以使用構建 JAR 文件./gradlew build,然后運行 ?JAR 文件,如下所示:

          java -jar build/libs/gs-serving-web-content-0.1.0.jar

          如果您使用 Maven,則可以使用./mvnw spring-boot:run. 或者,您可以使用構建 JAR 文件,./mvnw clean package然后運行該 JAR 文件,如下所示:

          java -jar 目標/gs-serving-web-content-0.1.0.jar

          此處描述的步驟創建了一個可運行的 JAR。您還可以構建經典的 WAR 文件。

          顯示記錄輸出。應用程序應在幾秒鐘內啟動并運行。

          測試應用程序

          現在網站正在運行,請訪問http://localhost:8080/greeting,您應該會看到“Hello, World!”。

          通過訪問提供name查詢字符串參數http://localhost:8080/greeting?name=User。注意消息是如何從“Hello, World!”改變的。到“你好,用戶!”:

          這一變化表明,@RequestParam安排在GreetingController按預期工作。該name參數已被賦予默認值World,但可以通過查詢字符串顯式覆蓋它。

          添加主頁

          靜態資源,包括 HTML、JavaScript 和 CSS,可以通過將它們放到源代碼中的正確位置從 Spring Boot 應用程序提供。默認情況下,Spring Boot 從位于/static(或/public) 的類路徑中的資源中提供靜態內容。該index.html資源是特殊的,因為如果它存在,它會被用作“歡迎頁面” "serving-web-content/ which means it is served up as the root resource (that is, at `http://localhost:8080/)。因此,您需要創建以下文件(您可以在 中找到該文件src/main/resources/static/index.html):

          <!DOCTYPE HTML>
          <html>
          <head> 
              <title>Getting Started: Serving Web Content</title> 
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
          </head>
          <body>
              <p>Get your greeting <a href="/greeting">here</a></p>
          </body>
          </html>復制

          重新啟動應用程序時,您將看到 HTML 位于http://localhost:8080/.

          概括

          恭喜!您剛剛使用 Spring 開發了一個網頁。

          #程序員#

          活就像海洋,只有意志堅強的人才能到達彼岸。

          已經很久沒有發文章了呀,想必大家都掛念我了,哈哈。

          溫故而知新,今天一起來復習一下spring mvc的內容吧。

          spring mvc簡介與運行原理

          Spring的模型-視圖-控制器(MVC)框架是圍繞一個DispatcherServlet來設計的,這個Servlet會把請求分發給各個處理器,并支持可配置的處理器映射、視圖渲染、本地化、時區與主題渲染等,甚至還能支持文件上傳。

          原理.png

          • (1) Http請求:客戶端請求提交到DispatcherServlet。
          • (2) 尋找處理器:由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller。
          • (3) 調用處理器:DispatcherServlet將請求提交到Controller。
          • (4)(5)調用業務處理和返回結果:Controller調用業務邏輯處理后,返回ModelAndView。
          • (6)(7)處理視圖映射并返回模型: DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖。
          • (8) Http響應:視圖負責將結果顯示到客戶端。

          主要注解

          spring mvc注解.png

          ContextLoaderListener

          在講ContextLoaderListener之前,首先來了解一下web.xml的作用。

          • 一個web中可以沒有web.xml文件,也就是說,web.xml文件并不是web工程必須的。web.xml文件是用來初始化配置信息:比如Welcome頁面、servlet、servlet-mapping、filter、listener、啟動加載級別等。當你的web工程沒用到這些時,你可以不用web.xml文件來配置你的Application。
          • 當要啟動某個web項目時,服務器軟件或容器如(tomcat)會第一步加載項目中的web.xml文件,通過其中的各種配置來啟動項目,只有其中配置的各項均無誤時,項目才能正確啟動。web.xml有多項標簽,在其加載的過程中順序依次為:context-param >> listener >> fileter >> servlet?。(同類多個節點以出現順序依次加載)

          web.xml加載過程.png

          而spring mvc啟動過程大致分為兩個過程:

          • ContextLoaderListener初始化,實例化IoC容器,并將此容器實例注冊到ServletContext中。
          • DispatcherServlet初始化。

          web.xml配置.png

          其中ContextLoaderListener監聽器它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啟動容器時,就會默認執行它實現的方法。在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成。

          • ContextLoaderListener在web.xml中的配置
          <!-- 配置contextConfigLocation初始化參數 -->
          <context-param>
           <param-name>contextConfigLocation</param-name>
           <param-value>/WEB-INF/applicationContext.xml</param-value>
          </context-param>
          <!-- 配置ContextLoaderListerner -->
          <listener>
           <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener> 
          

          ServletContextListener 接口有兩個方法:contextInitialized,contextDestroyed

          DispatcherServlet

          Spring MVC框架,與其他很多web的MVC框架一樣:請求驅動;所有設計都圍繞著一個中央Servlet來展開,它負責把所有請求分發到控制器;同時提供其他web應用開發所需要的功能。不過Spring的中央處理器,DispatcherServlet,能做的比這更多。

          下圖展示了Spring Web MVC的DispatcherServlet處理請求的工作流。熟悉設計模式的朋友會發現,DispatcherServlet應用的其實就是一個“前端控制器”的設計模式(其他很多優秀的web框架也都使用了這個設計模式)。

          • 流程圖

          spring mvc處理請求的流程.jpg

          • 在web.xml中的配置
          <!-- servlet定義 -->
          <servlet>
           <servlet-name>dispatcher</servlet-name>
           <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
           <load-on-startup>1</load-on-startup>
          </servlet>
           
          <servlet-mapping>
           <servlet-name>dispatcher</servlet-name>
           <url-pattern>/</url-pattern>
          </servlet-mapping>
          

          其中

          • load-on-startup:表示啟動容器時初始化該Servlet;
          • url-pattern:表示哪些請求交給Spring Web MVC處理, “/” 是用來定義默認servlet映射的。也可以如“*.html”表示攔截所有以html為擴展名的請求。

          在Spring MVC中,每個DispatcherServlet都持有一個自己的上下文對象WebApplicationContext,它又繼承了根(root)WebApplicationContext對象中已經定義的所有bean。這些繼承的bean可以在具體的Servlet實例中被重載,在每個Servlet實例中你也可以定義其scope下的新bean。

          WebApplicationContext繼承自ApplicationContext,它提供了一些web應用經常需要用到的特性。它與普通的ApplicationContext不同的地方在于,它支持主題的解析,并且知道它關聯到的是哪個servlet(它持有一個該ServletContext的引用)

          DispatcherServlet繼承結構

          spring mvc同時提供了很多特殊的注解,用于處理請求和渲染視圖等。DispatcherServlet初始化的過程中會默認使用這些特殊bean進行配置。如果你想指定使用哪個特定的bean,你可以在web應用上下文WebApplicationContext中簡單地配置它們。

          特殊bean.png

          其中,常用的ViewResolver的配置。以jsp作為視圖為例

          <!-- 對模型視圖名稱的解析,即在模型視圖名稱添加前后綴 -->
          <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
           <property name="prefix" value="/WEB-INF/jsp/" />
           <property name="suffix" value=".jsp" />
          </bean>
          

          配置上傳文件限制MultipartResolver

          <!-- 上傳限制 -->
          <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
           <!-- 上傳文件大小限制為31M,31*1024*1024 -->
           <property name="maxUploadSize" value="32505856"/>
          </bean>
          

          applicationContext.xml中的標簽

          applicationContext.xml配置文件標簽.png

          文件上傳

          前面說到DispatcherServlet中有個特殊的Bean叫MultipartResolver,可用于限制文件的上傳大小等。當解析器MultipartResolver完成處理時,請求便會像其他請求一樣被正常流程處理。

          • 表單
          <form method="post" action="/form" enctype="multipart/form-data">
           <input type="text" name="name"/>
           <input type="file" name="file"/>
           <input type="submit"/>
          </form>
          
          • 控制器

          @RequestMapping(path="/form", method=RequestMethod.POST)

          public String handleFormUpload(@RequestParam("name") String name,

          @RequestParam("file") MultipartFile file) {

          if (!file.isEmpty()) {

          byte[] bytes=file.getBytes();

          // store the bytes somewhere

          return "redirect:uploadSuccess";

          }

          return "redirect:uploadFailure";

          }

          異常處理

          先來說下常見的異常處理有幾種方式,如下圖:

          異常處理方式.png

          Spring的處理器異常解析器HandlerExceptionResolver接口的實現負責處理各類控制器執行過程中出現的異常。也是上面提到的,是DispatcherServlet中的特殊bean,可以自定義配置處理。

          某種程度上講,HandlerExceptionResolver與你在web應用描述符web.xml文件中能定義的異常映射(exception mapping)很相像,不過它比后者提供了更靈活的方式。比如它能提供異常被拋出時正在執行的是哪個處理器這樣的信息。

          • HandlerExceptionResolver 提供resolveException接口
          public interface HandlerExceptionResolver { 
           ModelAndView resolveException( 
           HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); 
          } 
          
          • 在BaseController中使用 @ExceptionHandler注解處理異常
           @ExceptionHandler(Exception.class)
           public Object exceptionHandler(Exception ex, HttpServletResponse response, 
           HttpServletRequest request) throws IOException {
           String url="";
           String msg=ex.getMessage();
           Object resultModel=null;
           try {
           if (ex.getClass()==HttpRequestMethodNotSupportedException.class) {
           url="admin/common/500";
           System.out.println("--------毛有找到對應方法---------");
           } else if (ex.getClass()==ParameterException.class) {//自定義的異常
           
           } else if (ex.getClass()==UnauthorizedException.class) {
           url="admin/common/unauth";
           System.out.println("--------毛有權限---------");
           }
           String header=req.getHeader("X-Requested-With");
           boolean isAjax="XMLHttpRequest".equalsIgnoreCase(header);
           String method=req.getMethod();
           boolean isPost="POST".equalsIgnoreCase(method);
           if (isAjax || isPost) {
           return Message.error(msg);
           } else {
           ModelAndView view=new ModelAndView(url);
           view.addObject("error", msg);
           view.addObject("class", ex.getClass());
           view.addObject("method", request.getRequestURI());
           return view;
           }
           } catch (Exception exception) {
           logger.error(exception.getMessage(), exception);
           return resultModel;
           } finally {
           logger.error(msg, ex);
           ex.printStackTrace();
           }
           }
          
          • *在web.xml中處理異常 *
          <!-- 默認的錯誤處理頁面 -->
          <error-page>
           <error-code>403</error-code>
           <location>/403.html</location>
          </error-page>
          <error-page>
           <error-code>404</error-code>
           <location>/404.html</location>
          </error-page>
          <!-- 僅僅在調試的時候注視掉,在正式部署的時候不能注釋 -->
          <!-- 這樣配置也是可以的,表示發生500錯誤的時候,轉到500.jsp頁面處理。 -->
          <error-page> 
           <error-code>500</error-code> 
           <location>/500.html</location> 
          </error-page> 
          <!-- 這樣的配置表示如果jsp頁面或者servlet發生java.lang.Exception類型(當然包含子類)的異常就會轉到500.jsp頁面處理。 -->
          <error-page> 
           <exception-type>java.lang.Exception</exception-type> 
           <location>/500.jsp</location> 
          </error-page> 
          <error-page> 
           <exception-type>java.lang.Throwable</exception-type> 
           <location>/500.jsp</location> 
          </error-page>
          <!-- 當error-code和exception-type都配置時,exception-type配置的頁面優先級高及出現500錯誤,發生異常Exception時會跳轉到500.jsp--> 
          
          • 來一個問題:HandlerExceptionResolver和web.xml中配置的error-page會有沖突嗎?

          解答:如果resolveException返回了ModelAndView,會優先根據返回值中的頁面來顯示。不過,resolveException可以返回null,此時則展示web.xml中的error-page的500狀態碼配置的頁面。

          當web.xml中有相應的error-page配置,則可以在實現resolveException方法時返回null。

          API文檔中對返回值的解釋:

          ** return a corresponding ModelAndView to forward to, or null for default processing.**

          寫在最后

          歡迎大家關注,后期文章一定更加精彩


          上一篇:Nginx
          下一篇:JavaScript Document 對象
          主站蜘蛛池模板: 亚洲一区二区无码偷拍| 久久久久久一区国产精品| 亚洲一区二区三区丝袜| 久草新视频一区二区三区| 国产精品综合AV一区二区国产馆| 精品国产一区二区三区在线| 日韩制服国产精品一区| 国产精品免费综合一区视频| 亚洲天堂一区在线| 中文字幕在线无码一区二区三区| 亚洲色欲一区二区三区在线观看 | 国产精品久久无码一区二区三区网| 中文字幕人妻无码一区二区三区 | 无码人妻精品一区二区三区99性 | 国产成人无码aa精品一区| 成人在线视频一区| 日韩一区二区在线免费观看| 一区精品麻豆入口| 无码精品人妻一区二区三区漫画 | 色妞AV永久一区二区国产AV | 视频在线一区二区| 国产微拍精品一区二区| 麻豆视传媒一区二区三区| 国产av天堂一区二区三区| 无码av免费毛片一区二区| 无码少妇一区二区浪潮av| 日韩人妻无码一区二区三区久久| 中文字幕亚洲一区| 亚洲国产情侣一区二区三区 | 国产免费播放一区二区| tom影院亚洲国产一区二区| 99精品国产一区二区三区不卡| 精品国产免费观看一区| 另类一区二区三区| 视频精品一区二区三区| 中日韩精品无码一区二区三区| 国产一区麻豆剧传媒果冻精品| 成人日韩熟女高清视频一区| 日韩久久精品一区二区三区| 日韩爆乳一区二区无码| 成人免费av一区二区三区|