springMVC官方文檔知識點梳理-關鍵

1、異步請求處理的相關配置

Servlet容器配置html

對於那些使用web.xml配置文件的應用,請確保web.xml的版本更新到3.0:java

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance http://java.sun.com/xml/ns/javaee
                    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    ...

</web-app>

異步請求必須在web.xmlDispatcherServlet下的子元素<async-supported>true</async-supported>設置爲true。此外,全部可能參與異步請求處理的過濾器Filter都必須配置爲支持ASYNC類型的請求分派。在Spring框架中爲過濾器啓用支持ASYNC類型的請求分派應是安全的,由於這些過濾器通常都繼承了基類OncePerRequestFilter,後者在運行時會檢查該過濾器是否須要參與到異步分派的請求處理中。web

如下是一個例子,展現了web.xml的配置:spring

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
                http://java.sun.com/xml/ns/javaee
                http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        version="3.0">

        <filter>
            <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
            <filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
            <async-supported>true</async-supported>
        </filter>

        <filter-mapping>
            <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>ASYNC</dispatcher>
        </filter-mapping>

    </web-app>

若是應用使用的是Servlet 3規範基於Java編程的配置方式,好比經過WebApplicationInitializer,那麼你也須要設置"asyncSupported"標誌和ASYNC分派類型的支持,就像你在web.xml 中所配置的同樣。你能夠考慮直接繼承AbstractDispatcherServletInitializerAbstractAnnotationConfigDispatcherServletInitializer來簡化配置,它們都自動地爲你設置了這些配置項,並使得註冊Filter過濾器實例變得很是簡單。數據庫

 注意過濾器的地址。apache

2、使用HandlerInterceptor攔截請求

Spring的處理器映射機制包含了處理器攔截器。攔截器在你須要爲特定類型的請求應用一些功能時可能頗有用,好比,檢查用戶身份等。編程

處理器映射處理過程配置的攔截器,必須實現 org.springframework.web.servlet包下的 HandlerInterceptor接口。這個接口定義了三個方法: preHandle(..),它在處理器實際執行 以前 會被執行; postHandle(..),它在處理器執行 完畢 之後被執行; afterCompletion(..),它在 整個請求處理完成 以後被執行。這三個方法爲各類類型的前處理和後處理需求提供了足夠的靈活性。json

preHandle(..)方法返回一個boolean值。你能夠經過這個方法來決定是否繼續執行處理鏈中的部件。當方法返回 true時,處理器鏈會繼續執行;若方法返回 false, DispatcherServlet即認爲攔截器自身已經完成了對請求的處理(好比說,已經渲染了一個合適的視圖),那麼其他的攔截器以及執行鏈中的其餘處理器就不會再被執行了。後端

攔截器能夠經過interceptors屬性來配置,該選項在全部繼承了AbstractHandlerMapping的處理器映射類HandlerMapping都提供了配置的接口。以下面代碼樣例所示:瀏覽器

<beans>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime" value="9"/>
        <property name="closingTime" value="18"/>
    </bean>
<beans>
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }

    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour && hour < closingTime) {
            return true;
        }
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

在上面的例子中,全部被此處理器處理的請求都會被TimeBasedAccessInterceptor攔截器攔截。若是當前時間在工做時間之外,那麼用戶就會被重定向到一個HTML文件提示用戶,好比顯示「你只有在工做時間才能夠訪問本網站」之類的信息。

使用RequestMappingHandlerMapping時,實際的處理器是一個處理器方法HandlerMethod的實例,它標識了一個將被用於處理該請求的控制器方法。

如你所見,Spring的攔截器適配器HandlerInterceptorAdapter讓繼承HandlerInterceptor接口變得更簡單了。

上面的例子中,全部控制器方法處理的請求都會被配置的攔截器先攔截到。若是你想進一步縮小攔截的URL範圍,你能夠經過MVC命名空間或MVC Java編程的方式來配置,或者,聲明一個MappedInterceptor類型的bean實例來處理。

