spring mvc原理淺析

Spring MVC主要包括以下要點:
1:由DispatcherServlet控制的整個流程;
2:註解驅動的控制器,其中包括請求映射、數據的綁定和格式化;
3:文件上傳;
4:一些雜項,如靜態資源處理、異常處理等等。
這些東西構成了精緻的Spring MVC框架,下面我將針對這些要點做詳細討論,以期其能在開發上對各位觀衆有些作用。
1.   Spring MVC框架原理

Spring
 
 
       DispatcherServlet是Spring MVC的靈魂和心臟,它負責接收HTTP請求並協調Spring MVC的各個組件完成請求處理的工作。和任何Servlet一樣,用戶必須在web.xml中配置好DispatcherServlet,並且讓其接受一 切HTTP請求。當用戶的請求被截獲時,DispatcherServlet通過HandlerMapping定位到特定的Controller(使用 @Controller註解的普通Java類,此處已經定位到了具體的業務處理方法了,所以我們稱其爲Handler)。然後通過 HandlerAdapter調用Handler中對應的業務處理方法(從這裏可以看出與Struts不同的是,Spring MVC是方法級的攔截)。業務處理方法返回一個邏輯視圖名(View)和模型數據(Model,二者統稱ModelAndView)交給 DispatcherServlet,DispatcherServlet調用ViewResolver解析出真實的視圖對象,得到這個視圖對象 後,DispatcherServlet就使用Model對其進行渲染,將最後結果返回給用戶。
       要了解Spring MVC框架的工作原理,必須回答以下三個問題:
              1)  DispatcherServlet如何截獲特定的HTTP請求,交由Spring MVC框架處理?
              2)  位於Web層的Spring容器(WebApplicationContext),如何與位於業務層的Spring容器(ApplicationContext)建立關聯,以使Web層的Bean可以調用業務層的Bean?
              3)  如何初始化SpringMVC的各個組件,並將它們裝配到DispatcherServlet中?
第一個問題已然在上面的步驟中說明了,很簡單。對於第二條,其實Web層的容器是作爲業務層容器的子容器來配置的,所以訪問不是問題。第三,DispatcherServlet有一個初始化方法initStrategies,它在WebApplicationContext初始化後執行,此時所有的組件Bean均已可用。該方法通過反射機制查找並裝配Spring容器中用戶顯示自定義的組件Bean,如果找不到再裝配默認的組件實例。
       怎麼樣,是不是對SpringMVC的工作原理有一個模糊的認識了。到這一步你只要知道Spring MVC也是基於Servlet的,它可以根據URL直接定位到業務處理方法,同時我們可以天然地使用Spring容器,相當之美!
2.   註解驅動的控制器
       正如上面所說,SpringMVC可以直接定位到業務處理方法,那麼我們提交的數據是不是還要像Servlet那樣通過HTTPServlet來獲取,或 者可以像Struts一樣綁定到Form中或是Action裏面。當然可以,不僅僅如此,Spring MVC還提供了更加強大的數據綁定和轉化的功能,使之將Struts之類遠遠甩到後面去了。
2.1. 請求映射
       在POJO類定義處標註@Controller,再通過<context:component-scan/>掃描相應的類包,即可使POJO 成爲一個能處理HTTP請求的控制器。一個控制器的每一個方法都可以成爲請求處理方法,如何將請求映射到控制器的方法中是Spring MVC框架最重要的任務之一,這項任務由@RequestMapping承擔。

<ignore_js_op style="word-wrap: break-word;">mapping.jpg
 
       ①處的註解很重要,Spring會在啓動的時候掃描它,將其劃入到Handler中去,沒有它一切都白搭。②處的@RequestMapping標註的路 徑是相對於應用系統根路徑的,在此處寫這個註解是爲了同一控制器的多個處理方法負責處理相同業務模塊的不同操作,這個註解亦可省略,但建議不要這樣。③處 的註解是必須的,要定位到具體的處理方法中去。上面的顯示列表的URL可以是這樣:host:port/app/excavation /list.XXX。
       @RequestMapping不但支持標準的URL,還支持Ant風格(即?、*和**的字符)的和帶{xxx}佔位符的URL。以下URL都是合法的:
  1. /user/*/createUser
  2.    匹配/user/aaa/createUser、/user/bbb/createUser等URL。
  3. /user/**/createUser
  4.    匹配/user/createUser、/user/aaa/bbb/createUser等URL。
  5. /user/createUser??
  6.    匹配/user/createUseraa、/user/createUserbb等URL。
  7. /user/{userId}
  8.    匹配user/123、user/abc等URL。
  9. /user/**/{userId}
  10.    匹配user/aaa/bbb/123、user/aaa/456等URL。
  11. company/{companyId}/user/{userId}/detail
  12.    匹配company/123/user/456/detail等的URL。
複製代碼
       佔位符的URL是Spring 3.0新增的功能,該功能在SpringMVC向REST目標挺進的發展過程中具有里程碑的意義。通過@PathVariable可以將URL中的佔位符參數綁定到控制器處理方法的入參中,如下所示:

<ignore_js_op style="word-wrap: break-word;">rest.jpg
 
       除了通過URL進行映射外,我們還可以通過請求參數、請求方法和請求頭進行映射,由於以上方法已經足夠我們進行通常的開發,所以這裏就不再詳述其他映射方法了,有興趣的同事可以查詢互聯網。
2.2. 數據的綁定
       前面說過了,SpringMVC是方法級的映射,那麼Spring是如何處理方法簽名的,又是如何將表單數據綁定到方法參數中的?下面我們就來討論這個問題。
2.2.1. 處理方法簽名
       首先,我們可以在方法簽名中放入@CookieValue註解參數,Spring自動將Cookie值綁定到參數中;同理@RequestHeader可 以綁定報文頭的屬性值;同時我們還可以將Servlet API如HttpServletRequest、HttpServletResponse、HttpSession、WebRequest直接作爲方法參 數,Spring負責綁定;Spring MVC還允許控制器的處理方法使用java.io.InputStream/java.io.Reader及java.io.OutputStream /java.io.Writer作爲方法的入參,SpringMVC將獲取ServletRequest的InputStream/Reader或 ServletResponse的OutputStream/Writer,然後按類型匹配的方式,傳遞給控制器的處理方法入參;控制器處理方法的入參除 支持以上類型的參數以外,還支持java.util.Locale、java.security.Principal,可以通過Servlet的 HttpServletRequest的getLocale()及getUserPrincipal()得到相應的值。如果處理方法的入參類型爲 Locale或Principal,Spring MVC自動從請求對象中獲取相應的對象並傳遞給處理方法的入參。
2.2.1. 表單數據綁定到方法參數
       再有,表單的數據只要名稱相同就可以往方法參數中放,或者是級聯的可以封裝成對象置於參數中,Spring會自動綁定,如下圖所示,極其地方便簡單:
數據綁定.jpg
 
2.2.3. HttpMessageConverter<T>
       最後,還有一類處理方法入參的形式,即使用HttpMessageConverter<T>,這個非常強大。它提供了兩種途徑:
              1)        使用@RequestBody/@ResponseBody對處理方法進行標註;
              2)        使用HttpEntity<T>/ResponseEntity<T>作爲處理方法的入參或返回值。
HttpMessageConverter顧名思義,它負責將請求信息轉換爲一個對象,或者將對象輸出爲響應信息。前面說過,當請求映射到具體的處理方法後,DispatcherServlet調用HandlerAdapter來封裝並執行處理方法。DispatcherServlet默認已經安裝了AnnotationMethodHandlerAdapter作爲HandlerAdapter的組件實現類,HttpMessageConverter即由AnnotationMethodHandlerAdapter使用,將請求信息轉換爲對象,或者將對象轉換爲響應信息。先看幾個示例:
  1. 將報文體轉換爲字符串綁定到方法入參中
  2. @RequestMapping(value= "/handle41")
  3. publicString handle41(@RequestBody  StringrequestBody ) {
  4.       System.out.println(requestBody);
  5.       return "success";
  6. }
複製代碼
  1. 讀取一張圖片,並將圖片數據輸出到響應流中,客戶端將顯示這張圖片
  2. @ResponseBody
  3. @RequestMapping(value= "/handle42/{imageId}")
  4. public byte[]handle42(@PathVariable("imageId") String imageId) throwsIOException {
  5.        System.out.println("load image of"+imageId);
  6.        Resource res = newClassPathResource("/image.jpg");
  7.        byte[] fileData=FileCopyUtils.copyToByteArray(res.getInputStream());
  8.        return fileData;
  9. }
