Spring 4 官方文檔學習(十一)Web MVC 框架

  1. 介紹Spring Web MVC 框架
    1. Spring Web MVC的特性
    2. 其餘MVC實現的可插拔性
  2. DispatcherServlet
    1. 在WebApplicationContext中的特殊的bean types
    2. 默認的DispatcherServlet配置
    3. DispatcherServlet處理順序
  3. 實現Controller
    1. 使用@Controller定義一個Controller
    2. 使用@RequestMapping映射requests
    3. 定義@RequestMapping handler methods
    4. 異步請求的處理
    5. 測試controllers
  4. Handler mappings
    1. 使用HandlerInterceptor攔截requests
  5. resolving views 解析視圖
  6. Spring 4 官方文檔學習(十一)Web MVC 框架之Flash Attributes
  7. Spring 4 官方文檔學習(十一)Web MVC 框架之URI Builder
  8. Spring 4 官方文檔學習(十一)Web MVC 框架之locales
  9. Spring 4 官方文檔學習(十一)Web MVC 框架之themes
  10. Spring 4 官方文檔學習(十一)Web MVC 框架之multipart(文件上傳)支持
  11. Spring 4 官方文檔學習(十一)Web MVC 框架之異常處理
  12. Spring 4 官方文檔學習(十一)Web MVC 框架之約定優於配置
  13. Spring 4 官方文檔學習(十一)Web MVC 框架之HTTP caching support
  14. Spring 4 官方文檔學習(十一)Web MVC 框架之編碼式Servlet容器初始化
  15. Spring 4 官方文檔學習(十一)Web MVC 框架之配置Spring MVC

 

一、介紹Spring Web MVC 框架html

Spring Web MVC 框架是圍繞DispatcherServlet設計的,所謂DispatcherServlet就是將請求分發到handler,須要有配置好的handler映射、視圖解析、本地化、時區、theme解決方案、還有上傳文件的支持。默認的handler是基於@Controller和@RequestMapping註解。自Spring 3.0 起,@Controller註解還能用於RESTful,須要配合@PathVariable以及其餘特性。java

Spring Web MVC 和 Spring的設計準則是「對擴展開放,對修改關閉」--能夠擴展,不能修改。git

Spring Web MVC中核心類的一些方法被標記爲final。開發者不能覆蓋這些方法以提供本身的行爲。這不是任性,而是聽從設計準則。github

在Spring MVC中,你不能爲final方法添加advice。web

在Spring Web MVC中,你可使用任何對象做爲命令對象或者form-backing對象;你不須要實現框架的特定接口或者基類。Spring的數據綁定是高度彈性的:例如,它將類型錯誤匹配視爲校驗錯誤,而非系統錯誤,從而可被應用evaluate。正則表達式

Spring的view resolution是很是彈性的。Controller負責準備一個model Map,其中帶有數據,還負責挑選一個view name -- 也能夠直接寫到響應流而完成一個請求。view name resolution是高度可定製的,使用文件擴展名或者Accept header content type negotiation,經過bean names、properties文件、或者,甚至一個自定義的ViewResolver實現。model(MVC中的M)是一個Map接口,容許view技術的徹底抽象。你能夠直接集成基於模板的rendering技術,例如JSP、Velocity和Freemarker、或者直接生成XML、JSON、Atom、以及不少其餘內容類型。model Map會被簡單的變成合適的格式,如JSP請求屬性、如Velocity模板模型。spring

 

1.一、Spring Web MVC的特性數據庫

Spring Web Flow編程

目標是成爲管理web應用頁面流程的最佳解決方案。json

SWF集成了現有的框架,如Spring MVC和JSF,在Servlet和Portlet環境中。若是你有一個業務process(或多個)想從conversational model中受益(與純request model相比),那麼SWF就是這種解決方案。

SWF容許你捕獲logical page flows,將其做爲self-contained modules,能夠在不一樣環境下複用,所以,這是構建web應用模塊的理想方式,可以引導用戶-- 使用驅動業務processes的controlled navigations。

 

Spring的web模塊包含許多獨特的web支持特性:

  • 角色徹底隔離。 每一個角色-- controller、validator、command object、form object、model object、DispatcherServlet、handler mapping、view resolver、等等 -- 都能使用特殊的對象來實現。
  • 框架和應用classes的強大且條理清晰的配置。 這個配置能力包括不一樣contexts中的簡單引用,例如從web controllers到業務對象和validators的引用。
  • 適用能力、非侵入式、彈性。 能夠定義任何須要的controller 方法簽名,針對特定的情景 可能須要使用某個參數註解(如@RequestParam、@RequestHeader、@PathVariable等等)。
  • 可複用的業務代碼,不須要重複。 將現有業務對象做爲命令對象或者form對象,而非將它們鏡像到一個擴展類。
  • 可定製的綁定和校驗。Type mismatches as application-level validation errors that keep the offending value, localized date and number binding, and so on instead of String-only form objects with manual parsing and conversion to business objects.
  • 可定製的handler mapping和view resolution。 handler mapping 和 view resolution策略:從簡單的基於URL的配置,到複雜的構建目的的解決方案策略。相比只使用一種技術的web MVC框架來講,Spring更彈性
  • 彈性的模型傳輸。 使用name/value Map的模型傳輸支持與任意view技術的簡單集成。
  • 可定製的locale、time zone和theme resolution,支持JSP(使用/不使用Spring標籤),支持JSTL,支持Velocity--不須要額外的橋樑,等等。
  • 簡單且強大的JSP標籤庫--Spring標籤庫,支持的特性包括數據綁定和themes。
  • JSP form標籤庫,Spring 2.0引入的, 使得在JSP頁面中寫forms更簡單。
  • lifecycle被限定在當前HTTP請求或HTTP Session的beans。這不是Spring MVC獨有的技術,而是Spring MVC使用的WebApplicationContext的特性。

 

1.二、其餘MVC實現的可插拔性

對於某些項目來講,非Spring的MVC實現更適合。不少團隊但願借用已有的投資(囧,真抽象),例如,使用JSF。

若是你不想使用Spring Web MVC,而想使用Spring提供的其餘解決方案,你能夠將 你選擇的web MVC框架  集成到Spring中,這很簡單。經過Spring的ContextLoaderListener啓動Spring的root application context,能夠在任意action object中經過它的ServletContext屬性來獲取它 -- 也可使用Spring的相應幫助方法來獲取。

你註冊的beans和Spring的服務隨時可用 -- 哪怕沒有Spring MVC。這種情景下,Spring不會同其餘web框架競爭。它只是簡單的致力於純web MVC框架沒有關注的地方,從bean配置到數據訪問和事務處理。因此,哪怕你只是想配合JDBC或Hibernate來使用Spring的事務抽象,仍然能夠將Spring中間件 和/或 數據訪問中間件做爲你應用的一部分。

 

二、DispatcherServlet

像不少其餘web MVC框架同樣,Spring MVC框架也是請求驅動的,圍繞一箇中心Servlet來設計,該中心Servlet能夠分發請求到Controllers,並提供其餘功能。然而Spring的DispatcherServlet作的更多。它被完全地與Spring IoC容器集成到了一塊兒,因此,你可使用Spring的全部特性。

 

Spring MVC DispatcherServlet處理請求的工做流程以下圖所示。聰明的讀者會認出DispatcherServlet是Front Controller設計模式的一種實現

mvc

