Spring MVC 解析之 DispatcherServlet

Spring MVC 是什麼

Spring Web MVC (Spring MVC) 是一套以 Servlet API 爲基礎平臺的優雅的 Web 框架,一直是 Spring Framework 中重要的一個組成部分。 正式名稱 「Spring Web MVC」 來自其源模塊 spring-webmvc 的名稱,但它一般被稱爲「Spring MVC」。css

與 Spring Web MVC 並行,Spring Framework 5.0 引入了一個 Reactive stack —— Web框架,其名稱 Spring WebFlux 也基於它的源模塊 spring-webflux。html

DispatcherServlet

與許多其餘 Web 框架同樣,Spring MVC 一樣圍繞前端頁面的控制器模式 (Controller) 進行設計,其中最爲核心的 Servlet —— DispatcherServlet 爲來自客戶端的請求處理提供通用的方法,而實際的工做交由可自定義配置的組件來執行。 這種模型使用方式很是靈活,能夠知足多樣化的項目需求。前端

和任何普通的 Servlet 同樣,DispatcherServlet 須要根據 Servlet 規範使用 Java 代碼配置或在 web.xml 文件中聲明請求和 Servlet 的映射關係。 DispatcherServlet 經過讀取 Spring 的配置來發現它在請求映射,視圖解析,異常處理等方面所依賴的組件。java

如下是註冊和初始化 DispatcherServlet 的 Java 代碼配置示例。 該類將被 Servlet 容器自動檢測到:web

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // 加載 Spring Web Application 的配置
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // 建立並註冊 DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
複製代碼

如下是在 web.xml 中註冊和初始化 DispatcherServlet 的方法:spring

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- Spring 上下文的配置 -->
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <!-- "/*" 表示將全部請求交由 DispatcherServlet 處理 -->
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>
複製代碼

1. 應用上下文的層次結構

DispatcherServlet 依賴於一個 WebApplicationContext(對普通 ApplicationContext 的功能擴展)來實現本身的配置。 WebApplicationContext 中包含了一個指向它所關聯的 ServletContext 和 Servlet 的連接。 它同時還綁定到 Servlet 上下文中,以便應用程序可使用 RequestContextUtils 中的靜態方法在 WebApplicationContext 進行查找,來判斷是否須要調用 DispatcherServlet 中的方法。瀏覽器

對於只有一個 WebApplicationContext 應用程序來講,這已經能夠知足使用了。 同時也可使用具備層次結構的上下文,其中有一個根上下文(或者叫基上下文) 被多個 DispatcherServlet(或其餘普通 Servlet)實例所共享,每一個實例都有屬於本身的子上下文配置。緩存

根上下文一般包含被多個 Servlet 實例共享的公共 bean,例如數據倉庫和業務。 這些 bean 被繼承下來使用,還能夠在特定的 Servlet 的子上下文中重寫(即從新聲明一個 bean 的配置),子上下文中擁有該 Servlet 所獨有的局部 bean 實例:安全

如下是使用 WebApplicationContext 層次結構的示例配置:cookie

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}
複製代碼

在 web.xml 中的配置

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- 根上下文的配置 -->
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- app1 專屬的的子上下文 -->
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>
複製代碼

2. 帶有特殊功能的 Bean

DispatcherServlet 依靠這些特殊的 bean 來處理請求並返回響應。 這些特殊的 bean 是指實現 WebFlux 框架協議的,一樣由 Spring 管理的對象。 這些對象都含有一套默認的配置,但也能夠自定義各類屬性,從而進行靈活擴展或功能改寫。