複製代碼
  1. @RequestMapping(value= "/handle43")
  2. publicString handle43(HttpEntity<String> httpEntity){
  3.      long contentLen = httpEntity.getHeaders().getContentLength();
  4.      System.out.println(httpEntity.getBody());
  5.       return "success";
  6. }
  7. @RequestMapping(params= "method=login")
  8. public ResponseEntity<String>doFirst(){
  9.     HttpHeaders headers = new HttpHeaders();
  10.     MediaType mt=newMediaType("text","html",Charset.forName(“UTF-8"));
  11.     headers.setContentType(mt);
  12.     ResponseEntity<String> re=null;
  13.     String return = newString("test");
  14.     re=newResponseEntity<String>(return,headers, HttpStatus.OK);
  15.     return re;
  16. }
複製代碼
       這裏講一下HttpMessageConverter中的重點@ResponseBody,我們用它來處理XML和JSON非常之方便。只要在 SpringWeb容器中爲AnnotationMethodHandlerAdapter裝配好相應的處理XML、JSON的 HttpMessageConverter(AnnotationMethodHandlerAdapter默認只裝配部分轉換器),並在交互中通過請求 的Accept指定MIME類型,Spring MVC就可以使服務端的處理方法和客戶端透明地通過XML或JSON格式的消息進行通信了。

<ignore_js_op style="word-wrap: break-word;">XJ.jpg
 
代碼中我們可以這樣做:

J.jpg-wrap: 
 
       這部分其實很簡單,對於服務端的處理方法而言,除使用@RequestBody/@ResponseBodyHttpEntity<T>/ResponseEntity<T>進行方法簽名外,不需要進行任何額外的處理,藉由Spring MVC中裝配的HttpMessageConverter,它即擁有了處理XMLJSON的能力了。
3.   文件上傳
       Spring MVC爲文件上傳提供了直接的支持,這種支持是通過即插即用的MultipartResolver實現的。

<ignore_js_op style="word-wrap: break-word;">文件上傳.jpg
 
       爲了能使CommonsMultipartResolver正確工作,必須將JakartaCommons FileUploadJakarta Commons io的類包添加到類路徑下。下面是代碼的寫法:

<ignore_js_op style="word-wrap: break-word;">文件上傳代碼.jpg
file:///C:\Users\wmq\AppData\Local\Temp\msohtmlclip1\01\clip_image015.jpg
4.   雜項
       這裏主要講一下靜態文件的處理。如何訪問到靜態的文件,如jpg,js,css?如果你的DispatcherServlet攔截 *.do這樣的URL,就不存在訪問不到靜態資源的問題。如果你的DispatcherServlet攔截“/”,攔截了所有的請求,同時 對*.js,*.jpg的訪問也就被攔截了。這種情況下如何搞定靜態文件訪問問題:
 
方案一:激活TomcatdefaultServlet來處理靜態文件
  1. <servlet-mapping>
  2.             <servlet-name>default</servlet-name>
  3.             <url-pattern>*.jpg</url-pattern>
  4.          </servlet-mapping>
  5.          <servlet-mapping>
  6.             <servlet-name>default</servlet-name>
  7.             <url-pattern>*.js</url-pattern>
  8.          </servlet-mapping>
  9.          <servlet-mapping>
  10.             <servlet-name>default</servlet-name>
  11.             <url-pattern>*.css</url-pattern>
  12.          </servlet-mapping>
複製代碼
    要配置多個,每種文件配置一個;要寫在DispatcherServlet的前面,讓defaultServlet先攔截,這個就不會進入Spring了,我想性能是最好的吧。各服務器defaultServlet名稱如下:
  1. Tomcat,Jetty, JBoss, and GlassFish  默認 Servlet的名字 --"default"
  2. GoogleApp Engine 默認 Servlet的名字 -- "_ah_default"
  3. Resin 默認 Servlet的名字 --"resin-file"
  4. WebLogic 默認 Servlet的名字  -- "FileServlet"
  5. WebSphere  默認 Servlet的名字 --"SimpleFileServlet"
複製代碼
方案二:spring3.0.4以後版本提供了mvc:resources
  1. <!-- 對靜態資源文件的訪問-->
  2. <mvc:resourcesmapping="/images/**" location="/images/" />
複製代碼
    /images/**映射到ResourceHttpRequestHandler進行處理,location指定靜態資源的位置.可以是webapplication根目錄下、jar包裏面,這樣可以把靜態資源壓縮到jar包中。使用<mvc:resources/>元素,mappingURI註冊到SimpleUrlHandlerMappingurlMap,keymappingURI pattern,valueResourceHttpRequestHandler,這樣就巧妙的把對靜態資源的訪問由HandlerMapping轉到ResourceHttpRequestHandler處理並返回,所以就支持classpath目錄,jar包內靜態資源的訪問.另外需要注意的一點是,不要對SimpleUrlHandlerMapping設置defaultHandler.因爲對static uridefaultHandler就是ResourceHttpRequestHandler,否則無法處理static resourcesrequest.
 
方案三,使用<mvc:default-servlet-handler/>
  1. <mvc:default-servlet-handler/>
複製代碼
    會把"/**" url,註冊到SimpleUrlHandlerMappingurlMap,把對靜態資源的訪問由HandlerMapping轉到DefaultServletHttpRequestHandler處理並返回。DefaultServletHttpRequestHandler使用就是各個Servlet容器自己的默認Servlet
補充說明:多個HandlerMapping的執行順序問題:
  1. DefaultAnnotationHandlerMapping的order屬性值是:0
  2. <mvc:resources/>自動註冊的 SimpleUrlHandlerMapping的order屬性值是:2147483646
  3. <mvc:default-servlet-handler/>自動註冊 的SimpleUrlHandlerMapping的order屬性值是: 2147483647
複製代碼
    Spring會先執行order值比較小的。當訪問一個a.jpg圖片文件時,先通過DefaultAnnotationHandlerMapping來找處理器,一定是找不到的,我們沒有叫a.jpgAction。再按order值升序找,由於最後一個SimpleUrlHandlerMapping是匹配"/**"的,所以一定會匹配上,再響應圖片。
    最後再說明一下,如果你的DispatcherServlet攔截 *.do這樣的URL,就不存上述問題了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章