須要注意的是,HandlerInterceptor的後攔截postHandle方法不必定老是適用於註解了@ResponseBodyResponseEntity的方法。這些場景中,HttpMessageConverter會在攔截器的postHandle方法被調以前就把信息寫回響應中。這樣攔截器就沒法再改變響應了,好比要增長一個響應頭之類的。若是有這種需求,請讓你的應用實現ResponseBodyAdvice接口,並將其定義爲一個@ControllerAdvicebean或直接在RequestMappingHandlerMapping中配置。

3、視圖解析

全部web應用的MVC框架都提供了視圖相關的支持。Spring提供了一些視圖解析器,它們讓你可以在瀏覽器中渲染模型,並支持你自由選用適合的視圖技術而沒必要與框架綁定到一塊兒。Spring原生支持JSP視圖技術、Velocity模板技術和XSLT視圖等。

有兩個接口在Spring處理視圖相關事宜時相當重要,分別是視圖解析器接口ViewResolver和視圖接口自己View。視圖解析器ViewResolver負責處理視圖名與實際視圖之間的映射關係。視圖接口View負責準備請求,並將請求的渲染交給某種具體的視圖技術實現。

21.5.1 使用ViewResolver接口解析視圖

Spring MVC中全部控制器的處理器方法都必須返回一個邏輯視圖的名字,不管是顯式返回(好比返回一個StringView或者ModelAndView)仍是隱式返回(好比基於約定的返回)。Spring中的視圖由一個視圖名標識,並由視圖解析器來渲染。Spring有很是多內置的視圖解析器。下表列出了大部分,表後也給出了一些例子。

表21.3 視圖解析器

視圖解析器 描述
AbstractCachingViewResolver 一個抽象的視圖解析器類,提供了緩存視圖的功能。一般視圖在可以被使用以前須要通過準備。繼承這個基類的視圖解析器便可以得到緩存視圖的能力。
XmlViewResolver 視圖解析器接口ViewResolver的一個實現,該類接受一個XML格式的配置文件。該XML文件必須與Spring XML的bean工廠有相同的DTD。默認的配置文件名是/WEB-INF/views.xml
ResourceBundleViewResolver 視圖解析器接口ViewResolver的一個實現,採用bundle根路徑所指定的ResourceBundle中的bean定義做爲配置。通常bundle都定義在classpath路徑下的一個配置文件中。默認的配置文件名爲views.properties
UrlBasedViewResolver ViewResolver接口的一個簡單實現。它直接使用URL來解析到邏輯視圖名,除此以外不須要其餘任何顯式的映射聲明。若是你的邏輯視圖名與你真正的視圖資源名是直接對應的,那麼這種直接解析的方式就很方便,不須要你再指定額外的映射。
InternalResourceViewResolver UrlBasedViewResolver的一個好用的子類。它支持內部資源視圖(具體來講,Servlet和JSP)、以及諸如JstlViewTilesView等類的子類。You can specify the view class for all views generated by this resolver by using setViewClass(..)。更多的細節,請見UrlBasedViewResolver類的java文檔。
VelocityViewResolver / FreeMarkerViewResolver UrlBasedViewResolver下的實用子類,支持Velocity視圖VelocityView(Velocity模板)和FreeMarker視圖FreeMarkerView以及它們對應子類。
ContentNegotiatingViewResolver 視圖解析器接口ViewResolver的一個實現,它會根據所請求的文件名或請求的Accept頭來解析一個視圖。

咱們能夠舉個例子,假設這裏使用的是JSP視圖技術,那麼咱們可使用一個基於URL的視圖解析器UrlBasedViewResolver。這個視圖解析器會將URL解析成一個視圖名,並將請求轉交給請求分發器來進行視圖渲染。

<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

若返回一個test邏輯視圖名,那麼該視圖解析器會將請求轉發到RequestDispatcher,後者會將請求交給/WEB-INF/jsp/test.jsp視圖去渲染。

若是須要在應用中使用多種不一樣的視圖技術,你可使用ResourceBundleViewResolver

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver會檢索由bundle根路徑下所配置的ResourceBundle,對於每一個視圖而言,其視圖類由[viewname].(class)屬性的值指定,其視圖url由[viewname].url屬性的值指定。下一節將詳細講解視圖技術,你能夠在那裏找到更多例子。你還能夠看到,視圖還容許有基視圖,即properties文件中全部視圖都「繼承」的一個文件。經過繼承技術,你能夠爲衆多視圖指定一個默認的視圖基類。