bean 類名 功能
HandlerMapping 將請求映射處處理程序以及用於預處理和後續處理的一系列攔截器。 這種映射有着一套標準,具體的功能因 HandlerMapping 實現而異。 HandlerMapping 的兩個最主要實現是 RequestMappingHandlerMapping 和 SimpleUrlHandlerMapping ,前者支持 @RequestMapping 註釋方法,它爲請求的處理進行 URI 映射的註冊。
HandlerAdapter 協助 DispatcherServlet 調用匹配請求映射的處理程序,且不須要關心如何調用處理程序以及處理程序的任何細節。 例如,調用帶註釋的控制器中的方法須要先對 @RequestMapping 等註釋進行解析。 HandlerAdapter 的主要功能是屏蔽 DispatcherServlet 的實現細節。
HandlerExceptionResolver 包含各類異常的解決方法,能夠將不一樣的異常映射到響應的處理程序或頁面等。
ViewResolver 將處理程序中的方法返回值(字符串)的邏輯視圖名稱解析爲實際視圖,來將響應返回給客戶端。
LocaleResolver, LocaleContextResolver 識別客戶端的當前區域設置以估測大概的時區,從而可以返回響應地區的國際化視圖。
ThemeResolver 解析當前 Web 應用程序可用的主題,例如提供個性化佈局。
MultipartResolver 在相應的解析庫的輔助下,對 multi-part 請求(好比瀏覽器的表單文件上傳)進行解析。
FlashMapManager 存儲和檢索可將參數從一個請求傳遞到另外一個請求的「輸入」和「輸出」的 FlashMap,一般經過重定向來實現。

3. Web MVC 的配置

應用程序能夠單獨聲明上文中「帶有特殊功能的 Bean」中列出的基礎版的 bean。 DispatcherServlet 掃描這些 bean 所屬的 WebApplicationContext。 若是沒有匹配的 bean 類型,它將返回 DispatcherServlet.properties 中的默認類型。

在大多數狀況下,MVC 默認配置是最好的實現。 它採用 Java 代碼或 XML 文件來配置所需的 bean,同時提供更高級別的配置回調 API 用於改寫默認配置。

4. Servlet 的配置

在 Servlet 3.0 以上的版本中,能夠用 Java代碼或與 web.xml 文件相結合來配置 Servlet 容器。 如下是用 Java 代碼註冊 DispatcherServlet 的示例:

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}
複製代碼

WebApplicationInitializer 是 Spring MVC 提供的一個接口,其全部實現類均可以被掃描到,並自動用於初始化任何 Servlet 3 容器。 AbstractDispatcherServletInitializer (WebApplicationInitializer 的一個抽象父類)的實現類能夠經過覆蓋方法來配置 Servlet 請求映射、DispatcherServlet 配置文件的目錄,這樣很簡單的就實現了 DispatcherServlet 的配置。

若是經過 Java 代碼來配置 Spring 的話,須要這樣作:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
複製代碼

若是用 xml 文件來配置 Spring,則只需定義一個 AbstractDispatcherServletInitializer 實現類便可:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
複製代碼

AbstractDispatcherServletInitializer 還能夠輕鬆添加 Filter 並自動映射到 DispatcherServlet 中:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}
複製代碼

不一樣的 Filter 都會以不一樣的名稱註冊,同時映射到 DispatcherServlet 中。

The isAsyncSupported protected method of AbstractDispatcherServletInitializer provides a single place to enable async support on the DispatcherServlet and all filters mapped to it. By default this flag is set to true.

AbstractDispatcherServletInitializer 中的 isAsyncSupported() 方法能夠設置各個 Filter 是否開啓異步支持。

若是還想更加細化自定義配置,能夠經過重寫 createDispatcherServlet() 方法來實現。

5. 處理請求

DispatcherServlet 處理請求的規則:

  • 在請求中查找並綁定 WebApplicationContext,它能夠做爲參數被控制器中的方法使用。 默認綁定到 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 對應的值。
  • 區域解析器 (LocaleResolver) 也綁定到請求上,它能夠在請求解析、呈現視圖、準備數據等過程當中將信息解析爲當前的區域環境。若是無需解析這些信息,能夠不用管它。
  • 主題解析器用來決定使用哪一個主題。 若是你不使用主題,能夠忽略掉它。
  • 若是在應用中聲明瞭 multipart file resolver,則會對請求進行 multipart 檢查;若是發現了 multiparts,請求會被包裝成 MultipartHttpServlet 來進行處理。
  • 若是返回模型,則會解析並返回視圖。 若是沒有返回模型(因爲其餘處理程序攔截了請求,可能出於安全緣由),則不會返回視圖,由於可能已經有響應返回給客戶端了。

