1. 程式人生 > >Spring MVC 教程,快速入門,深入分析

Spring MVC 教程,快速入門,深入分析

作者:趙磊

部落格:http://elf8848.iteye.com

目錄 

一、前言
二、spring mvc 核心類與介面
三、spring mvc 核心流程圖

四、spring mvc DispatcherServlet說明

五、spring mvc 父子上下文的說明

六、springMVC-mvc.xml 配置檔案片段講解 
七、spring mvc 如何訪問到靜態的檔案,如jpg,js,css

八、spring mvc 請求如何對映到具體的Action中的方法

九、 spring mvc 中的攔截器:
十、 spring mvc 如何使用攔截器

十一、 spring mvc 如何實現全域性的異常處理

十二、 spring mvc 如何把全域性異常記錄到日誌中

十三、 如何給spring3 MVC中的Action做JUnit單元測試

十四、 spring mvc 轉發與重定向 (帶引數重定向)

十五、 spring mvc 處理ajax請求

十六、 spring mvc 關於寫幾個配置檔案的說明 

十七、 spring mvc 如何取得Spring管理的bean

十八、 spring mvc 多檢視控制器

十九、 <mvc:annotation-driven /> 到底做了什麼工作 
二十、 本文中springMVC.xml配置檔案是核心,這裡給一個下載地址

一、前言:

為開發團隊選擇一款優秀的MVC框架是件難事兒,在眾多可行的方案中決擇需要很高的經驗和水平。你的一個決定會影響團隊未來的幾年。要考慮方面太多:

1、簡單易用,以提高開發效率。使小部分的精力在框架上,大部分的精力放在業務上。

2、效能優秀,這是一個最能吸引眼球的話題。

3、儘量使用大眾的框架(避免使用小眾的、私有的框架),新招聘來的開發人員有一些這方面技術積累,減低人員流動再適應的影響。

如果你還在為這件事件發愁,本文最適合你了。選擇Spring MVC吧。

Spring MVC是當前最優秀的MVC框架,自從Spring 2.5版本釋出後,由於支援註解配置,易用性有了大幅度的提高。Spring 3.0更加完善,實現了對Struts 2的超越。現在越來越多的開發團隊選擇了Spring MVC。

Struts2也是非常優秀的MVC構架,優點非常多比如良好的結構,攔截器的思想,豐富的功能。但這裡想說的是缺點,Struts2由於採用了值棧、OGNL表示式、struts2標籤庫等,會導致應用的效能下降,應避免使用這些功能。而Struts2的多層攔截器、多例項action效能都很好。可以參考我寫的一篇關於Spring MVC與Struts2與Servlet比較的文章《Struts2、SpringMVC、Servlet(Jsp)效能對比 測試》

Spring3 MVC的優點:

1、Spring3 MVC使用簡單,學習成本低。學習難度小於Struts2,Struts2用不上的多餘功能太多。呵呵,當然這不是決定因素。

2、Spring3 MVC很容易就可以寫出效能優秀的程式,Struts2要處處小心才可以寫出效能優秀的程式(指MVC部分)

3、Spring3 MVC的靈活是你無法想像的,Spring框架的擴充套件性有口皆碑,Spring3 MVC當然也不會落後,不會因使用了MVC框架而感到有任何的限制。

Struts2的眾多優點:

1、老牌的知名框架,從Struts1起積累了大量使用者群體。技術文件豐富。

2、其它方面略...   (呵呵,是不是不公平?)

二、核心類與介面:

先來了解一下,幾個重要的介面與類。現在不知道他們是幹什麼的沒關係,先混個臉熟,為以後認識他們打個基礎。

DispatcherServlet   -- 前置控制器

HandlerMapping介面 -- 處理請求的對映

HandlerMapping介面的實現類:

SimpleUrlHandlerMapping  通過配置檔案,把一個URL對映到Controller

DefaultAnnotationHandlerMapping  通過註解,把一個URL對映到Controller類上

HandlerAdapter介面 -- 處理請求的對映

AnnotationMethodHandlerAdapter類,通過註解,把一個URL對映到Controller類的方法上

Controller介面 -- 控制器