DispatcherServlet是一個具體的Servlet(繼承自HttpServlet基類),因此你須要使用一個URL mapping來映射請求 -- 就是你想讓DispatcherServlet處理的請求。這是一個標準Java EE Servlet配置,在Servlet 3.0+ 環境下能夠這樣註冊該Servlet:

public class MyWebApplicationInitializer implements WebApplicationInitializer { // 這個接口,或者其抽象實現類

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = container.addServlet("example", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }

}

上面的例子中,全部以 /example開頭的請求 都會由名字爲example的DispatcherServlet實例來處理。

 

WebApplicationInitializer是Spring MVC提供的接口,能夠確保基於代碼的配置被探測到、且被自動用於初始化Servlet 3 容器。該接口的一個abstract基類是AbstractAnnotationConfigDispatcherServletInitializer,該abstract基類註冊DispatcherServlet更簡便,只須要指定映射、列出配置類便可 -- 這是設置Spring MVC項目的一種推薦的方式(java config)

或者,傳統模式,web.xml中的設置方式:

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

</web-app>

 

在前面曾講過,在Spring中,ApplicationContext實例能夠被scoped (就是有scope)。

而在Spring MVC框架中,每一個DispatcherServlet都有其自有的WebApplicationContext,它會繼承在root WebApplicationContext中定義的beans。 root WebApplicationContext應該包含全部基礎beans,以被其餘contexts 和 Servlet實例分享。被繼承的beans能夠被特定Servlet scope重寫,你能夠定義針對給定Servlet實例(其scope)的beans。

mvc context hierarchy

在DispatcherServlet初始化過程當中,Spring MVC會在web應用的/WEB-INF文件夾下查找名字爲 [servlet-name]-servlet.xml 的文件,建立其中定義的bean,並會重寫在全局scope中已經定義的任何beans的定義

 

看一下下面的DispatcherServlet配置:

<web-app>
    <servlet>
        <servlet-name>golfing</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>golfing</servlet-name>
        <url-pattern>/golfing/*</url-pattern>
    </servlet-mapping>
</web-app>

根據上面的Servlet配置,還須要一個文件:/WEB-INF/golfing-servlet.xml。該文件會包含全部的Spring MVC特定的組件(beans)。固然,你能夠更改該路徑,經過特定的Servlet初始化參數(詳見下面)。

對於DispatcherServlet情景來講,也能夠只有一個root context

mvc root context

這能夠經過設置一個空的contextConfigLocation servlet init 參數來配置,以下:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</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>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

WebApplicationContext是簡單的ApplicationContext的一個擴展,針對web應用擁有一些額外的特性。

它不一樣於normal ApplicationContext的地方是它可以resolve themes,它還知道關聯哪一個Servlet(經過到ServletContext的鏈接)。

WebApplicationContext被束縛在ServletContext中,經過使用RequestContextUtils類的靜態方法,你能夠隨時查找WebApplicationContext。

 

注意,咱們可使用基於Java的配置達到一樣的目的:

public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // 

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // GolfingAppConfig defines beans that would be in root-context.xml
        return new Class[] { GolfingAppConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // GolfingWebConfig defines beans that would be in golfing-servlet.xml
        return new Class[] { GolfingWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/golfing/*" };
    }

}

 

2.一、在WebApplicationContext中的特殊的bean types

Spring DispatcherServlet使用特殊的beans來處理請求並渲染視圖。這些beans是Spring MVC的一部分。你能夠選擇使用哪一個 -- 只須要在WebApplicationContext中簡單的配置一些便可。

Spring MVC維護了一個默認beans列表以供使用,下一部分會講。

如今先來看看DispatcherServlet依賴的特殊的bean types

bean type 解釋
HandlerMapping 基於criteria(不一樣的HandlerMapping實現有不一樣的criteria)將incoming requests映射到handlers以及一個pre和post-processors(handler interceptors)。最流行的實現支持註解controllers。
HandlerAdapter 幫助DispatcherServlet調用映射到一個request的handler,不管handler是否實際被調用了。例如,調用一個註解Controller須要處理不一樣的註解。因此,HandlerAdapter的主要目的是讓DispatcherServlet遠離這些細節。
HandlerExceptionResolver 將exceptions映射到views,也容許更復雜的exception處理代碼。
ViewResolver 處理基於字符串的邏輯視圖的名字,將其轉成實際的View 類型。
LocaleResolver & LocaleContextResolver Resolves the locale a client is using and possibly their time zone, in order to be able to offer internationalized views
ThemeResolver Resolves themes your web application can use, for example, to offer personalized layouts
MultipartResolver 解析multi-part請求,例如,支持處理來自HTML forms的文件上傳。
FlashMapManager 存儲和獲取input 和 output的FlashMap -- 可用於從一個request將attributes傳遞至另外一個,一般跨域一個redirect。

 

2.二、默認的DispatcherServlet配置

上面有提到,對每一個特殊的bean,DispatcherServlet默認都維護了一個實現列表以供使用。這個信息保存在 DispatcherServlet.properties 中,在org.springframework.web.servlet包下。

 

全部的特殊的beans都有其合理的默認(bean仍是設置?)。或早或晚,你總會須要定製這些beans提供的一個或多個properties。例如,配置InternalResourceViewResolver的prefix property 。

這裏最須要理解的概念就是:一旦你在你的WebApplicationContext中配置了一個特殊的bean,如InternalResourceViewResolver,你就徹底重寫了某個類型的默認的實現列表。例如,若是你配置了InternalResourceViewResolver,那默認的ViewResolver實現列表會被忽略!

 

2.三、DispatcherServlet處理順序

當你set up了一個DispatcherServlet,而後一個由其負責的request進來了,這時該DispatcherServlet會以以下流程開始處理請求:

  1. 搜索WebApplicationContext,並將其綁定到該request,做爲其attribute,controller和其餘元素在該process中可能用到。默認鍵是 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
  2. 將locale resolver綁定到request,以讓process中的元素在processing the request時(如rendering the view、preparing data 等等)可以resolve 須要使用的locale。若是你不須要locale resolving,你不須要這個。
  3. 將theme resolver綁定到request,讓元素(如views)決定使用哪一個theme。若是你不使用themes,能夠忽略它。
  4. 若是你指定了一個multipart file resolver,該request會被檢查有無multiparts;若是有,該請求會被封裝成MultipartHttpServletRequest以更進一步的處理。
  5. 查找恰當合適的handler。若是找到了,關聯到該handler的執行鏈(preprocessors、postprocessors、以及controllers)會被執行,以準備一個model或者rendering。
  6. 若是返回了model,view會被rendered。若是沒有返回model,(可能因爲攔截等緣由),沒有view會被渲染,由於request可能已經被完成了!

 

在WebApplicationContext中聲明的handler exception resolvers會pick up 處理request過程當中拋出的exceptions。使用這些exception resolvers容許你定義本身的行爲來處理exceptions。

 

SpringDispatcherServlet也支持返回 last-modification-date(最後修改日期),如同Servlet API中指定的同樣。決定特定request的last modification date的處理是簡單粗暴的:DispatcherServlet會查找合適的handler mapping,並測試找到的handler是否實現了LastModified接口。若是實現了,long getLastModified(request)方法的值會被返回。

 

你能夠定製本身的DispatcherServlet實例,只要在web.xml的Servlet聲明中添加Servlet初始化參數(init-param elements)便可。下面列出了支持的參數。

DispatcherServlet 初始化參數

參數 解釋
contextClass 實現了WebApplicationContext的類,實例化了該Servlet使用的context。默認,使用XmlWebApplicationContext。
contextConfigLocation 傳遞給context實例(由contextClass指定)的字符串,用於指出在什麼地方找到context config。若有多個,以逗號間隔。若是多個,且beans有重複定義,以最後一個爲準。
namespace WebApplicationContext的命名空間。默認是[servlet-name]-servlet。

3.、實現Controller

Controller將用戶的input解釋並轉成一個model,該model能夠經過view描述給用戶。

Spring 2.5 爲MVC Controllers引入了基於註解的編程模型,使用諸如@RequestMapping、@RequestParam、@ModelAttribute之類的註解。在Servlet MVC和Portlet MVC中均可用。

spring-projects Org on Github 裏,大量的web應用都使用了這種支持。例如MvcShowcase, MvcAjax, MvcBasic, PetClinic, PetCare, 以及其餘。

@Controller // 一個註解便可Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld") // 
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

3.一、使用@Controller定義一個Controller

@Controller註解用於標記一個class擁有controller的角色。

注意,這種註解Controller須要開啓自動探測才能使用。以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

3.二、使用@RequestMapping映射requests

使用@RequestMapping註解將URLs如/app映射到一個類或者特定的handler方法。示例:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(path = "/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

class級別上的@RequestMapping不是強制的。

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

@RequestMapping 變體

Spring 4.3 引入瞭如下@RequestMapping註解的變體,方法級別的。

@GetMapping

@PostMapping

@PutMapping

@DeleteMapping

@PatchMapping

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @GetMapping
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @GetMapping("/{day}")
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @GetMapping("/new")
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @PostMapping
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

@Controller 和 AOP 代理

一些狀況下,一個controller可能須要在運行時被AOP代理裝飾。一個例子是在controller上使用@Transactional。這時,對這些controllers,咱們建議使用基於類的代理。這也是controller的默認選項。

然而,若是一個controller必須實現一個非Spring Context回調的接口(如InitializingBean、*Aware等等)的話,你可能須要顯式的配置基於類的代理。例如,將 <tx:annotation-driven/> 改爲 <tx:annotation-driven proxy-target-class="true"/>

 

對Spring MVC 3.1中@RequestMapping methods的新的支持類(解析類?)

Spring 3.1 引入了針對RequestMapping methods的一組新的支持類,叫作:RequestMappingHandlerMappingRequestMappingHandlerAdapter。推薦使用它們,甚至須要利用Spring MVC 3.1及之後的一些新特性。這些新的支持類默認就由MVC命名空間和MVC Java config啓用,其餘狀況必須顯式的配置。本部分會描述一下新舊支持類之間的一些重要區別。

 

Spring 3.1以前,type和method -level request mappings是在兩個獨立的階段中檢查的 -- controller先被DefaultAnnotationHandlerMapping選中,具體的方法調用則由AnnotationMethodHandlerAdapter負責。

 

Spring 3.1 中新的支持類,只須要使用RequestMappingHandlerMapping。不妨將controller的那些方法看做一個集合,其內容是帶有映射的各類端點。

 

這使得一些新的可能成爲現實。For once a HandlerInterceptor or a HandlerExceptionResolver can now expect the Object-based handler to be a HandlerMethod, which allows them to examine the exact method, its parameters and associated annotations. The processing for a URL no longer needs to be split across different controllers.

 

下面這幾件事情已經再也不有效

  • Select a controller first with a SimpleUrlHandlerMapping or BeanNameUrlHandlerMapping and then narrow the method based on @RequestMapping annotations. -- 先選擇一個controller,再根據其@RequestMapping註解窄化請求?爲何不行了???幾個意思?
  • Rely on method names as a fall-back mechanism to disambiguate between two @RequestMapping methods that don’t have an explicit path mapping URL path but otherwise match equally, e.g. by HTTP method. In the new support classes @RequestMapping methods have to be mapped uniquely. --再也不支持基於方法名來區分沒有顯式指定映射URL的@RequestMapping方法。必須顯式的指定映射URL。
  • Have a single default method (without an explicit path mapping) with which requests are processed if no other controller method matches more concretely. In the new support classes if a matching method is not found a 404 error is raised.  -- 再也不支持默認方法處理請求!

上面的特性仍由現有的支持類支持。然而,若是想使用Spring 3.1的新特性,你須要使用新的支持類!

 

URI Template Patterns

URI Template是一個相似URI的字符串,包含一個或多個變量名字。當你使用值將其中的變量所有替換以後,該模板會變成一個URI。

在Spring MVC中,你能夠在方法的一個參數上使用@PathVariable註解,就能夠將實參綁定到URI模板變量的值。

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

注意,若是方法的參數名與URI模板變量的名字不符,那須要顯式的指定;不然能夠省略。以下:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // implementation omitted
}

一個方法能夠擁有任意數量的@PathVariable註解:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

當在Map<String, String>實參上使用@PathVariable時,map會被全部的URI模板變量填滿。

URI模板能夠從type和method級別的@RequestMapping註解中組裝。

@Controller
@RequestMapping("/owners/{ownerId}") // 這裏,類中的方法可使用
public class RelativePathUriTemplateController {

    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

@PathVariable實參能夠是任何簡單類型,如int、long、Date等。Spring會自動轉換到合適的類型,若是失敗會拋出TypeMishmatchException。-- 也能夠註冊其餘數據類型: You can also register support for parsing additional data types. See the section called 「Method Parameters And Type Conversion」 and the section called 「Customizing WebDataBinder initialization」.

 

帶正則表達式的URI Template Patterns

有時候你須要更精確的定義URI模板變量。考慮下 URL "/spring-web/spring-web-3.0.5.jar",你怎麼將其拆分紅多個部分?

 

@RequestMapping支持在URI模板變量中使用正則表達式。語法是: {varName:regex} 。以下:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
}

Path Patterns

除了URI模板,@RequestMapping註解和其全部變體還支持ant-style的path patterns,如 /mypath/*.do。

 

Path Pattern Comparison

當一個URL匹配多個patterns時,會使用一種排序來查找最佳匹配。

帶有更少URI變量和通配符的pattern ,被認爲更匹配。例如,/hotels/{hotel}/* /hotels/{hotel}/** 更合適,由於其餘同樣,通配符更少。

若是變量數量同樣,更長的被認爲更匹配。例如,/foo/bar* /foo/* 更匹配。

當兩個patterns擁有相同數量的變量和長度時,通配符少的更匹配。例如,/hotels/{hotel}/hotels/* 更匹配

另外,還有兩個特殊規則:

  • /**  匹配度最差。
  • 帶前綴的pattern,比其餘全部不含雙通配符的pattern,更差。例如:/public/** 比 /public/path/{a} 更差。

帶有佔位符的path patterns

@RequestMapping註解的patterns還支持 ${...} 佔位符。

後綴pattern匹配

默認,Spring MVC會執行 「.*」的匹配,因此,當一個controller的被映射到/person的時候,其實是隱式的被映射到/person.*。這樣可使用URL輕鬆的請求不一樣的資源表現,如/person.pdf, /person.xml。

後綴pattern匹配能夠被關閉,或者被限制在一組爲了內容協商目的而註冊的路徑擴展名中。很是建議使用最普通的請求映射來最小化請求的模糊性,由於有時候「.」不必定表明擴展名,例如/person/{id},有多是/person/joe@xx.com

後綴pattern匹配和RFD

Reflected file download (RFD) 攻擊,最先由Trustwave在2014年指出。這種攻擊相似XSS,都依賴能夠反射到響應的輸入。然而,不是將js插入到HTML中,RFD攻擊依賴於瀏覽器的下載,並依賴於瀏覽器將響應視爲一個可執行腳本(雙擊能運行的那種)。

 

在Spring MVC中,@ResponseBody 和 @ResponseEntity方法一樣具有風險,由於它們能夠渲染不一樣內容類型--客戶端可能經過URL路徑擴展名來請求的類型。須要注意,不管是單獨的禁用後綴pattern匹配仍是單獨的禁用用於內容協商目的的路徑擴展名,都不能有效的組織RFD攻擊。

 

爲了有效的防禦RFD,Spring在渲染響應體以前添加了一個header(Content-Disposition:inline;filename=f.txt)以建議一個固定和安全的下載文件名。這隻有在URL路徑包含的擴展名既不在白名單中,也不是因內容協商目的而顯式註冊的文件擴展名時纔有效。然而,這樣作也有其反作用,有時候可能會直接顯示在瀏覽器中!

 

默認,不少經常使用的路徑擴展名已經在白名單中。此外,REST API的調用一般不是用做在瀏覽器中使用的URL。儘管如此,使用自定義HttpMessageConverter實現的應用,能夠顯式的註冊用於內容協商目的的文件擴展名,針對這些擴展名Content-Disposition header(Content-Disposition:inline;filename=f.txt)不會被添加。

This was originally introduced as part of work for CVE-2015-5211. Below are additional recommendations from the report:

  • Encode rather than escape JSON responses. This is also an OWASP XSS recommendation. For an example of how to do that with Spring see spring-jackson-owasp.
  • Configure suffix pattern matching to be turned off or restricted to explicitly registered suffixes only.
  • Configure content negotiation with the properties "useJaf" and "ignoreUnknownPathExtensions" set to false which would result in a 406 response for URLs with unknown extensions. Note however that this may not be an option if URLs are naturally expected to have a dot towards the end.
  • Add X-Content-Type-Options: nosniff header to responses. Spring Security 4 does this by default.

Matrix Variables

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-matrix-variables

URI specification RFC 3986定義了在path segments內包含name-value對的可行性。在Spring MVC中,它們被視爲matrix Variables。

matrix variables可能出如今任意path segment中,每一個matrix variable都由分號隔離。例如:"/cars;color=red;year=2012"。多個value的時候,可使用逗號拼接,如"color=red,green,blue",也能夠重複name,如"color=red;color=green;color=blue"

 

若是但願一個URL包含matrix variables,請求映射pattern必須使用URI模板來表明它們。

下面是提取matrix variable 」q」的例子:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11

}

由於全部的path segments均可能含有matrix variables,某些狀況下你須要更精確的信息來肯定須要的變量:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22

}

一個matrix variable能夠被定義爲可選項,能夠擁有一個指定的默認值;

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1

}

全部的matrix variables能夠用一個Map來獲取:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

}

注意:爲了啓用matrix variables,你必須設置RequestMappingHandlerMapping的removeSemicolonContent property爲false。其默認是true。

The MVC Java config and the MVC namespace both provide options for enabling the use of matrix variables.

If you are using Java config, The Advanced Customizations with MVC Java Config section describes how the RequestMappingHandlerMapping can be customized.

In the MVC namespace, the <mvc:annotation-driven> element has an enable-matrix-variables attribute that should be set to true. By default it is set to false.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven enable-matrix-variables="true"/>

</beans>

Consumable Media Types

經過指定一個consumable media types列表來窄化映射。只有request header中的Content-Type符合指定的媒體類型時,請求才匹配。例如:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

注意,consumable media type表達式可使用「!」來否認匹配的媒體類型,如使用「!text/plain」來匹配除了text/plain以外的Content-Type。建議使用MediaType中的常量,如 APPLICATION_JSON_VALUE、 APPLICATION_JSON_UTF8_VALUE

注意,雖然consumes條件支持type和method級別,可是,不一樣於其餘條件,method級別的會覆蓋type級別的類型!!!

 

Producible Media Types

還能夠經過指定一個producible media types列表來窄化請求。僅當request header的Accept匹配時,該request纔會匹配。此外,使用produces條件會確保實際的內容類型。以下:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

注意produces條件指定的媒體類型,也能夠選擇性的指定一個字符集。例如,在上面的代碼片斷中,咱們指定了與MappingJackson2HttpMessageConverter中默認配置的媒體類型一致的媒體類型。--是否能夠認爲,一種字符集就是一種媒體類型?

同consumes相似,produces也可使用「!」。一樣建議使用MediaType中的常量。

同consumes相似,方法級別的produces會覆蓋類級別的媒體類型!!!

 

請求參數和請求頭的值 Request Parameter 、Request Header values

能夠經過請求參數條件來窄化請求的匹配,如:"myParam", "!myParam", or "myParam=myValue"。前兩個用於測試請求參數中是否出現該參數,第三個則須要請求參數有一個特定的值。例子:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

一樣的狀況還適合請求頭:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets", headers = "myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

雖然你可使用通配符來匹配Content-Type和Accept header values(如headers="content-type=text/*",能夠匹配"text/plain""text/html"),但建議使用consumes和produces。這也是它們的設計目的。

 

HTTP HEAD 和 HTTP OPTIONS

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-head-options

@RequestMapping方法映射到「GET」,同時也會隱式的映射到「HEAD」!

@RequestMapping方法內建支持HTTP OPTIONS。略。

 

3.三、定義@RequestMapping handler methods

@RequestMapping handler methods能夠有很是靈活的簽名。除了BindingResult參數以外的參數能夠按任意順序排放。

Spring 3.1 爲 @RequestMapping methods引入了一組新的支持類,分別是:RequestMappingHandlerMapping and RequestMappingHandlerAdapter。建議使用它們,並且,應該使用Spring 3.1及之後的新特性。這些新的支持類默認由MVC命名空間啓用,若是是Java config,必須顯式的配置--不然沒法使用。

支持的方法參數類型

  • 請求/響應對象(Servlet API)。如ServletRequest 或 HttpServletRequest。
  • 會話對象(Servlet API),HttpSession類型的。該類型的參數會強制相應的session出現。就是說,其實參永遠非null。

session的訪問可能不是線程安全的,特別是在一個Servlet環境中。若是須要多個請求併發訪問一個session時,能夠考慮將RequestMappingHandlerAdapter的synchronizeOnSession設置爲true。

  • org.springframework.web.context.request.WebRequest 或者 org.springframework.web.context.request.NativeWebRequest。容許泛型的請求參數訪問,以及請求/會話屬性訪問,不須要綁定到native的Servlet/Portlet API。
  • java.util.Locale,用於獲取當前請求的locale,由已啓用的最符合的locale resolver來決定--其實是在MVC環境中配置的LocaleResolver/LocacleContextResolver。
  • java.util.TimeZone (Java 6+) / java.time.ZoneId (Java 8),用於獲取當前請求的時區,由LocaleContextResolver決定。
  • java.io.InputStream / java.io.Reader,用於獲取請求的內容。其值是由Servlet API暴露的原生InputStream/Reader。
  • java.io.OutputStream / java.io.Writer,用於生成響應的內容。其值是由Servlet API暴露的原生的OutputStream/Writer。
  • org.springframework.http.HttpMethod,用於獲取HTTP request method。
  • java.security.Principle,包含了當前已認證的用戶。
  • @PathVariable註解的參數,用於獲取URI模板變量。
  • @MatrixVariable註解的參數,用於獲取URI path segments中的name-value對。
  • @RequestParam註解的參數,用於獲取特定的Servlet 請求參數。參數值會被轉換成方法參數類型 -- 類型轉換。
  • @RequestHeader註解的參數,用於獲取特定的Servlet請求HTTP headers。類型轉換。
  • @RequestBody註解的參數,用於獲取HTTP請求體。參數值會使用HttpMessageConverters來進行類型轉換。
  • @RequestPart註解的參數,用於獲取一個multipart/form-data請求的內容。
  • @SessionAttribute註解的參數,用於獲取已有的、永久的session attributes (例如用戶認證對象)。與其對比,經過@SessionAttributes會獲取臨時存儲在session的model attributes。
  • @RequestAttribute註解的參數,用於獲取request attributes。
  • HttpEntity<?> 參數,用於獲取Servlet request HTTP headers和contents。request stream會被轉成entity body--使用HttpMessageConverters。
  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap,略。
  • org.springframework.web.servlet.mvc.support.RedirectAttributes,用於指定在重定向中使用的一組具體的attributes,也能夠用於添加flash attributes(attributes暫存在服務器側,以在重定向後可用。)。
  • command或者form objects,用於綁定請求參數到bean properties,或者直接綁定到字段,經過定製的類型轉換--依賴於@InitBinder方法 和/或 HandlerAdapter配置。見RequestMappingHandlerAdapter的webBindingInitializer property。這些command objects和它們的校驗結果默認會被暴露成model attributes,使用command class name -- 例如,用model attribute 「orderAddress」表明一個「some.package.OrderAddress」類型的command object。@ModelAttribute註解,能夠用在方法參數上,以定製model attribute name。
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult校驗結果,用於其前面的第一個command 或者 form object。
  • org.springframework.web.bind.support.SessionStatus,狀態處理,用於標記form處理已完成,該標記會致使session attributes被清理 -- 那些由@SessionAttributes註解在type上指定的session attributes!
  • org.springframework.web.util.UriComponentsBuilder,一個builder,用於預處理一個URL,關聯到當前請求的host、port、scheme、context path、以及servlet mapping的字面值部分。

Errors和BindingResult參數,必須跟在model object後面,由於可能有多個model object,Spring會爲每一個model object建立一個單獨的BindingResult,因此,下面的例子不會執行

@PostMapping // Invalid ordering of BindingResult and @ModelAttribute. 
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

注意,在Pet和BindingResult之間有一個Model參數。必須以下排序才能工做:

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

支持JDK 1.8 的 java.util.Optional 做爲方法參數類型,與帶有required attribute的註解一塊兒使用(如@RequestParam、@RequestHeader等等)。這些狀況下,java.util.Optional 至關於 required = false 。

 

支持的方法返回類型

  • ModelAndView對象。with the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • Model對象。with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • Map對象。 for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • View對象。 with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
  • 一個String值,能被解釋爲logical view name。with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
  • void,若是方法本身處理響應 -- 方法形參裏聲明ServletResponse / HttpServletResponse。or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature).
  • 若是方法帶有@ResponseBody,返回類型會被寫成response HTTP body。返回值會被轉成聲明的方法參數類型--使用HttpMessageConverters。
  • HttpEntity<?>或ResponseEntity<?>對象,用於訪問Servlet response HTTP headers 和 contents。該entity body會被轉成response stream -- 經過HttpMessageConverters。
  • HttpHeaders對象,用於返回一個無body的response。
  • 當應用想在一個由Spring MVC管理的線程中異步地produce返回值時,能夠返回Callable<?>。
  • 當應用想從它本身選擇的線程中produce返回值時,能夠返回DeferredResult<?>。
  • 當應用想從它本身選擇的線程中produce返回值時,能夠返回ListenableFuture<?>。
  • 返回一個ResponseBodyEmittter,以異步的將多個對象寫入到response。也能夠做爲ResponseEntity內的body。
  • 返回一個SseEmitter,以異步的將Server-Sent Events寫入到response。也能夠做爲ResponseEntity內的body。
  • 返回一個StreamingResponseBody,以異步的寫入到response OutputStream。也能夠做爲ResponseEntity內的body。
  • 任意其餘返回類型,都被認爲是一個單獨的model attribute,暴露給view,--使用經過方法級別的@ModelAttribute指定的attribute name(或者,默認的attribute name,基於返回類型的類名字)。The model is implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.

 

使用@RequestParameter將請求參數綁定到方法參數

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

默認,@RequestParam的required attribute是true,能夠設置爲false。@RequestParam(name="id", required=false)

若是目標方法參數的類型不是String,會自動使用類型轉換。

當在Map<String, String> 或 MultiValueMap<String, String>實參上使用@RequestParam註解時,會被填入全部的request parameters。

使用RequestBody註解來映射request body

方法參數的@RequestBody註解,標識了方法參數應該綁成HTTP request body的值。例如:

@PutMapping("/something")
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

能夠經過使用一個HttpMessageConverter將request body轉成method argument。HttpMessageConverter負責將HTTP request msg轉成一個對象以及將對象轉成HTTP response body。RequestMappingHandlerAdapter支持@RequestBody的默認HttpMessageConverters:

  • ByteArrayHttpMessageConverter converts byte arrays.
  • StringHttpMessageConverter converts strings.
  • FormHttpMessageConverter converts form data to/from a MultiValueMap<String, String>.
  • SourceHttpMessageConverter converts to/from a javax.xml.transform.Source.

注意,若是使用MVC 命名空間或者使用MVC Java config,默認會註冊更多的message converters。

若是你打算讀寫XML,你會須要配置一個MarshallingHttpMessageConverter -- 使用org.springframework.oxm包中的特定的Marshaller和Unmarshaller實現。下面的例子演示瞭如何直接讀寫XML -- 可是,若是你的應用是經過MVC命名空間或MVC Java config來配置的,見 Section 22.16.1, 「Enabling the MVC Java Config or the MVC XML Namespace」

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <util:list id="beanList">
            <ref bean="stringHttpMessageConverter"/>
            <ref bean="marshallingHttpMessageConverter"/>
        </util:list>
    </property
</bean>

<bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter"/>

<bean id="marshallingHttpMessageConverter"
        class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <property name="marshaller" ref="castorMarshaller"/>
    <property name="unmarshaller" ref="castorMarshaller"/>
</bean>

<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

@RequestBody 註解的方法參數還可使用@Valid註解,Spring會使用配置好的Validator實例來校驗該參數。當使用MVC命名空間或MVC Java config時,一個JSR-303 validator會被自定的配置 -- 假如classpath中有一個JSR-303實現。

就像使用@ModelAttribute註解的參數已有,Errors參數能夠用來檢查errors。若是沒有聲明這樣一個參數,會拋出一個MethodArgumentNotValidException。該異常由DefaultHandlerExceptionResolver來處理,會返回400 error。

使用@ResponseBody註解來映射response body

@ResponseBody註解相似於@RequestBody。該註解放在方法上指示返回類型會被寫入HTTP response body (沒有被放入Model,或被解釋成view name)。 例如:

@GetMapping("/something")
@ResponseBody
public String helloWorld() {
    return "Hello World";
}

上面的例子,會將字符串寫入HTTP response stream。

如同@RequestBody,Spring會將返回的對象轉換成一個response body -- 使用一個HttpMessageConverter。

 

使用@RestController註解建立一個REST Controller

使用@RestController代替@ResponseBody與@Controller。它雖然是由後二者組合而成,但在未來會被賦予更多語義。

@RestController也能夠配合@ControllerAdvice或@RestControllerAdvice beans。詳見 the the section called 「Advising controllers with @ControllerAdvice and @RestControllerAdvice」 section。

使用HttpEntity

HttpEntity 相似於 @RequestBody和@ResponseBody。除了能獲取request和response body以外,HttpEntity(以及其response子類:ResponseEntity)還容許獲取request和response  headers,以下:

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

The above example gets the value of the MyRequestHeader request header, and reads the body as a byte array. It adds the MyResponseHeader to the response, writes Hello World to the response stream, and sets the response status code to 201 (Created).

As with @RequestBody and @ResponseBody, Spring uses HttpMessageConverter to convert from and to the request and response streams. For more information on these converters, see the previous section and Message Converters.

 

在方法上使用@ModelAttribute

該註解能夠用在方法或方法參數上。本部分講解用在方法上的做用,下一部分會講解用在方法參數上的做用。

在方法上使用該註解,意味着該方法的一個目的是增長一個或多個model attribute。該方法支持的參數類型與@RequestMapping methods同樣,但不能直接映射到請求。相反,同一個Controller中的@ModelAttribute methods會在@RequestMapping methods以前被調用!!!例子:

// 添加一個 attribute
// 該方法的返回值會被添加到model account中
// 你能夠定義該model的名字,例如 @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// 添加多個 attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

@ModelAttribute methods被用於將經常使用的attributes填入model。

注意兩種形式的@ModelAttribute methods。第一個,是隱式的將返回值添加爲attribute。第二個,接收一個Model,而後在其中增長任意數量的model attributes。

一個Controller能夠擁有任意數量的@ModelAttribute methods。全部這些方法都會在同一個Controller中的@RequestMapping methods以前被調用!

@ModelAttribute methods 也能夠被定義在@ControllerAdvice class內,這樣的methods會被用於全部Controllers。--就是在全部Controller的全部@RequestMapping methods以前被調用!

若是沒有顯式指定一個model attribute name,會發生什麼?這種狀況下,會基於其類型賦予一個默認的名字。例如,若是方法返回了Account類型,那默認的name就是account。

@ModelAttribute註解也能夠用在@RequestMapping methods上。這種狀況下,方法的返回值被解釋成一個model attribute,而非view name。view name會基於name慣例而獲取到,更像是返回了void。 see Section 22.13.3, 「The View - RequestToViewNameTranslator」

在方法參數上使用@ModelAttribute

當@ModelAttribute用於方法參數上時,表明該參數應該從該model中獲取。若是model中沒有,該參數會先被實例化,再被添加到model。一旦出如今model中,該參數的字段會被匹配名字的request parameters填充。這就是Spring MVC中的數據綁定(data binding),一個很是有用的機制,節省了你手動解析每一個form字段的時間。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

上面的例子,Pet實例從哪裏來?有幾個選項:

  • 可能已經存在於@SessionAttributes的model中。
  • 可能已經存在於同一個Controller@ModelAttribute method的model中。
  • 可能基於URI模板變量和類型轉換器而獲取(稍後詳解)。
  • 可能使用其默認構造器實例化。

@ModelAttribute method是從數據庫中獲取attribute的一種經常使用方式,可能可選的存儲於requests之間--經過使用@SessionAttributes。某些狀況下,使用URI模板變量和類型轉換器更爲方便。例子:

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

上面的例子,model attribute的name與URI模板變量的名字一致。若是你註冊了一個Converter<String, Account>,那麼上面的例子就能夠沒必要使用一個@ModelAttribute method。

 

下一步就是數據綁定。WebDataBinder類會匹配request parameter names -- 包含query string parameters 和 form fields -- 到model attribute fields,根據名字。匹配的字段會被填充--當必要的類型轉換被應用了以後。Data binding and validation are covered in Chapter 9, Validation, Data Binding, and Type Conversion. Customizing the data binding process for a controller level is covered in the section called 「Customizing WebDataBinder initialization」.

 

數據綁定的一個結果是,可能存在errors,例如缺失必須的字段或者類型轉換錯誤。爲了檢查該類錯誤,須要在@ModelAttribute argument以後緊跟着添加一個BindingResult argument。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

使用BindingResult,你能夠檢查是否有errors,可使用Spring的<errors> form tag來在同一個form中顯示錯誤。

 

注意,某些狀況下,不使用數據綁定而獲取model中的一個attribute頗有用。這些狀況下,你能夠在Controller中注入Model,或者在註解上使用binding flag,以下:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) {

    // ...
}

In addition to data binding you can also invoke validation using your own custom validator passing the same BindingResult that was used to record data binding errors. That allows for data binding and validation errors to be accumulated in one place and subsequently reported back to the user:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

-- 就是根據BindingResult的結果進行本身的操做。

或者,可使用JSR-303 @Valid註解來自動調用校驗:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

使用@SessionAttributes在requests之間的HTTP session中存儲model attributes

type-level @SessionAttributes註解,聲明瞭用於特定handler的session attributes。這會列出model attributes的names或types -- 應該透明的存儲於session或某conversational storage,在後續的requests中做爲form-backing beans。

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

使用@SessionAttribute訪問預存的全局session attributes

若是須要訪問pre-existing global session attributes,就是在controller外部(例如在filter中)管理的 ,且可能或可能不會出如今method parameter上使用@SessionAttribute註解(--什麼鬼)(If you need access to pre-existing session attributes that are managed globally, i.e. outside the controller (e.g. by a filter), and may or may not be present use the @SessionAttribute annotation on a method parameter)。

@RequestMapping("/")
public String handle(@SessionAttribute User user) {
    // ...
}

當須要增長或移除session attributes時,能夠考慮在controller method上注入 org.springframework.web.context.request.WebRequest 或 javax.servlet.http.HttpSession。

爲了在session中臨時存儲model attributes以做爲controller workflow的一部分,能夠考慮使用SessionAttributes as described in the section called 「Using @SessionAttributes to store model attributes in the HTTP session between requests」.

 

使用@RequestAttribute來獲取request attributes

相似於@SessionAttribute,@RequestAttribute也能夠用於獲取pre-existing request attributes -- 由filter或interceptor建立的。

@RequestMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}

處理application/x-www-form-urlencoded data

前面的部分描述了使用@ModelAttribute來支持來自瀏覽器客戶端的form submission requests。@ModelAttribute註解還被推薦用於處理來自非瀏覽器客戶端的請求。然而,當處理HTTP PUT requests時,有一個顯著的不一樣。瀏覽器會經過HTTP GET或HTTP POST提交表單數據。非瀏覽器客戶端還能夠經過HTTP PUT來提交。這就有一個問題,由於Servlet specification要求 ServletRequest.getParameter*()方法僅支持HTTP POST的字段獲取,而非HTTP PUT。

爲了支持HTTP PUT和PATCH 請求,spring-web模塊提供了過濾器:HttpPutFormContentFilter

<filter>
    <filter-name>httpPutFormFilter</filter-name>
    <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpPutFormFilter</filter-name>
    <servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

上面的filter,會攔截content type爲 application/x-www-form-urlencoded 的 HTTP PUT和PATCH請求,從其請求體中讀取表單數據,封裝ServletRequest以讓ServletRequest.getParameter*() 可以使用表單數據。

因爲HttpPutFormContentFilter會consume請求體,因此,不該爲那些依賴針對 application/x-www-form-urlencoded 的轉換器的PUT或PATCH URLs配置該filter。這包括@RequestBody MultiValueMap<String, String> 和 HttpEntity<MultiValueMap<String, String>>。

使用@CookieValue註解來映射cookie values

該註解容許一個方法參數綁定一個HTTP cookie的值。

假定從http request接收了以下cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代碼演示瞭如何獲取JSESSIONID cookie:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
    //...
}

若是目標方法參數的類型不是String,會自動應用類型轉換。

該註解,也被在Servlet和Portlet環境下的annotated handler methods支持。

 

使用@RequestHeader註解來映射request header attributes

這是一個樣例request header:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

下面的代碼演示瞭如何獲取Accept-Encoding和Keep-Alive headers的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

若是目標方法參數的類型不是String,會自動應用類型轉換。

當@RequestHeader註解用於一個Map<String, String>、 MultiValueMap<String, String>,或HttpHeaders argument時,該map會被填入全部的header values。

內建的支持容許轉換一個逗號間隔的字符串,將其轉成一個字符串或其餘類型的數組/集合。例如,@RequestHeader(「Accept」)註解的方法參數,多是String類型,也多是String[]或List<String>類型。

該註解,也被在Servlet和Portlet環境下的annotated handler methods支持。

 

method parameters 和 type conversion

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-typeconversion

從request中提取出來的基於字符串的值,包括request parameters、path variables、request headers、還有cookie values,可能須要被轉成它們要綁定的method parameter或field的類型。若是目標類型不是String,Spring會自動轉成合適的類型。支持全部簡單類型,如int、long、Date等等。甚至,你可使用一個WebDataBinder來定製轉換過程,或者經過在FormattingConversionService中註冊Formatters。

 

定製WebDataBinder 初始化

經過Spring的WebDataBinder使用PropertyEditors來定製request parameter 的綁定,你能夠在你的controller中使用@InitBinder methods,或者在@ControllerAdvice class中使用@InitBinder methods,或者提供一個定製的WebBindingInitializer。 See the the section called 「Advising controllers with @ControllerAdvice and @RestControllerAdvice」 section for more details.

 

使用@InitBinder 定製數據綁定

在controller方法上使用@InitBinder,容許你在controller內部配置web數據綁定。@InitBinder表明方法初始化了WebDataBinder--會被用於填充被註解的handler方法的command 和 form object arguments。

這些init-binder methods,支持@RequestMapping所支持的全部參數,除了command/form objects和相應的校驗結果對象。init-binder methods必須沒有返回值。因此,大多被聲明爲void。 典型的arguments包括WebDataBinder 結合 WebRequest或 java.util.Locale,容許代碼註冊特定context的editors。

 

下面的案例演示了使用@InitBinder來配置針對全部java.util.Date form properties的一個CustomDateEditor。

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

或者,自Spring 4.2起,能夠考慮使用addCustomFormatter來指定Formatter實現,取代PropertyEditor實例

若是你在一個shared FormattingConversionService中有基於Formatter的設置,這會很是有用,只須要一樣的作法來複用特定controller針對binding rules的調節。

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

配置一個定製的WebBindingInitializer

爲了將數據綁定初始化外部化,你能夠提供一個定製的WebBindingInitializer實現,而後經過提供一個定製的AnnotationMethodHandlerAdapter的bean configuration來啓用它。

下面的例子示意了使用了org.springframework.samples.petclinic.web.ClinicBindingInitializer的配置,該配置配置了幾個PetClinic controllers須要的PropertyEditors。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0"/>
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/>
    </property>
</bean>

@InitBinder methods也能夠定義在@ControllerAdvice class內,會用於全部的controllers。效果同WebBindingInitializer。See the the section called 「Advising controllers with @ControllerAdvice and @RestControllerAdvice」 section for more details.

 

advising controllers with @ControllerAdvice and @RestControllerAdvice

@ControllerAdvice註解,是一個component annotation,容許實現類可以在classpath掃描中被自動探測到。當使用MVC namespace或MVC Java config時,自動啓用。

@ControllerAdvice class,能夠含有@ExceptionHandler、@InitBinder以及@ModelAttribute methods,這些methods,會被應用到全部controller中@RequestMapping methods,而非僅僅其所聲明的controller中。

 

@RestControllerAdvice,等同於@ExceptionHandler + @ResponseBody methods。

@ControllerAdvice和@RestControllerAdvice 均可以指定做用的controllers:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

詳見@ControllerAdvice文檔。

 

Jackson Serialization View Support

It can sometimes be useful to filter contextually the object that will be serialized to the HTTP response body. 爲了提供這種能力,Spring MVC提供了內建的支持,能夠rendering with Jackson’s Serialization Views.

在一個@Response controller method上,或者在那些返回ResponseEntity的controller methods上,簡單的添加@JsonView註解,並指定須要使用的view class或Interface便可。以下:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

注意,儘管@JsonView容許指定多個class,但在controller method上使用時只能指定一個class!能夠考慮使用複合接口,若是你須要啓用多個views。

對於那些依賴view resolution的controllers,簡單的將序列化view class添加到model便可:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

 

Jackson JSONP 支持

爲了啓用JSONP對@ResponseBody和@ResponseEntity methods的支持,聲明一個@ControllerAdvice bean  -- 須要繼承AbstractJsonpResponseBodyAdvice,並在其構造中指出JSONP的query parameter name(s)。以下:

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

對於依賴view resolution的controllers,JSONP自動被啓用,默認的query parameter name是 jsonp 或 callback。 能夠經過其jsonpParameterNames property來定製。

 

3.四、異步請求的處理

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async

Spring 3.2 引入了基於 Servlet 3 的異步請求處理。不是一直以來的讓controller method返回一個值,而是,讓controller method返回一個java.util.concurrent.Callable,而後從Spring MVC管理的線程中produce 返回值。同時,main Servlet container thread 會被退出和釋放,以處理其餘請求。Spring MVC在一個獨立的線程調用Callable -- 經過TaskExecutor,當Callable返回時,請求會被分派回Servlet container,從而恢復處理。例子:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

另外一個選擇是,controller method返回DeferredResult。這種狀況下,也多是任意線程produce的返回值,就是說,非Spring MVC管理的線程!例如,結果多是響應某個外部事件,如一個JMS message、一個scheduled task等等,而produce的結果。下面是一個例子:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);

若是沒有Servlet 3.0 異步請求處理特性的相關知識,會很難理解這點。這裏是關於底層機制的一些基本的事實:

  • 一個ServletRequest能夠被放入asynchronous mode,使用request.startAsync()便可。這樣作的主要效果就是,該Servlet以及全部Filters,可以退出,但response仍然保持open-- 容許在後面完成處理。
  • request.startAsync() 會返回 AsyncContext,能夠被用於對async處理的更進一步的控制。例如,它提供了dispatch方法,相似於Servlet API的forward -- 除了它容許應用恢復在一個Servlet container thread中的請求處理。
  • ServletRequest提供了對當前DispatcherType的訪問,該DispatcherType can be used to distinguish between processing the initial request, an async dispatch, a forward, and other dispatcher types.

With the above in mind, the following is the sequence of events for async request processing with a Callable:

  • Controller returns a Callable.
  • Spring MVC starts asynchronous processing and submits the Callable to a TaskExecutor for processing in a separate thread.
  • The DispatcherServlet and all Filter’s exit the Servlet container thread but the response remains open.
  • The Callable produces a result and Spring MVC dispatches the request back to the Servlet container to resume processing.
  • The DispatcherServlet is invoked again and processing resumes with the asynchronously produced result from the Callable.

The sequence for DeferredResult is very similar except it’s up to the application to produce the asynchronous result from any thread:

  • Controller returns a DeferredResult and saves it in some in-memory queue or list where it can be accessed.
  • Spring MVC starts async processing.
  • The DispatcherServlet and all configured Filter’s exit the request processing thread but the response remains open.
  • The application sets the DeferredResult from some thread and Spring MVC dispatches the request back to the Servlet container.
  • The DispatcherServlet is invoked again and processing resumes with the asynchronously produced result.

For further background on the motivation for async request processing and when or why to use it please read this blog post series.

 

async requests 的 Exception處理

若是,一個由controller method返回的Callable在執行時 拋出了一個Exception,會發生什麼?簡短的答案是與一個controller method拋出一個異常時相同。會經歷常規的異常處理機制。 長的解釋是,當Callable拋出一個Exception時,Spring MVC會將該Exception分派到Servlet container,將其做爲結果以及恢復request processing的引導,此時request processing會處理Exception,而非controller method return value。當使用DeferredResult時,你還能夠選擇是否調用setResult或者setErrorResult -- 傳入Exception實例。

 

攔截async requests

一個HandlerInterceptor也能夠實現AsyncHandlerInterceptor,以實現afterConcurrentHandlingStarted callback,當asynchronous processing開始時,會調用afterConcurrentHandlingStarted ,而非postHandle和afterComplete。

 

一個HandlerInterceptor也能夠註冊一個CallableProcessingInterceptor 或 一個 DeferredResultProcessingInterceptor,以更深度地集成asynchronous request的lifecycle,例如,handle 一個 timeout event。詳見 AsyncHandlerInterceptor javadoc。

 

DeferredResult 類型,也提供了諸如 onTimeout(Runnable)、onCompletion(Runnable)之類的方法。詳見javadoc。

 

當使用一個Callable時,你能夠將其wrap進一個WebAsyncTask的實例,該實例也能夠提供timeout和completion的方法註冊。

 

HTTP Streaming

一個controller method可使用DeferredResult和Callable來異步的produce其返回值,可被用於實現諸如long polling之類的技術 -- 這樣,服務器能夠將一個事件儘快的push到客戶端。

若是你想要在一個HTTP response上push多個事件會怎樣?這就是與」Long Polling」 有關的技術,也就是HTTP Streaming。 Spring MVC經過ResponseBodyEmitter 返回值類型使其成爲可能,該返回值類型可悲用於發送多個對象(而非使用@ResponseBody只發送一個 -- 這種更常見),每一個被髮送的對象都經過一個HttpMessageConverter被寫入到response 。

例子:

@RequestMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

注意,ResponseBodyEmitter 也可被用作ResponseEntity的body,以便定製response的status 和 headers。

 

HTTP Streaming With Server-Sent Events

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async-sse

 

HTTP Streaming Directly To The OutputStream

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async-output-stream

@RequestMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

 

Configuring Asynchronous Request Processing

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async-configuration

 

3.五、測試controllers

spring-test模塊提供了第一等級的針對註解controller的測試支持。See Section 15.6, 「Spring MVC Test Framework」.

 

四、Handler mappings

在以前的Spring版本中,用戶必需要在web應用上下文中定義一個或者多個HandlerMapping beans 以將incoming web requests映射到合適的handlers。 隨着annotated controllers的引入,如今通常能夠沒必要那樣作了,由於RequestMappingHandlerMapping 會自動在全部@Controller beans中查找@RequestMapping註解。然而,務必記住,全部的繼承自AbstractHandlerMapping的HandlerMapping類,都有如下properties -- 你能夠用來定製它們的行爲:

interceptors,使用的攔截器列表。

defaultHandler,默認使用的handler -- 當handler mapping 沒有一個匹配的handler時。

order,基於該property的值(Ordered接口),Spring將全部可用的handler mappings進行排序,並應用第一匹配的handler。

alwaysUseFullPath,若是設爲true,Spring會在當前Servlet context中使用全路徑來查找合適的handler。若是false(默認就是),會使用當前Servlet mapping內的路徑。例如,若是一個Servlet被映射到/testing/*,當設爲true時,使用/testing/viewPage.html,不然,/viewPage.html。

urlDecode,默認true,自Spring 2.5起。若是你傾向於對比encoded paths,須要設爲false。然而,HttpServletRequest老是以decoded形式暴露Servlet path。注意,當與encoded path對比時,Servlet path不會匹配。

配置攔截器的例子:

<beans>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <bean class="example.MyInterceptor"/>
        </property>
    </bean>
<beans>

 

4.1 使用HandlerInterceptor攔截requests

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-handlermapping-interceptor

spring的handler mapping機制包括handler interceptors,當你想要針對特定的requests應用特定的功能時,很是有用。

 

位於handler mapping內的interceptors,必須實現org.springframework.web.servlet.HandlerInterceptor (或者其實現/子類)。

該接口定義有三個方法preHandle(..) postHandle(..) afterHandle(..)。   見這裏。(爲知筆記的鏈接,不知道行不行,之後再說)

preHandle(..)方法會返回一個boolean值,若是false,會破壞執行鏈的處理過程(再也不往下執行)。若是false,DispatcherServlet會認定該攔截器自身來處理請求(例如,渲染視圖等),因此不會繼續執行其餘的攔截器和實際的handler。

 

攔截器能夠在全部繼承自AbstractHandlerMapping的類中設置,使用其interceptors屬性! 以下:

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

該映射處理的全部請求,都會被 TimeBaseAccessInterceptor 攔截。  該攔截器的就是讓你只能在辦公時間訪問。

注意:當使用RequestMappingHandlerMapping時,實際的handler是HandlerMethod的一個實例,該HandlerMethod會識別要被調用的特定的controller method。

個人補充:handler mapping這個過程,是將request與handler之間映射起來的過程。Spring提供的實現類,能用的也就這幾個:

3a57b6dd-987f-44a5-87f9-6bfb53989d2b

--- 還有一個RequestMappingHandlerAdapter,不要混淆了。

 

如你所見,Spring的適配器類 HandlerInterceptorAdapter,讓繼承HandlerInterceptor更加簡單。

 

在上面的例子中,被配置過的攔截器會被應用到全部由註解過的controller method處理的請求上。若是你想窄化一個攔截器應用的URL路徑,你可使用MVC 命名空間或者MVC Java config,或者聲明MappedInterceptor類型的bean實例來完成。See Section 22.16.1, 「Enabling the MVC Java Config or the MVC XML Namespace」.

 

注意:postHandle方法,一般不能理想地配合@ResponseBody和ResponseEntity方法。    在這種狀況下,一個HttpMessageConverter會寫入並提交響應 -- 先於postHandle!從而致使沒法修改響應,例如,添加一個header。這種狀況下,能夠實現ResponseBodyAdvice,而後要麼將其聲明爲@ControllerAdvice bean,要麼直接在RequestMappingHandlerAdapter中配置。

相關文章
相關標籤/搜索