WebApplicationContext 中聲明的 HandlerExceptionResolver bean 能夠解析請求處理時拋出的異常。 能夠給異常解析器進行特定的配置來解決特定的異常。

DispatcherServlet 還支持返回最後修改日期。 DispatcherServlet 掃描註冊的映射關係並,判斷找到的處理程序是否實現了 LastModified 接口。 若是實現了,則將 LastModified 接口的 long getLastModified(request)方法的返回值返回給客戶端。

在 web.xml 中,能夠經過配置 Servlet 的初始化參數(init-param)來自定義一個 DispatcherServlet 的實例。

DispatcherServlet 的初始化參數

參數 含義
contextClass WebApplicationContext 的實現類,初始化 Servlet 的上下文,默認爲 XmlWebApplicationContext
contextConfigLocation 做爲參數傳遞給在 contextClass 中指定上下文實例,用於標識上下文的位置,接受多個字符串(以逗號分隔),若是同一個 bean 的配置在多個上下文中出現,則以最後一個爲準。
namespace WebApplicationContext 的命名空間,默認爲 元素中的值加上「servlet」。好比,app,那麼,命名空間爲 appServlet。

6. 攔截器

全部 HandlerMapping 的實現類都支持使用攔截器,特別是將某些功能只應用到特定的請求上時(好比判斷權限),攔截器就很是有用。 攔截器必須實現 org.springframework.web.servlet 包中的 HandlerInterceptor,它提供了三個方法,供不一樣時刻來調用:

  • preHandle(..):在請求處理前執行...
  • postHandle(..):在請求處理後執行...
  • afterCompletion(..):在請求處理徹底結束後執行...

preHandle(..) 方法返回布爾值,能夠經過這一點選擇來切斷或繼續請求的處理鏈。當返回 true 的時候,處理器鏈會繼續執行;返回 false 的時候, DispatcherServlet 就會認爲攔截器已經處理了請求或返回了視圖,並不會繼續被處理鏈中的其餘處理器或攔截器所處理。

請注意,postHandle(..) 對使用 @ResponseBody 和 ResponseEntity 方法的用處不大,在 HandlerAdapter 中, postHandle(..) 調用以前就已經提交響應了。 因此這時再修改響應什麼用也沒有了。 這種狀況下,能夠實現 ResponseBodyAdvice,並將其聲明爲 Controller Advice bean (在 Controller 類上添加 @ControllerAdvice 註解)或直接在 RequestMappingHandlerAdapter 中進行配置。

7. 異常處理

在映射或調用請求處理程序 (例如帶有 @Controller 註解的控制器) 處理請求時,若是拋出了異常,則 DispatcherServlet 調用 HandlerExceptionResolver bean 來處理異常,而後將錯誤頁面或錯誤狀態碼等信息返回個客戶端。

下面列出 HandlerExceptionResolver 的幾個實現類:

HandlerExceptionResolver 實現類

類名 功能
SimpleMappingExceptionResolver 將異常類型映射到異常頁面的視圖名,能夠很容易實現錯誤頁面的返回
DefaultHandlerExceptionResolver 處理由 Spring MVC 拋出的異常,能夠直接映射到不一樣的 HTTP 狀態碼
ResponseStatusExceptionResolver 處理帶有 @ResponseStatus 註解的異常,並將其映射到 HTTP 狀態碼
ExceptionHandlerExceptionResolver 經過調用控制器(帶有 @Controller 註解或 @ControllerAdvice 註解)中的帶有 @ExceptionHandler 註解的方法,來處理異常

若是須要同時映射多個異常類型,須要設置不一樣異常的權重 (order 屬性),權重越高,處理時機越晚。

處理方式