由於我們使用了@Controller註解,添加了@Controller註解註解的類就可以擔任控制器(Action)的職責,

所以我們並沒有用到這個介面。

HandlerInterceptor 介面--攔截器

無圖,我們自己實現這個介面,來完成攔截的器的工作。

ViewResolver介面的實現類

UrlBasedViewResolver類 通過配置檔案,把一個檢視名交給到一個View來處理

InternalResourceViewResolver類,比上面的類,加入了JSTL的支援

View介面

JstlView類

LocalResolver介面

HandlerExceptionResolver介面 --異常處理

SimpleMappingExceptionResolver實現類

ModelAndView類

無圖。

三、核心流程圖

本圖是我個人畫的,有不嚴謹的地方,大家對付看吧。總比沒的看強。


四、DispatcherServlet說明

使用Spring MVC,配置DispatcherServlet是第一步。

DispatcherServlet是一個Servlet,所以可以配置多個DispatcherServlet。

DispatcherServlet是前置控制器,配置在web.xml檔案中的。攔截匹配的請求,Servlet攔截匹配規則要自已定義,把攔截下來的請求,依據某某規則分發到目標Controller(我們寫的Action)來處理。

“某某規則”:是根據你使用了哪個HandlerMapping介面的實現類的不同而不同。

先來看第一個例子:

Xml程式碼  收藏程式碼
  1. <web-app>  
  2.     <servlet>  
  3.         <servlet-name>example</servlet-name>  
  4.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  5.         <load-on-startup>1</load-on-startup>  
  6.     </servlet>  
  7.     <servlet-mapping>  
  8.         <servlet-name>example</servlet-name>  
  9.         <url-pattern>*.form</url-pattern>  
  10.     </servlet-mapping>  
  11. </web-app>  

 <load-on-startup>1</load-on-startup>是啟動順序,讓這個Servlet隨Servletp容器一起啟動。

 <url-pattern>*.form</url-pattern> 會攔截*.form結尾的請求。

 <servlet-name>example</servlet-name>這個Servlet的名字是example,可以有多個DispatcherServlet,是通過名字來區分的。每一個DispatcherServlet有自己的WebApplicationContext上下文物件。同時儲存的ServletContext中和Request物件中,關於key,以後說明。

在DispatcherServlet的初始化過程中,框架會在web應用的 WEB-INF資料夾下尋找名為[servlet-name]-servlet.xml 的配置檔案,生成檔案中定義的bean。

第二個例子:

Xml程式碼  收藏程式碼
  1. <servlet>  
  2.     <servlet-name>springMVC</servlet-name>  
  3.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.     <init-param>  
  5.         <param-name>contextConfigLocation</param-name>  
  6.         <param-value>classpath*:/springMVC.xml</param-value>  
  7.     </init-param>  
  8.     <load-on-startup>1</load-on-startup>  
  9. </servlet>  
  10. <servlet-mapping>  
  11.     <servlet-name>springMVC</servlet-name>  
  12.     <url-pattern>/</url-pattern>  
  13. </servlet-mapping>  

指明瞭配置檔案的檔名,不使用預設配置檔名,而使用springMVC.xml配置檔案。

其中<param-value>**.xml</param-value> 這裡可以使用多種寫法
1、不寫,使用預設值:/WEB-INF/<servlet-name>-servlet.xml
2、<param-value>/WEB-INF/classes/springMVC.xml</param-value>
3、<param-value>classpath*:springMVC-mvc.xml</param-value>
4、多個值用逗號分隔


Servlet攔截匹配規則可以自已定義,攔截哪種URL合適? 
當對映為@RequestMapping("/user/add")時,為例:


1、攔截*.do、*.htm, 例如:/user/add.do

這是最傳統的方式,最簡單也最實用。不會導致靜態檔案(jpg,js,css)被攔截。

2、攔截/,例如:/user/add

可以實現現在很流行的REST風格。很多網際網路型別的應用很喜歡這種風格的URL。

弊端:會導致靜態檔案(jpg,js,css)被攔截後不能正常顯示。想實現REST風格,事情就是麻煩一些。後面有解決辦法還算簡單。