AbstractCachingViewResolver的子類可以緩存已經解析過的視圖實例。關閉緩存特性也是能夠的,只須要將cache屬性設置爲false便可。另外,若是實在須要在運行時刷新某個視圖(好比修改了Velocity模板時),你可使用removeFromCache(String viewName, Locale loc)方法。`

爲啥沒有HTML的頁面視圖。由於其它都是包含HTML元素的,並無純粹的HTML頁面,都須要注入視圖相關的東西。這個連接http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/view.html詳細介紹了各類視圖,例如jsp、xml、freemark等。那怎麼解決這個問題呢?先後端分離,就能夠解決。

3.2視圖鏈

Spring支持同時使用多個視圖解析器。所以,你能夠配置一個解析器鏈,並作更多的事好比,在特定條件下覆寫一個視圖等。你能夠經過把多個視圖解析器設置到應用上下文(application context)中的方式來串聯它們。若是須要指定它們的次序,那麼設置order屬性便可。請記住,order屬性的值越大,該視圖解析器在鏈中的位置就越靠後。

在下面的代碼例子中,視圖解析器鏈中包含了兩個解析器:一個是InternalResourceViewResolver,它老是自動被放置在解析器鏈的最後;另外一個是XmlViewResolver,它用來指定Excel視圖。InternalResourceViewResolver不支持Excel視圖。

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

<!-- in views.xml -->

<beans>
    <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

若是一個視圖解析器不能返回一個視圖,那麼Spring會繼續檢查上下文中其餘的視圖解析器。此時若是存在其餘的解析器,Spring會繼續調用它們,直到產生一個視圖返回爲止。若是最後全部視圖解析器都不能返回一個視圖,Spring就拋出一個ServletException

視圖解析器的接口清楚聲明瞭,一個視圖解析器是能夠返回null值的,這表示不能找到任何合適的視圖。並不是全部的視圖解析器都這麼作,可是也存在不得不如此的場景,即解析器確實沒法檢測對應的視圖是否存在。好比,InternalResourceViewResolver在內部使用了RequestDispatcher,而且進入分派過程是檢測一個JSP視圖是否存在的惟一方法,但這個過程僅可能發生惟一一次。一樣的VelocityViewResolver和部分其餘的視圖解析器也存在這樣的狀況。具體的請查閱某個特定的視圖解析器的Java文檔,看它是否會report不存在的視圖。所以,若是不把InternalResourceViewResolver放置在解析器鏈的最後,將可能致使解析器鏈沒法徹底執行,由於InternalResourceViewResolver永遠都會 返回一個視圖。

重定向前綴——redirect:

儘管使用RedirectView來作重定向能工做得很好,但若是控制器自身仍是須要建立一個RedirectView,那無疑控制器仍是瞭解重定向這麼一件事情的發生。這仍是有點不盡完美,不一樣範疇的耦合仍是太強。控制器其實不該該去關心響應會如何被渲染。In general it should operate only in terms of view names that have been injected into it.

一個特別的視圖名前綴能完成這個解耦:redirect:。若是返回的視圖名中含有redirect:前綴,那麼UrlBasedViewResolver(及它的全部子類)就會接受到這個信號,意識到這裏須要發生重定向。而後視圖名剩下的部分會被解析成重定向URL。

這種方式與經過控制器返回一個重定向視圖RedirectView所達到的效果是同樣的,不過這樣一來控制器就能夠只專一於處理並返回邏輯視圖名了。若是邏輯視圖名是這樣的形式:redirect:/myapp/some/resource,他們重定向路徑將以Servlet上下文做爲相對路徑進行查找,而邏輯視圖名若是是這樣的形式:redirect:http://myhost.com/some/arbitrary/path,那麼重定向URL使用的就是絕對路徑。

注意的是,若是控制器方法註解了@ResponseStatus,那麼註解設置的狀態碼值會覆蓋RedirectView設置的響應狀態碼值。

重定向前綴——forward:

對於最終會被UrlBasedViewResolver或其子類解析的視圖名,你可使用一個特殊的前綴:forward:。這會致使一個InternalResourceView視圖對象的建立(它最終會調用RequestDispatcher.forward()方法),後者會認爲視圖名剩下的部分是一個URL。所以,這個前綴在使用InternalResourceViewResolverInternalResourceView時並無特別的做用(好比對於JSP來講)。但當你主要使用的是其餘的視圖技術,而又想要強制把一個資源轉發給Servlet/JSP引擎進行處理時,這個前綴可能就頗有用(或者,你也可能同時串聯多個視圖解析器)。

redirect:前綴同樣,若是控制器中的視圖名使用了forward:前綴,控制器自己並不會發覺任何異常,它關注的仍然只是如何處理響應的問題。

@RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

Spring內置對多路上傳的支持,專門用於處理web應用中的文件上傳。你能夠經過註冊一個可插拔的MultipartResolver對象來啓用對文件多路上傳的支持。該接口在定義於org.springframework.web.multipart包下。Spring爲通常的文件上傳提供了MultipartResolver接口的一個實現,爲Servlet 3.0多路請求的轉換提供了另外一個實現。

默認狀況下,Spring的多路上傳支持是不開啓的,由於有些開發者但願由本身來處理多路請求。若是想啓用Spring的多路上傳支持,你須要在web應用的上下文中添加一個多路傳輸解析器。每一個進來的請求,解析器都會檢查是否是一個多部分請求。若發現請求是完整的,則請求按正常流程被處理;若是發現請求是一個多路請求,則你在上下文中註冊的MultipartResolver解析器會被用來處理該請求。以後,請求中的多路上傳屬性就與其餘屬性同樣被正常對待了。【最後一句翻的很差,multipart翻譯成多路仍是多部分還在斟酌中。望閱讀者注意此處。】

21.10.2 使用MultipartResolver與Commons FileUpload傳輸文件

下面的代碼展現瞭如何使用一個通用的多路上傳解析器CommonsMultipartResolver

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- 支持的其中一個屬性,支持的最大文件大小,以字節爲單位 -->
    <property name="maxUploadSize" value="100000"/>

</bean>

固然,要讓多路解析器正常工做,你須要在classpath路徑下準備必須的jar包。若是使用的是通用的多路上傳解析器CommonsMultipartResolver,你所須要的jar包是commons-fileupload.jar

當Spring的DispatcherServlet檢測到一個多部分請求時,它會激活你在上下文中聲明的多路解析器並把請求交給它。解析器會把當前的HttpServletRequest請求對象包裝成一個支持多路文件上傳的請求對象MultipartHttpServletRequest。有了MultipartHttpServletRequest對象,你不只能夠獲取該多路請求中的信息,還能夠在你的控制器中得到該多路請求的內容自己。

4、文件上傳 Servlet 3.0下的MultipartResolver

要使用基於Servlet 3.0的多路傳輸轉換功能,你必須在web.xml中爲DispatcherServlet添加一個multipart-config元素,或者經過Servlet編程的方法使用javax.servlet.MultipartConfigElement進行註冊,或你本身定製了本身的Servlet類,那你必須使用javax.servlet.annotation.MultipartConfig對其進行註解。其餘諸如最大文件大小或存儲位置等配置選項都必須在這個Servlet級別進行註冊,由於Servlet 3.0不容許在解析器MultipartResolver的層級配置這些信息。

當你經過以上任一種方式啓用了Servlet 3.0多路傳輸轉換功能,你就能夠把一個StandardServletMultipartResolver解析器添加到你的Spring配置中去了:

<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

21.10.4 處理表單中的文件上傳

當解析器MultipartResolver完成處理時,請求便會像其餘請求同樣被正常流程處理。首先,建立一個接受文件上傳的表單將容許用於直接上傳整個表單。編碼屬性(enctype="multipart/form-data")能讓瀏覽器知道如何對多路上傳請求的表單進行編碼(encode)。

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

下一步是建立一個能處理文件上傳的控制器。這裏須要的控制器與通常註解了@Controller的控制器基本同樣,除了它接受的方法參數類型是MultipartHttpServletRequest,或MultipartFile

@Controller
public class FileUploadController {

    @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";
    }

}

請留意@RequestParam註解是如何將方法參數對應到表單中的定義的輸入字段的。在上面的例子中,咱們拿到了byte[]文件數據,只是沒對它作任何事。在實際應用中,你可能會將它保存到數據庫、存儲在文件系統上,或作其餘的處理。特別注意這個,有的時候缺失這個,可能致使file爲null。

當使用Servlet 3.0的多路傳輸轉換時,你也可使用javax.servlet.http.Part做爲方法參數:

@Controller
public class FileUploadController {

    @RequestMapping(path = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

21.10.5 處理客戶端發起的文件上傳請求

在使用了RESTful服務的場景下,非瀏覽器的客戶端也能夠直接提交多路文件請求。上一節講述的全部例子與配置在這裏也都一樣適用。但與瀏覽器不一樣的是,提交的文件和簡單的表單字段,客戶端發送的數據能夠更加複雜,數據能夠指定爲某種特定的內容類型(content type)——好比,一個多路上傳請求可能第一部分是個文件,而第二部分是個JSON格式的數據:

POST /someUrl
    Content-Type: multipart/mixed

    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
    Content-Disposition: form-data; name="meta-data"
    Content-Type: application/json; charset=UTF-8
    Content-Transfer-Encoding: 8bit

    {
        "name": "value"
    }
    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
    Content-Disposition: form-data; name="file-data"; filename="file.properties"
    Content-Type: text/xml
    Content-Transfer-Encoding: 8bit
    ... File Data ...

對於名稱爲meta-data的部分,你能夠經過控制器方法上的@RequestParam("meta-data") String metadata參數來得到。但對於那部分請求體中爲JSON格式數據的請求,你可能更想經過接受一個對應的強類型對象,就像@RequestBody經過HttpMessageConverter將通常請求的請求體轉換成一個對象同樣。

這是可能的,你可使用@RequestPart註解來實現,而非@RequestParam。該註解將使得特定多路請求的請求體被傳給HttpMessageConverter,而且在轉換時考慮多路請求中不一樣的內容類型參數'Content-Type'

@RequestMapping(path = "/someUrl", method = RequestMethod.POST)
public String onSubmit(@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file) {

    // ...

}

請注意MultipartFile方法參數是如何可以在@RequestParam@RequestPart註解下互用的,兩種方法都能拿到數據。但,這裏的方法參數@RequestPart("meta-data") MetaData則會由於請求中的內容類型請求頭'Content-Type'被讀入成爲JSON數據,而後再經過MappingJackson2HttpMessageConverter被轉換成特定的對象。

5、異常

異常 HTTP狀態碼
BindException 400 (無效請求)
ConversionNotSupportedException 500 (服務器內部錯誤)
HttpMediaTypeNotAcceptableException 406 (不接受)
HttpMediaTypeNotSupportedException 415 (不支持的媒體類型)
HttpMessageNotReadableException 400 (無效請求)
HttpMessageNotWritableException 500 (服務器內部錯誤)
HttpRequestMethodNotSupportedException 405 (不支持的方法)
MethodArgumentNotValidException 400 (無效請求)
MissingServletRequestParameterException 400 (無效請求)
MissingServletRequestPartException 400 (無效請求)
NoHandlerFoundException 404 (請求未找到)
NoSuchRequestHandlingMethodException 404 (請求未找到)
TypeMismatchException 400 (無效請求)
MissingPathVariableException 500 (服務器內部錯誤)
NoHandlerFoundException 404 (請求未找到)

總結:

圖21.1 Spring Web MVC處理請求的(高層抽象)工做流

Spring的視圖解析也是設計得異常靈活。控制器通常負責準備一個Map模型、填充數據、返回一個合適的視圖名等,同時它也能夠直接將數據寫到響應流中。視圖名的解析高度靈活,支持多種配置,包括經過文件擴展名、Accept內容頭、bean、配置文件等的配置,甚至你還能夠本身實現一個視圖解析器ViewResolver模型(MVC中的M,model)實際上是一個Map類型的接口,完全地把數據從視圖技術中抽象分離了出來。你能夠與基於模板的渲染技術直接整合,如JSP、Velocity和Freemarker等,或者你還能夠直接生成XML、JSON、Atom以及其餘多種類型的內容。Map模型會簡單地被轉換成合適的格式,好比JSP的請求屬性(attribute),一個Velocity模板的模型等。

相關文章
相關標籤/搜索