HandlerExceptionResolver 中能夠返回:

  • ModelAndView: 指向視圖名
  • 不帶屬性的 ModelAndView: 若是已經經過權重更低的方法處理過異常了
  • null: 這個異常沒法被當前異常處理器識別,須要丟給接下來的處理器,若是全部處理器都不能處理這個異常,異常會傳到 Servlet 容器中,由容器來處理
  • 自定義異常處理請求也很是簡單,好比,在 xml 中配置一個 HandlerExceptionResolver 的 bean, Spring MVC 會自動使用內部的默認異常處理器來處理 Spring MVC 拋出的異常(帶有 @ResponseStatus 註解的異常或帶有 @ExceptionHandler 註解的方法),能夠修改這些默認配置或乾脆直接重寫新的配置。

Servlet 容器中定義的錯誤頁面 (error-page)

若是拋出的異常沒能被任何 HandlerExceptionResolver 處理,就會傳到 Servlet 容器中。若是將 HTTP 響應碼設置爲 4xx 或 5xx, Servlet 容器會直接返回默認的錯誤頁面。能夠在 web.xml 中配置自定義的錯誤頁面:

<error-page>
    <location>/error</location>
</error-page>
複製代碼

此時,若是想要對異常處理進一步自定義,能夠這樣作:

@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}
複製代碼

這樣就能再次將 /error 請求交給 DispatcherServlet 來處理,這種方式解決了使用 RestController 返回 JSON,而不是返回視圖的狀況。

8. 視圖名解析

Spring MVC 定義了 View 和 ViewResolver 這兩個接口,View 負責返回視圖前的數據準備, ViewResolver 則負責將邏輯視圖名映射到實際視圖。 屏蔽了具體的視圖實現細節。

ViewResolver 的幾個實現類

類名 功能
AbstractCachingViewResolver AbstractCachingViewResolver 的實現類會將解析過的視圖緩存,緩存能夠提高特定視圖技術的性能。能夠經過設置 cache 屬性的值爲 false 來將緩存功能關閉。若是想在運行時刷新視圖緩存,能夠調用 removeFromCache(String viewName, Locale loc) 方法將已緩存內容移除。
XmlViewResolver 可使用 Spring bean DTD 定義,在 xml 中對其配置。默認的配置文件爲 /WEB-INF/views.xml
ResourceBundleViewResolver 經過解析定義的 ResourceBundle bean,將 viewname 解析爲視圖名,url 解析爲視圖路徑
UrlBasedViewResolver 能夠直接將 url 映射到返回值中,無需再進行額外的映射配置,適用於視圖結構清晰,能夠直接對應匹配的狀況
InternalResourceViewResolver UrlBasedViewResolver 的子類,能夠解析 InternalResourceView (好比 Servlet 或 JSP) 以及其子類,好比 JstlView 或 TilesView。能夠經過 setViewClass(..) 方法來指定要解析的具體視圖類型
FreeMarkerViewResolver UrlBasedViewResolver 的子類,能夠解析 FreeMarkerView 以及自定義的子類
ContentNegotiatingViewResolver 經過請求的 url 或請求頭信息來解析視圖

處理方式

和上面提到的異常解析器同樣,經過配置 bean 來解析視圖,一樣能夠指定不一樣解析器的權重,權重越高,調用時機越晚。

ViewResolver 能夠經過返回 null 來表示視圖沒法解析,若是是 JSP 或 InternalResourceViewResolver,能夠判斷 JSP 是否存在的惟一方法就是經過執行 RequestDispatcher 的請求調度方法。 所以必須將 InternalResourceViewResolver 的權重配置爲全部視圖解析器中最高的。

若是返回視圖的執行過程不須要處理任何的業務邏輯,可使用視圖控制器來實現:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 將 url 爲 "/" 的請求映射到名爲 home 的視圖上
        registry.addViewController("/").setViewName("index");
    }
}
複製代碼

在 xml 中這樣寫:

<mvc:view-controller path="/" view-name="index"/>
複製代碼

重定向

若是返回的視圖名以 "redirect:" 開頭, UrlBasedViewResolver 就會將其解析爲請求重定向,後面的視圖名就是將重定向的 url。