3、攔截/*,這是一個錯誤的方式,請求可以走到Action中,但轉到jsp時再次被攔截,不能訪問到jsp。

五、父子上下文(WebApplicationContext)

如果你使用了listener監聽器來載入配置,一般在Struts+Spring+Hibernate的專案中都是使用listener監聽器的。如下

Java程式碼  收藏程式碼
  1. <listener>   
  2.   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>   
  3. </listener>   

Spring會建立一個WebApplicationContext上下文,稱為父上下文(父容器) ,儲存在 ServletContext中,key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。

可以使用Spring提供的工具類取出上下文物件:WebApplicationContextUtils.getWebApplicationContext(ServletContext);

DispatcherServlet是一個Servlet,可以同時配置多個,每個 DispatcherServlet有一個自己的上下文物件(WebApplicationContext),稱為子上下文(子容器),子上下文可以訪問父上下文中的內容,但父上下文不能訪問子上下文中的內容。 它也儲存在 ServletContext中,key是"org.springframework.web.servlet.FrameworkServlet.CONTEXT"+Servlet名稱。當一個Request物件產生時,會把這個子上下文物件(WebApplicationContext)儲存在Request物件中,key是DispatcherServlet.class.getName() + ".CONTEXT"。

可以使用工具類取出上下文物件:RequestContextUtils.getWebApplicationContext(request);

說明 :Spring 並沒有限制我們,必須使用父子上下文。我們可以自己決定如何使用。

方案一,傳統型:

父上下文容器中儲存資料來源、服務層、DAO層、事務的Bean。

子上下文容器中儲存Mvc相關的Action的Bean.

事務控制在服務層。

由於父上下文容器不能訪問子上下文容器中內容,事務的Bean在父上下文容器中,無法訪問子上下文容器中內容,就無法對子上下文容器中Action進行AOP(事務)。

當然,做為“傳統型”方案,也沒有必要這要做。

方案二,激進型:

Java世界的“面向介面程式設計”的思想是正確的,但在增刪改查為主業務的系統裡,Dao層介面,Dao層實現類,Service層介面,Service層實現類,Action父類,Action。再加上眾多的O(vo\po\bo)和jsp頁面。寫一個小功能 7、8個類就寫出來了。 開發者說我就是想接點私活兒,和PHP,ASP搶搶飯碗,但我又是Java程式設計師。最好的結果是大專案能做好,小專案能做快。所以“激進型”方案就出現了-----沒有介面、沒有Service層、還可以沒有眾多的O(vo\po\bo)。那沒有Service層事務控制在哪一層?只好上升的Action層。

本文不想說這是不是正確的思想,我想說的是Spring不會限制你這樣做。

由於有了父子上下文,你將無法實現這一目標。解決方案是只使用子上下文容器,不要父上下文容器 。所以資料來源、服務層、DAO層、事務的Bean、Action的Bean都放在子上下文容器中。就可以實現了,事務(註解事務)就正常工作了。這樣才夠激進。

總結:不使用listener監聽器來載入spring的配置檔案,只使用DispatcherServlet來載入spring的配置,不要父子上下文,只使用一個DispatcherServlet,事情就簡單了,什麼麻煩事兒也沒有了。

Java--大專案能做好--按傳統方式做,規規矩矩的做,好擴充套件,好維護。

Java--小專案能做快--按激進方式做,一週時間就可以出一個版本,先上線接受市場(使用者)的反饋,再改進,再反饋,時間就是生命(成本)。

六、springMVC-mvc.xml 配置檔案片段講解 (未使用預設配置檔名)

Xml程式碼  收藏程式碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:tx="http://www.springframework.org/schema/tx"  
  6.     xmlns:context="http://www.springframework.org/schema/context"    
  7.     xmlns:mvc="http://www.springframework.org/schema/mvc"    
  8.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  9.     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
  10.     http://www.springframework.org/schema/tx   
  11.     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
  12.     http://www.springframework.org/schema/context  
  13.     http://www.springframework.org/schema/context/spring-context-3.0.xsd  
  14.     http://www.springframework.org/schema/mvc  
  15.     http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
  16.     <!-- 自動掃描的包名 -->  
  17.     <context:component-scan base-package="com.app,com.core,JUnit4" ></context:component-scan>  
  18.     <!-- 預設的註解對映的支援 -->  
  19.     <mvc:annotation-driven />  
  20.     <!-- 檢視解釋類 -->  
  21.     <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
  22.         <property name="prefix" value="/WEB-INF/jsp/"/>  
  23.         <property name="suffix" value=".jsp"/><!--可為空,方便實現自已的依據副檔名來選擇檢視解釋類的邏輯  -->  
  24.         <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />  
  25.     </bean>  
  26.     <!-- 攔截器 -->  
  27.     <mvc:interceptors>  
  28.         <bean class="com.core.mvc.MyInteceptor" />  
  29.     </mvc:interceptors>       
  30.     <!-- 對靜態資原始檔的訪問  方案一 (二選一) -->  
  31.     <mvc:default-servlet-handler/>  
  32.     <!-- 對靜態資原始檔的訪問  方案二 (二選一)-->  
  33.     <mvc:resources mapping="/images/**" location="/images/" cache-period="31556926"/>  
  34.     <mvc:resources mapping="/js/**" location="/js/" cache-period="31556926"/>  
  35.     <mvc:resources mapping="/css/**" location="/css/" cache-period="31556926"/>  
  36. </beans>   

<context:component-scan/> 掃描指定的包中的類上的註解,常用的註解有:

@Controller 宣告Action元件
@Service    宣告Service元件    @Service("myMovieLister") 
@Repository 宣告Dao元件
@Component   泛指元件, 當不好歸類時. 
@RequestMapping("/menu")  請求對映
@Resource  用於注入,( j2ee提供的 ) 預設按名稱裝配,@Resource(name="beanName") 
@Autowired 用於注入,(srping提供的) 預設按型別裝配 
@Transactional( rollbackFor={Exception.class}) 事務管理
@ResponseBody
@Scope("prototype")   設定bean的作用域

<mvc:annotation-driven /> 是一種簡寫形式,完全可以手動配置替代這種簡寫形式,簡寫形式可以讓初學都快速應用預設配置方案。<mvc:annotation-driven /> 會自動註冊DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter 兩個bean,是spring MVC為@Controllers分發請求所必須的。
並提供了:資料繫結支援,@NumberFormatannotation支援,@DateTimeFormat支援,@Valid支援,讀寫XML的支援(JAXB),讀寫JSON的支援(Jackson)。
後面,我們處理響應ajax請求時,就使用到了對json的支援。
後面,對action寫JUnit單元測試時,要從spring IOC容器中取DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter 兩個bean,來完成測試,取的時候要知道是<mvc:annotation-driven />這一句註冊的這兩個bean。

如何替換 <mvc:annotation-driven />?他到底做了什麼工作,請看,最後面的 十九節 <mvc:annotation-driven /> 到底做了什麼工作。

<mvc:interceptors/> 是一種簡寫形式。通過看前面的大圖,知道,我們可以配置多個HandlerMapping。<mvc:interceptors/>會為每一個HandlerMapping,注入一個攔截器。其實我們也可以手動配置為每個HandlerMapping注入一個攔截器。

<mvc:default-servlet-handler/> 使用預設的Servlet來響應靜態檔案。

<mvc:resources mapping="/images/**" location="/images/" cache-period="31556926"/> 匹配URL  /images/**  的URL被當做靜態資源,由Spring讀出到記憶體中再響應http。

 
七、如何訪問到靜態的檔案,如jpg,js,css?

如何你的DispatcherServlet攔截"*.do"這樣的有後綴的URL,就不存在訪問不到靜態資源的問題。

如果你的DispatcherServlet攔截"/",為了實現REST風格,攔截了所有的請求,那麼同時對*.js,*.jpg等靜態檔案的訪問也就被攔截了。

我們要解決這個問題。

目的:可以正常訪問靜態檔案,不可以找不到靜態檔案報404。

 
方案一:啟用Tomcat的defaultServlet來處理靜態檔案

Xml程式碼  收藏程式碼
  1. <servlet-mapping>   
  2.     <servlet-name>default</servlet-name>  
  3.     <url-pa