這樣和返回 RedirectView 的效果是同樣的,他能夠將請求重定向到當前 Servlet 上下文的相對路徑,若是寫成這樣 redirect:http://myhost.com/some/arbitrary/path,就會重定向到這個絕對路徑上。

若是在控制器的方法上寫了 @ResponseStatus 註解,則註解中的狀態碼優先權高於 RedirectView 中的。

轉發

若是最終的視圖解析器爲 UrlBasedViewResolver, 則可使用 forward:,效果經過 forward() 方法建立一個 InternalResourceView 。這個前綴不適用於 InternalResourceViewResolver 和 InternalResourceView (JSP),但對於使用其餘的視圖技術且想執行請求轉發,就很是有用了。一樣,將多個視圖解析器組成處理鏈實現鏈式處理。

內容規約

ContentNegotiatingViewResolver 實際並不會解析視圖,而是調用其餘視圖解析器,查找與客戶端請求中有關信息匹配的視圖。這些信息能夠從請求頭中的 Accept 中或 url 中參數得到。

ContentNegotiatingViewResolver 將請求的媒體類型與 ViewResolvers 關聯的 View 支持的媒體類型(也稱爲 Content-Type)進行比較,選擇最佳的 View 來處理請求。 與這個 Content-Type 相符的第一個視圖會被首先返回給客戶端。 若是 ViewResolver 鏈沒法解析視圖,則會在 DefaultViews 的視圖列表進行查找。 後者適用於無需進行業務邏輯處理的單例 View ,所以不用考慮邏輯視圖名。 Accept 頭中能夠包含通配符,例如 text/ * ,則將匹配 content-type 爲 text/xml的 View 。

9. 語言環境

Spring MVC 和 Spring 中的大多數模塊同樣,也提供了內容國際化的功能。DispatcherServlet 能夠根據客戶端的語言環境進行內容的自動國際化,這要歸功於 LocaleResolver。

DispatcherServlet 接受到請求後會去上下文中查找 LocaleResolver bean,找到後就會使用它完成國際化的任務。能夠調用 RequestContext.getLocale() 來得到請求的語言環境。

若是不想使用默認的自動解析方式,還能夠經過在處理程序映射中添加一個攔截器來實現,好比解析 url 中的參數。

有關國際化的解析器和攔截器都定義在 org.springframework.web.servlet.i18n 中,而且都須要在應用上下文中配置。Spring 能夠國際化這些內容:

時區

除了語言環境,有時還須要得到客戶端的時區信息,LocaleContextResolver 對 LocaleResolver 中的功能進行了擴展,這樣能夠在 LocaleContext 中得到更爲豐富的信息。

經過 RequestContext.getTimeZone()方法獲取客戶端的時區信息,在 Spring 的 ConversionService 中註冊的日期/時間轉換器和格式化器會自動獲取這些信息。

請求頭

這個區域解析器會識別請求中的 accept-language 頭,這個字段通常包含客戶操做系統的區域信息,可是它沒法得到客戶端的時區信息。

Cookie

這個區域解析器會檢查客戶端上的 Cookie 是否存在語言或時區信息。這個區域解析器能夠設置 cookie 的名字和失效時間。在 xml 文件中定義 CookieLocaleResolver:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <!-- 根據具體狀況自定義 -->
    <property name="cookieName" value="clientlanguage"/>

    <!-- 100000 爲秒數,值爲 -1 時, cookie 會在瀏覽器關閉時被銷燬 -->
    <property name="cookieMaxAge" value="100000"/>

</bean>
複製代碼

CookieLocaleResolver 部分屬性的含義

屬性 默認值 含義
cookieName 類名+語言環境 cookie 的名稱
cookieMaxAge Servlet 容器中規定的值 cookie 的有效期,值爲 -1 時, cookie 會在瀏覽器關閉時被銷燬
cookiePath / 將 cookie 的做用域限制到制定的相對路徑下,cookie 將只在這個路徑及其自路徑中有效

Session

SessionLocaleResolver 能夠從會話上下文中獲取語言和時區信息,它和 CookieLocaleResolver 的區別是:它是在 Servlet 容器的 session 中存儲語言和時區信息,全部,這些信息都是暫時的,會話結束後也就不復存在了。

語言環境攔截器

LocaleChangeInterceptor bean 能夠經過自定義配置來修改請求中的參數,達到修改語言環境的目的。調用 LocaleResolver 中的 setLocal() 方法便可。下面的例子演示將全部請求中的 siteLanguage 參數修改成荷蘭語:

<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>
複製代碼

10. 主題

Spring Web MVC 框架支持自定義主題,應用程序的總體外觀能夠實現統一修改。主題即靜態資源的集合,一般是 CSS 和圖片,二者決定了應用的總體風格。

主題的定義

必須定義一個 org.springframework.ui.context.ThemeSource 接口的實現類才能使用主題功能。默認狀況下,WebApplicationContext 經過 org.springframework.ui.context.support.ResourceBundleThemeSource 實現主題功能,它從 classpath 的根目錄讀取配置文件。 要使用自定義的 ThemeSource,須要配置 ResourceBundleThemeSource 的主題名稱前綴,在應用程序上下文中註冊一個 themeSource bean。

使用 ResourceBundleThemeSource 自定義主題,須要將配置寫在 properties 文件中,這個文件中包含了構成主題的全部資源。

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
複製代碼

在 JSP 中使用主題設置,要用到 spring:theme 標籤:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
        ...
    </body>
</html>
複製代碼

默認狀況下,ResourceBundleThemeSource 主題名前綴是空的。properties 文件是從 classpath 的根目錄讀取的。因此應當將 abc.properties 配置文件放到根目錄下(/WEB-INF/classes)。 ResourceBundleThemeSource 能夠實現主題的國際化。好比,/WEB-INF/classes/abc_nl.properties,結合上面對 background 屬性的配置,能夠實現將背景切換爲帶有荷蘭文字的背景圖片。

主題的解析

定義好主題配置後,當有請求發來時,在預處理階段,經過查找上下文中叫 themeResolver 的 bean 來對決定用什麼解析器來解析主題,這裏的工做原理和上文中的 localeResolver 同樣。經過識別帶有不一樣參數的請求來對請求的主題進行切換。Spring 提供了這幾種 themeResolver:

類名 功能
FixedThemeResolver 經過讀取 defaultThemeName 屬性來選擇固定的主題
SessionThemeResolver 主題信息由 session 保存,每一個 session 只需解析一次,不一樣的 session 之間沒法共享
FixedThemeResolver 主題信息保存在客戶端的 cookie 中

Spring 還提供了一個 ThemeChangeInterceptor 攔截器,經過識別請求的參數來切換主題。

11. Multipart

org.springframework.web.multipart.MultipartResolver 提供了一種處理表單上傳文件的解決方案,有兩種實現方式,一種是基於 Apache 的 Commons-Fileupload,另外一種是基於 Servlet 3.0 的。

首先要在 Spring 的配置文件中爲 DispatcherServlet 聲明一個叫作 MultipartResolver 的 bean,DispatcherServlet 會自動識別並調用它來處理文件上傳請求。它會將 content-type 爲 multipart/form-data 的請求包裝成 MultipartHttpServletRequest,從而將這些 "part" 暴露爲請求的一個參數。

Apache FileUpload

要想使用 Apache Commons-Fileupload,須要將 multipartResolver bean 配置爲 CommonsMultipartResolver,另外不要忘了添加 commons-fileupload 的依賴。

Servlet 3.0

若是經過 Servlet 3.0 處理 multipart 請求,則一樣須要在 DispatcherServlet 中註冊。在 Java 代碼中配置的話,須要添加一個 MultipartConfigElement;在 xml 文件中配置的話,添加 <multipart-config> 節點。文件大小限制和文件保存位置等選項一樣須要這樣配置,由於在 Servlet 3.0 之後,不容許 MultipartResolver 這麼幹了。

Servlet 3.0 配置好後,只須要在 xml 文件中將 multipartResolver hean 配置爲 StandartMultipartResolver 便可。

相關文章
相關標籤/搜索