Web MVC framework框架
Spring Web MVC框架簡介
Spring MVC的核心是`DispatcherServlet`,該類作用非常多,分發請求處理,配置處理器映射,處理視圖view,本地化,時間區域和主題,也支持文件上傳。默認的處理器依賴於`@Controller`和`RequestMapping`注解,提供了大量的靈活的處理方法。spring3.0中就介紹過了,`@Controller`機制,可通過SpringMVC提供的`@PathVariable`注解和其他功能,創建`RESTful`web網站和應用。 ```text 在spring MVC中一個關鍵的設計就是“開閉原則”,即對擴展開放對修改閉合原則。Spring 一些核心類的方法 是`final`方法。開發者不能重寫這些方法,來增加自己的行為。這些都不是隨意決定,而是意在符合“開閉原則”。
預知此原則詳情,參看 Seth Ladd(這應該就是馬丁大叔)等人的 Expert Spring Web MVC and Web Flow,尤其是要看“A Look At Desig” 章節,在第一版的117頁
使用Spring MVC時不能對final 方法織入增強。比如,你不能對AbstractController.setSynchronizeOnSession()
方法織入增強。關於AOP代理和之所以不能對final方法織入增強,請參看Section 9.6.1, “Understanding AOP proxies”
在Spring Web MVC中,可以使用任意對象作為請求命令或者是表單回寫對象;無需實現框架指定接口或者是基類。spring數據綁定非常靈活:比如,它將類型不匹配處理為驗證錯誤,該錯誤由應用拋出,而不是作為系統錯誤。因此,無需你復制你的業務對象屬性、非常簡單的就能處理form表單中未定義的字串,或者是轉換Strings的類型。當然,最好是直接綁定你的業務對象。
Spring的視圖解決方案非常靈活。一個`Controller`通常是負責將數據轉換成model map,並選擇一個view name,但是它也能直接向response 流中寫入來完成request.View視圖名字解決方案是可配置的,實現途徑多種多樣:通過文件擴展或者Accetp header content type,通過bean名字,一個properties屬性文件,甚至是自定義的`ViewResolver`實現。model模型(MVC中的M)是一個`Map`接口,它是視圖技術的基礎。可以直接集成基於渲染技術的模板,像JSP,Velocity和Freemarker,或者直接生成XML,JSON,Atom和許多其他類型的內容。model Map將會進行簡單的轉換為合適的格式,像JSP中的reqeust attributes,Velocity模板的model。
<h4 id='mvc-features'>Spring Web MVC的功能</h4>
```text
Spring Web Flow
Spring Web Flow (SWF)意在管理web應用頁面流程的。
SWF支持現有框架集成,比如Spring MVC和JSF,Servelt環境和Port了環境都行。如果你有也業務,需要將model模型轉換為純粹的request model,那么SWF也許是最好的解決方案 。
SWF允許你捕獲邏輯頁面流程作為字包含模塊,該模塊將會在不同的場景中重用,比如,用戶向導就是通過控制導航用來驅動業務處理。
For more information about SWF, consult the Spring Web Flow website.
有關SWF更多的信息,參閱[Spring Web Flow網站 ](http://projects.spring.io/spring-webflow/)
Spring的 web模塊包含很多特有的web 支持功能:
- 清晰的角色分離:controller, validator, command object, form object, model object, DispatcherServlet, handler mapping, view resolver等等,都存在相關的專用對象
- 強大的、簡單的配置。配置能力包括易於跨context引用,比如在web controller中引用業務對象。
- 適應能力強、非侵入,非常靈活。controller的方法簽名隨意定義,參數可用注解用來解決給定的場景(比如@RequestPram,@RequestHeader,@PathVariable等等)
- 重用業務代碼,無需重復。使用已經存在的業務對象或者form 對象,無需復制或者繼承指定的框架基類。
- 自定義數據綁定和驗證。類型不匹配作為應用級別驗證錯誤並保持現其值,本地時間和數字綁定等等用來替代現有的轉換機制,現有轉換機制是指:僅有String的form對象和業務對象之間互相轉換
- 自定義handler mapping處理映射和視圖解決方案。Handler mapping和視圖解決方案策略,從簡單的到復雜的,以及特定的解決策略,都行。Spring和其他mvc框架相比,更靈活。
- Flexible model transfer. Model transfer with a name/value Map supports easy integration with any view technology.
- Customizable locale, time zone and theme resolution, support for JSPs with or without Spring tag library, support for JSTL, support for * * * Velocity without the need for extra bridges, and so on.
- A simple yet powerful JSP tag library known as the Spring tag library that provides support for features such as data binding and themes. The custom tags allow for maximum flexibility in terms of markup code. For information on the tag library descriptor, see the appendix entitled Chapter 39, spring.tld
- A JSP form tag library, introduced in Spring 2.0, that makes writing forms in JSP pages much easier. For information on the tag library descriptor, see the appendix entitled Chapter 40, spring-form.tld
- Beans whose lifecycle is scoped to the current HTTP request or HTTP Session. This is not a specific feature of Spring MVC itself, but rather of the WebApplicationContext container(s) that Spring MVC uses. These bean scopes are described in Section 5.5.4, “Request, session, and global session scopes”
其他MVC實現的可拔插集成
在有些項目中,非SPring的 MVC實現是可取的。很多團隊希望利用已經存在的技術和工具,比如JSF. 如果不想使用Spring’s Web MVC,但是想使用Spring其他的東西,那么就可以使用Spring集成你選擇的MVC框架,非常容易。通過`ContextLoaderListener`啟動Spring root application Context (Spring上下文),在任意的action對象中通過`ServletContext`屬性訪問上下文環境。無插件,無集成。在web層的view中,像使用類庫一樣使用Spring,root application context應用上下文作為Spring的訪問入口。 反正就是一句話,不用Spring MVC,照樣可以使用Spring管理bean ,注冊Service
WebApplicationContext中的特殊bean
`DispatcherServlet `使用特殊的bean處理request和相應的view。這些bean是Spring MVC的一部分。可以通過簡單的配置選擇`WebApplicationContext`中的特殊bean。如果你啥都不配置,也沒關系,Spring都為這些bean指定了默認的實現。接下來看看這些特殊bean。
Bean type | Explanation |
---|---|
HandlerMapping | Maps incoming requests to handlers and a list of pre- and post-processors (handler interceptors) based on some criteria the details of which vary by HandlerMapping implementation. The most popular implementation supports annotated controllers but other implementations exists as well. |
HandlerAdapter | Helps the DispatcherServlet to invoke a handler mapped to a request regardless of the handler is actually invoked. For example, invoking an annotated controller requires resolving various annotations. Thus the main purpose of a HandlerAdapter is to shield the DispatcherServlet from such details. |
HandlerExceptionResolver | Maps exceptions to views also allowing for more complex exception handling code. |
ViewResolver | Resolves logical String-based view names to actual View types. |
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 | Parses multi-part requests for example to support processing file uploads from HTML forms. |
FlashMapManager | Stores and retrieves the "input" and the "output" FlashMap that can be used to pass attributes from one request to another, usually across a redirect. |
DispatcherServlet默認配置
`DispatcherServlet `中使用的特殊bean的默認實現,其信息配置在`org.springframework.web.servlet`包中的`DispatcherServlet.properties`。 特殊bean默認實現的存在都是有道理的。很快你就會指定這些bean的自定義實現。比如,有個非常常用的配置,修改`InternalResourceViewResolver `類的`prefix `來設置view 文件的目錄。
Regardless of the details, the important concept to understand here is that once you configure a special bean such as an InternalResourceViewResolver in your WebApplicationContext, you effectively override the list of default implementations that would have been used otherwise for that special bean type. For example if you configure an InternalResourceViewResolver, the default list of ViewResolver implementations is ignored.
In Section 20.16, “Configuring Spring MVC” you’ll learn about other options for configuring Spring MVC including MVC Java config and the MVC XML namespace both of which provide a simple starting point and assume little knowledge of how Spring MVC works. Regardless of how you choose to configure your application, the concepts explained in this section are fundamental should be of help to you.
DispatcherServlet 處理順序
After you set up a DispatcherServlet, and a request comes in for that specific DispatcherServlet, the DispatcherServlet starts processing the request as follows:
- The WebApplicationContext is searched for and bound in the request as an attribute that the controller and other elements in the process can use. It is bound by default under the key DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE.
- The locale resolver is bound to the request to enable elements in the process to resolve the locale to use when processing the request (rendering the view, preparing data, and so on). If you do not need locale resolving, you do not need it.
- The theme resolver is bound to the request to let elements such as views determine which theme to use. If you do not use themes, you can ignore it.
- If you specify a multipart file resolver, the request is inspected for multiparts; if multiparts are found, the request is wrapped in a MultipartHttpServletRequest for further processing by other elements in the process. See Section 20.10, “Spring’s multipart (file upload) support” for further information about multipart handling.
- An appropriate handler is searched for. If a handler is found, the execution chain associated with the handler (preprocessors, postprocessors, and controllers) is executed in order to prepare a model or rendering.
- If a model is returned, the view is rendered. If no model is returned, (may be due to a preprocessor or postprocessor intercepting the request, perhaps for security reasons), no view is rendered, because the request could already have been fulfilled.
- Handler exception resolvers that are declared in the WebApplicationContext pick up exceptions that are thrown during processing of the request. Using these exception resolvers allows you to define custom behaviors to address exceptions.
The Spring DispatcherServlet also supports the return of the last-modification-date, as specified by the Servlet API. The process of determining the last modification date for a specific request is straightforward: the DispatcherServlet looks up an appropriate handler mapping and tests whether the handler that is found implements the LastModified interface. If so, the value of the long getLastModified(request) method of the LastModified interface is returned to the client.
You can customize individual DispatcherServlet instances by adding Servlet initialization parameters ( init-param elements) to the Servlet declaration in the web.xml file. See the following table for the list of supported parameters.
Table 20.2. DispatcherServlet initialization parameters
Parameter | Explanation |
---|---|
contextClass | Class that implements WebApplicationContext, which instantiates the context used by this Servlet. By default, the XmlWebApplicationContext is used. |
contextConfigLocation | String that is passed to the context instance (specified by contextClass) to indicate where context(s) can be found. The string consists potentially of multiple strings (using a comma as a delimiter) to support multiple contexts. In case of multiple context locations with beans that are defined twice, the latest location takes precedence. |
namespace | Namespace of the WebApplicationContext. Defaults to [servlet-name]-servlet. |
實現Controller
Controllers提供了訪問應用的入口。Controllers解析request並轉換為model模型,模型向view視圖提供數據。Spring高度抽象了controller,這樣開發者可通過各種方式創建controller。 Spring 2.5開始,可以使用注解創建controller,比如`@RequestMapping, @RequestParam, @ModelAttribute`等等。這些注解既可用於Spring MVC也可用於 Portlet MVC。這種方式無需繼承指定基類或者實現指定接口。此外,無需依賴`Servlet `API或者`Portlet `API,但是可以非常方便的訪問他們。
大量的web都是使用注解的,比如* MvcShowcase, MvcAjax, MvcBasic, PetClinic, PetCare*等等,不信你看https://github.com/spring-projects/
@Controller
public class HelloWorldController { @RequestMapping("/helloWorld") public String helloWorld(Model model) { model.addAttribute("message", "Hello World!"); return "helloWorld"; } }
@Controller
和@RequestMapping
注解不對方法的名和簽名做限制,你可以隨意,呃,注意要符合java規范。示例中的方法接受了一個Model
,返回了一個字串view,除此之外Spring還提供了很多的方法參數、返回值,預知詳情,請關注文檔更新進度。@Controller
和@RequestMapping
還有很多注解構成了Spring MVC實現,本章將詳細講解他們在Servlet環境中的用法。
使用@Controller定義一個controller
`@Controller`注解的類意味着該類是MVC中的C角色,無需繼承C角色積累,也無需引用ServletAPI*譯注,比如HttpServletReqeust,HttpServletResponse*,若有需要,也能非常容易的引用ServletAPI。
@Controller
注解表示類作為conroler控制器代碼層,dispatcher掃描@Controller注解類並探測@RequestMapping
注解然后做映射。
可以在 dispatcher’s上下文環境中,明確的定義注解了@Controller
類的Spring bean。然而,使用@Controller
注解的類,可自動探測並自動注冊。
為了開啟自動探測,得在配置中增加組件掃描功能。在XML 中使用spring-context
schema:
<?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>
使用@RequestMapping做映射
`@RequestMapping`注解可以將URL映射為類入口或者方法入口,比如URL `/appointments`由`AppointmentsController `類處理。通常,類級別注解`@RequestMapping`映射reqeust路徑,方法級別`@RequestMapping`用來指明要處理reqeust 路徑下的哪些HTTP方法,比如POST/GET/PUT/DELETE等等,或者是指明要處理哪些參數條件。 看樣例: ```java @Controller //所有"/appointments"路徑請求由本controller處理 @RequestMapping("/appointments") public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
// HTTP GET方法請求的"/appointments"由此方法處理
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(value="/{day}", method = RequestMethod.GET)
//URI模板模式
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(value="/new", method = RequestMethod.GET)
//處理HTTP GET方法請求的"/appointments/new"
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
// HTTP POST方法請求的"/appointments"由此方法處理
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
上例中多處使用了`@RequestMapping`。第一處是類注解,不翻了,全寫注釋里了。
`getForDay()`方法展示了`@RequestMapping`另一種用法:URI模板,詳情參看[mvc-ann-requestmapping-uri-templates](#mvc-ann-requestmapping-uri-templates)
`@RequestMapping`類注解不是必須的。若不寫的話,不利於路徑規划。看樣例:
```java
@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());
}
}
上例中未指定GET vs. PUT, POST等方法,因為默認情況下,@RequestMapping
將會處理相關路徑下的所有的HTTP方法。使用這種@RequestMapping(method=GET)
方式才能精准的映射。
@Controller和AOP代理
有些情況下,controller也許會有AOP代理裝飾。比如,在controller上直接定義`@Transactional`注解。這種情況,推薦使用類注解。然而,如果controller需要實現一個非Spring 回調接口(也就是`InitializingBean, *Aware`等等),則需要明確的配置基於類的代理。比如,使用了``就得改為``。
Spring 3.1中為@RequestMapping方法新增的支持類
Spring 3.1 introduced a new set of support classes for @RequestMapping methods called RequestMappingHandlerMapping and RequestMappingHandlerAdapter respectively. They are recommended for use and even required to take advantage of new features in Spring MVC 3.1 and going forward. The new support classes are enabled by default by the MVC namespace and the MVC Java config but must be configured explicitly if using neither. This section describes a few important differences between the old and the new support classes.
Prior to Spring 3.1, type and method-level request mappings were examined in two separate stages — a controller was selected first by the DefaultAnnotationHandlerMapping and the actual method to invoke was narrowed down second by the AnnotationMethodHandlerAdapter.
With the new support classes in Spring 3.1, the RequestMappingHandlerMapping is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level @RequestMapping information.
This enables some new possibilities. 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.
There are also several things no longer possible:
Select a controller first with a SimpleUrlHandlerMapping or BeanNameUrlHandlerMapping and then narrow the method based on @RequestMapping annotations. 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. 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. The above features are still supported with the existing support classes. However to take advantage of new Spring MVC 3.1 features you’ll need to use the new support classes.
URI模板模式
*URI模板*大大的方便了@RequestMapping方法中URL配置。 URI 模板是類URI字串,包含一個或多個變量名,為變量設置值時,它就成了URI。在[proposed RFC](http://bitworking.org/projects/URI-Templates/)中定義了是如何參數化的。比如,URI模板`http://www.example.com/users/{userId}`包含一個變量userId,設置userId變量的值為*fred*,`http://www.example.com/users/fred`。
在方法參數上使用 @PathVariable
注解,將會綁定URI中變量的值到參數上:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner"; }
URI模板/owners/{ownerId}
聲明了變量ownerId
。當ctroller處理該request時候,Spring MVC將會從請求路徑中取出ownerId變量值,將變量值綁定到參數上。比如,請求/owners/fred
時,變量ownerId就是fred
。
在處理
@PathVariable
過程中,Spring MVC 以 by name方式從URI模板中匹配變量,@PathVariable
可以指定name:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted }
如果URI模板中變量name和方法參數name相同,則無需配置@PathVariable的name。只要是編譯時未去除調試信息,Spring MVC就能匹配與參數重名的URI模板變量:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable String ownerId, Model model) { // implementation omitted }
一個方法可以有多個@PathVariable
@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET) 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"; }
當@PathVariable
注解用於Map<String, String>
參數,所有的URI 模板中的變量都會置入map。
URI模板可以是@RequestMapping
類注解和方法注解的合集,比如URL /owners/42/pets/21
將會調用findPet()
方法
@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 自動轉換合適的類型,轉換不成功時,拋異常TypeMismatchException
。不過,可以注冊自定義轉換器。詳情參看the section called “Method Parameters And Type Conversion” and the section called “Customizing WebDataBinder initialization”.
URI 模板模式與正則表達式
有時需要對URI模板變量進行精准的控制,比如`"/spring-web/spring-web-3.0.5.jar"`如何定義各個部分變量 ` @RequestMapping`支持URI模板變量中正則表達式,`{varName:regex}`語法,前面是變量名字,后面是正則表達式。 ```java @RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String extension) { // ... } } ```
路徑模式
`@RequestMapping`也支持ant風格路徑,比如`/myPath/*.do`。URI模板變量和ant風格路徑也可以組合使用,`/owners/*/pets/{petId}`。
路徑模式比較
當URL匹配了多種模式,各種模式會有一個匹配度的排序,也就是說哪些模式更精准的匹配,就會選用哪些模式。
具有較低計數量的模板變量和通配符更特殊、更具體。比如,/hotels/{hotel}/*
比/hotels/{hotel}/**
更具體、更優先。
如果兩個模式具有相同的計數量,俺么更短的勝出。比如,/foo/bar*
比/foo/*
更長,因此更具體
當兩個模式具有相同的計數和長度,具有更少的通配符的勝出。/hotels/{hotel}
比/hotels/*
更優先。
還有一些其他的規則
- 默認的映射模式
/**
比任何其他模式都具有更低的優先級,比如,/api/{a)}/{b}/{c}
與之相比更具體更優先 - 前綴模式
/public/**
比其他任何沒有雙通配符的模式具有更低的優先級,比如,/public/path3/{a}/{b}/{c}
比它更具體更優先
更多詳情參看AntPathMatcher
中的AntPatternComparator
。注意PathMatcher 可以自定義,參看 Section 20.16.9, “Path Matching”
路徑模式和占位符
`@RequestMapping`注解支持${...}占位符,占位符讀取的是本地properties、系統properties、環境變量。若是需要根據配置文件來改變controller映射路徑,此辦法就可以大顯身手了。占位符的更多細節,請參看`PropertyPlaceholderConfigurer`類的javadocs。
前綴匹配路徑模式
Spring MVC默認執行`".*"`前綴模式匹配,因此`/person`也會匹配`/persion.*`。這樣就可以通過文件擴展名指明內容類型,比如`/pserson.pdf`,`/person.xml`等等。 由此產生一個讓人困惑的地方,就是當路徑最后的部分是一個URI變量,比如`/persion/{id}`。當request請求`/psersion/1.json`,既能匹配路徑變量 id=1 ,也能匹配擴展名".json",當id包含一個點的時候,比如`/person/joe@email.com`,spring將不會認為`joe@email.com`是id,但是,`com`不是文件擴展名。
要解決此問題,得配置Spring MVC 的前綴模式匹配和注冊的文件擴展名協商處理 。For more on this, first see Section 20.16.4, “Content Negotiation” and then Section 20.16.9, “Path Matching” 展示了如何開啟前綴匹配和如何只用注冊的前綴匹配。
Matrix Variables矩陣變量
URI規范,是在路徑中可能含有鍵值對。在規范中並未包含特殊項。SpringMVC就能搞這些特殊項。 矩陣變量可以出現在任意路徑中,每一個矩陣變量有";"分號分隔。比如: `"/cars;color=red;year=2012"`,多個值的話使用","逗號分隔,`"color=red,green,blue"`,或者使用重復的變量名`"color=red;color=green;color=blue"`。
If a URL is expected to contain matrix variables, the request mapping pattern must represent them with a URI template. This ensures the request can be matched correctly regardless of whether matrix variables are present or not and in what order they are provided.
下例演示解析矩陣變量"q":
// GET /pets/42;q=11;r=22 @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
因為路徑中的任意段都可能會包含矩陣變量,有些場景下你需要更特殊的用法去識別變量:
// GET /owners/42;q=11/pets/21;q=22 @RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable(value="q", pathVar="ownerId") int q1, @MatrixVariable(value="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 }
矩陣變量也可以定義為可選,並設置默認值
// GET /pets/42 @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1 }
矩陣變量可以置入一個Map:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable Map<String, String> matrixVars, @MatrixVariable(pathVar="petId"") Map<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 11, "s" : 23] }
注意,若要開啟矩陣變量功能,必須設置RequestMappingHandlerMapping
的屬性removeSemicolonContent
為false
。該值默認為true
。
MVC Java config and the MVC namespace都提供了開啟矩陣變量的選項。
若是Java config,Advanced Customizations with MVC Java Config章節講解了如何設置RequestMappingHandlerMapping
.
若是MVC namespace命名空間,<mvc:annotation-driven>
元素的enable-matrix-variables
屬性則應該設置為true
。默認他是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>
消費媒體類型
可以設置映射的消費媒體類型,類型可以指定多個。那么,只有reqeust的*Content-Type*請求頭匹配映射中設置的消費類型,才會由mapping映射處理該request。 ``` @Controller @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json") public void addPet(@RequestBody Pet pet, Model model) { // implementation omitted } ```
消費類型可以使用非!運算表達式,!text/plain,意思是除了text/plain類型,其他所有的類型都可以匹配。
消費類型條件支持方法映射中配置,也支持類映射中配置。一般情況下,在類注解和方法注解中配置了條件,方法注解中的條件將會覆蓋類注解中的條件,但是,消費類型條件是繼承、擴展。
生產媒體類型
和消費媒體類型差不多。如果*Accept *reqeust header匹配了配置的生產媒體類型,則@RequestMapping處理request。 ```java @Controller @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json") @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // implementation omitted } ``` 非運算符也支持。 方法注解中的生產媒體類型配置也是I擴展類注解中生產媒體配置。
Reqeust參數和Header Value
使用參數條件,可reqeust參數匹配更精准,比如`"myParam","!myParam",或者"myParam=myValue`,第一二個是檢查存在/不存在,第三個是為了檢查是否為指定值。參看樣例: ```java @Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
對於request header的存在/不存在,或者匹配是否為指定的值也可使用類似的方法:
```java
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
雖然可以用request header檢查Content-Type和Accept header的值(比如,"content-type=text/"匹配"text/plain"和"text/html"),但是推薦使用consumes和produces*條件檢查媒體類型相關header value,媒體類型條件檢查就是為了干這個用的。
定義@RequestMapping 處理方法
`@RequestMapping`方法非常靈活,幾乎不受任何限制。支持的方法參數和返回值類型,在下面詳述。除`BindingResult `類型參數外,大多數參數次序隨意。  > Spring 3.1引入了一組`@RequestMapping`方法的支持類,分別是`RequestMappingHandlerMapping`和`RequestMappingHandlerAdapter`。
支持的方法參數類型
系列是支持的方法參數 * ServletAPI中的Rquest或者Response對象。比如`ServletRequest`或者`HttpServletReqeust`。 * Session 對象:比如`HttpSession`。此類型的參數將會注入響應的session,因此,此參數永遠不為null。
Session訪問也許不是線程安全的,尤其是在Servlet花逆境中。加入允許多個Request可並發的訪問session,可考慮使用
RequestMappingHandlerAdapter
的"synchronizeOnSession"屬性為"true"
org.springframework.web.context.request.WebRequest
或者org.springframework.web.context.request.NativeWebRequest
- org.springframework.web.context.request.WebRequest or org.springframework.web.context.request.NativeWebRequest. Allows for generic request parameter access as well as request/session attribute access, without ties to the native Servlet/Portlet API.
- java.util.Locale for the current request locale, determined by the most specific locale resolver available, in effect, the configured LocaleResolver / LocaleContextResolver in an MVC environment.
- java.util.TimeZone (Java 6+) / java.time.ZoneId (on Java 8) for the time zone associated with the current request, as determined by a LocaleContextResolver.
- java.io.InputStream / java.io.Reader for access to the request’s content. This value is the raw InputStream/Reader as exposed by the Servlet API.
- java.io.OutputStream / java.io.Writer for generating the response’s content. This value is the raw OutputStream/Writer as exposed by the Servlet API.
- org.springframework.http.HttpMethod for the HTTP request method.
- java.security.Principal containing the currently authenticated user.
- @PathVariable annotated parameters for access to URI template variables. See the section called “URI Template Patterns”.
- @MatrixVariable annotated parameters for access to name-value pairs located in URI path segments. See the section called “Matrix Variables”.
- @RequestParam annotated parameters for access to specific Servlet request parameters. Parameter values are converted to the declared method argument type. See the section called “Binding request parameters to method parameters with @RequestParam”.
- @RequestHeader annotated parameters for access to specific Servlet request HTTP headers. Parameter values are converted to the declared method argument type. See the section called “Mapping request header attributes with the @RequestHeader annotation”.
- @RequestBody annotated parameters for access to the HTTP request body. Parameter values are converted to the declared method argument type using HttpMessageConverters. See the section called “Mapping the request body with the @RequestBody annotation”.
- @RequestPart annotated parameters for access to the content of a "multipart/form-data" request part. See Section 17.10.5, “Handling a file upload request from programmatic clients” and Section 17.10, “Spring’s multipart (file upload) support”.
- HttpEntity<?> parameters for access to the Servlet request HTTP headers and contents. The request stream will be converted to the entity body using HttpMessageConverters. See the section called “Using HttpEntity”.
- java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap for enriching the implicit model that is exposed to the web view.
- org.springframework.web.servlet.mvc.support.RedirectAttributes to specify the exact set of attributes to use in case of a redirect and also to add flash attributes (attributes stored temporarily on the server-side to make them available to the request after the redirect). RedirectAttributes is used instead of the implicit model if the method returns a "redirect:" prefixed view name or RedirectView.
- Command or form objects to bind request parameters to bean properties (via setters) or directly to fields, with customizable type conversion, depending on @InitBinder methods and/or the HandlerAdapter configuration. See the webBindingInitializer property on RequestMappingHandlerAdapter. Such command objects along with their validation results will be exposed as model attributes by default, using the command class class name - e.g. model attribute "orderAddress" for a command object of type "some.package.OrderAddress". The ModelAttribute annotation can be used on a method argument to customize the model attribute name used.
- org.springframework.validation.Errors / org.springframework.validation.BindingResult validation results for a preceding command or form object (the immediately preceding method argument).
- org.springframework.web.bind.support.SessionStatus status handle for marking form processing as complete, which triggers the cleanup of session attributes that have been indicated by the @SessionAttributes annotation at the handler type level.
- org.springframework.web.util.UriComponentsBuilder a builder for preparing a URL relative to the current request’s host, port, scheme, context path, and the literal part of the servlet mapping.
The Errors or BindingResult parameters have to follow the model object that is being bound immediately as the method signature might have more that one model object and Spring will create a separate BindingResult instance for each of them so the following sample won’t work:
**Invalid ordering of BindingResult and @ModelAttribute. **
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
Note, that there is a Model parameter in between Pet and BindingResult. To get this working you have to reorder the parameters as follows:
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
JDK 1.8’s java.util.Optional is supported as a method parameter type with annotations that have a required attribute (e.g. @RequestParam, @RequestHeader, etc. The use of java.util.Optional in those cases is equivalent to having required=false.
支持的返回值類型
支持下列返回值: * A ModelAndView object, with the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods. * A Model object, 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. * A Map object 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. * A View object, 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). * A String value that is interpreted as the 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 if the method handles the response itself (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse for that purpose) or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature). * If the method is annotated with @ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters. See the section called “Mapping the response body with the @ResponseBody annotation”. * An HttpEntity or ResponseEntity object to provide access to the Servlet response HTTP headers and contents. The entity body will be converted to the response stream using HttpMessageConverters. See the section called “Using HttpEntity”. * An HttpHeaders object to return a response with no body. * A Callable can be returned when the application wants to produce the return value asynchronously in a thread managed by Spring MVC. * A DeferredResult can be returned when the application wants to produce the return value from a thread of its own choosing. * A ListenableFuture can be returned when the application wants to produce the return value from a thread of its own choosing. * Any other return type is considered to be a single model attribute to be exposed to the view, using the attribute name specified through @ModelAttribute at the method level (or the default attribute name based on the return type class name). The model is implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
綁定request參數到方法參數上
`@RequestParam`注解可以綁定request參數到方法參數上。 看樣例: ```java @Controller @RequestMapping("/pets") @SessionAttributes("pet") public class EditPetForm {
// ...
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
使用次注解的參數,默認是必須的,但是可以設置參數為可選,設置`@RequestParam`的`required`屬性為`false`(比如:`@RequestParam(value="id",required=false)`)。
如果方法參數類型不是`String`,那么Spring將會自動進行類型轉換。詳情參看[the section called “Method Parameters And Type Conversion”](#mvc-ann-typeconversion).
當`@RequestParam`注解用在`Map<String,String>`或者`MultiValueMap<String,Strng`參數上,那么所有的reqeust 參數都將會綁定到map中。
<h5 id='mvc-ann-requestbody'>使用注解@RequestBody映射request body</h5>
`@RequestBody`方法參數注解表名,HTTP request body綁定到方法參數值上,看樣例:
```java
@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
可以使用HttpMessageConverter
轉換request body到方法參數上,HttpMessageConverter
負責將HTTP reqeust消息轉換成為對象,也負責將對象轉換成HTTP response body。RequestMappingHandlerAdapter
支持@RequestBody
注解和下列默認的HttpMessageConverters
ByteArrayHttpMessageConverter
轉換字節數組StringHttpMessageConverter
轉換字串FormHttpMessageConverter
負責form表單數據與MultiValueMap<String,String>之間的轉換SourceHttpMessageConverter
負責XML與Source之間的互相轉換
詳情請參看Message Converters。同事也得注意,如果使用了MVC命名空間或者MVC Java config,默認會注冊很多message converter。詳情參看Section 20.16.1, “Enabling the MVC Java Config or the MVC XML Namespace”
若需要讀寫XML,得使用MarshallingHttpMessageConverter
配置指定的Marshaller
和Unmarshaller
實現,詳情參看org.springframework.oxm
包。 看樣例代碼
<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
,這樣,他就會被配置的Validator
實例校驗。Spring MVC會自動配置一個JSR-303 validator。
就像@ModielAttribute
參數,Errors
參數可用於校驗errors。如果未聲明該參數,則會拋出MethodArgumentNotValidException
異常。異常有DefaultHandlerExceptionResolver
處理,它會發送400
error給客戶端。
通過MVC命名空間或者MVC java config配置message轉換和配置validator,詳情請參看Section 17.16.1, “Enabling the MVC Java Config or the MVC XML Namespace”
通過@ResponseBody注解映射response body
`@ResponseBody`注解與`@RequestBody`相似。該注解是一個方法注解,作用是將返回值直接寫入HTTP response流。(既不是Model,也不是視圖),比如: ```java @RequestMapping(value = "/something", method = RequestMethod.PUT) @ResponseBody public String helloWorld() { return "Hello World"; } ```
上例中,將Hello World
字串直接寫入到response stream響應流中。
使用@RequestBody
時,Spring使用HttpMessageConverter
轉換對象為response body響應體。詳情參看Message Converters
使用@RestController注解創建REST Controller
Controller實現REST API非常常用,REST 風格Ctroller僅能提供JSON、XML、自定義MediaType content媒體類型。使用`RESTController`類注解,可以非常方便的實現RESTful風格API,用來替代`@RequestMapping`和`@Responsebody`的配合。
@RestController
是@ResponseBody
和@Controller
注解的組合,該注解具有擴展性,也許在將來的發布版中會增加額外的功能。
作為常規@Controller
,@RestController
通常配合@ControllerAdvice
使用。詳情請參看 the section called “Advising controllers with the @ControllerAdvice annotation”
使用HttpEntity
`HttpEntity`與`@RequestBody`、`@Resonsebody`非常像。除了訪問request和response body,`HttpEntity`(和response專用子類`ResponseEntity`)也可以訪問request和response的headers,像這樣: ```java @RequestMapping("/something") public ResponseEntity handle(HttpEntity requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader")); byte[] requestBody = requestEntity.getBody();
// 用request header和 body干一些事兒
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
上面的樣例中獲取了request header中`MyRequestHeader`的值,讀取body置入一個byte array數組。將`MyResponseHeader `設置如了response,將字串`Hello World`寫入的response流中,並設置了response的status code為201(Created)
如同`@RequestBody`和`@ResponseBody`,Spring使用`HttpMessageConverter `進行request和response流之間轉換。詳情請參看[Message Converters.](#rest-message-conversion)
<h5 id='mvc-ann-modelattrib-methods'>在方法上使用@ModelAttribute</h5>
`@ModelAttribute`注解可以用在方法上或者在方法參數上。本章將方法上注解的用法,下章講解方法參數上注解的用法。
方法上注解`@ModelAttribute`則表示方法的目的是增加一個或者多個model attributes。所有`@RequestMapping`注解方法支持的參數類型,`@ModelAttribute`注解的方法都支持,但是`@ModelAttribute`方法不能直接從request映射。在同一controller中`ModelAttribute`方法會在`@RequestMapping`方法之前調用。看看它的用法
```java
// 增加單個 attribute
//方法的返回值將添加到 model中,key鍵是"account"
//也可以自定義key鍵,如此@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
方法主要的應用場景,將常用的attribute屬性填充到model模型中,比如填充下拉菜單項、寵物類型、或者檢索常用對象(像Account)用於在HTML表單中描述數據。后面的用法在下章中討論。
@ModelAttribute
注解方法有2中寫法,第一種,方法通過返回值給model增加了一個attribute,,第二種,方法接收一個Model
並且增加了多個model attribute。根據實際情況選擇使用。
單個Controller可以有多個@ModelAttribute
方法。在同一controller中的@RequestMapping
方法調用之前,會執行所有的@ModelAtrribute
方法。
@ModelAttribute
方法也可以定義在@ControllerAdvice
注解的類中,這樣的方法則會應用於多個controller。詳情才看 the section called “Advising controllers with the @ControllerAdvice
annotation”
若未明確指定model attribute name時候,Spring會怎么干?這種情況下,默認會基於其類型指定其name。比如,如果方法返回的對象類型為
Account
,默認名字是account
。可以通過指定@ModelAttribute
注解的value來為其指定name。如果直接給Model
添加attributes,得使用合適的重載方法addAttribute(..)
,也就是有或者沒有指定attribute name。
@ModelAtribute
注解也可以在@RequestMapping
方法上使用。這種情況下,@RequestMapping
方法返回值將會作為model attribute,而不是作為視圖name。視圖name來自於視圖name命名慣例,就像是void方法。詳情參看Section 17.13.3, “The View - RequestToViewNameTranslator”.
在方法參數上使用@ModelAttribute
前面提到`@ModelAttribute`可用於方法,也可用於方法參數上。本章講解在方法參數上的用法。
在方法參數上注解@ModelAttribute
則表明參數應該從model中檢索出。 若model中不存在,參數將會被實例化並增加到model中。若存在於model中,Spring將會給參數的field域賦值,值來自於從request中解析出相匹配的值。這就是Spring MVC 中的數據綁定,它是非常有用的機制,將使你免於手動從form field表單域中逐一的取值。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { }
上例中Pet的實例從哪兒來?有以下來源:
- 或許使用了
@SessionAttributes
,則Pet實例已經在model中了。詳情參看the section called “Using @SessionAttributes to store model attributes in the HTTP session between requests” - 或許在同一個controller中使用了
@ModelAttribute
注解的方法,所以Pet實例已經存在於model中了,詳情參看上一章 - 來自於Spring數據綁定,使用URI模板變量配合轉換器方式獲取,下面詳細講述。
- 通過Pet空構造獲取實例
@ModelAttribute
注解方法是從數據庫檢索attribute的常用手段,也可以使用@SessionAttribute
將數據存儲於session中。在某些場景中,可以非常方便的通過URI template variable模板變量配合類型轉換器的方式來檢索attribute。看樣例:
@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT) public String save(@ModelAttribute("account") Account account) { }
model attribute的name(也就是"account")匹配URI template variable模板變量。如果你注冊了Converter<String, Account>
,該轉換器的實現能把String字串
accout變成Account
實例(譯注,比如接收一個主鍵,從數據庫中根據主鍵查出數據置入Account類實例),那么上例中的方法將不需要@ModelAttribute
注解也可以工作。
接下來是數據綁定。WebDataBinder
類解析request中參數的名字(包括query字串參數和form field表單域),並通過name匹配model attribute的field域。經過必要的類型轉換之后(將String轉換成目標域類型),匹配的域將會賦值。數據綁定和驗證在Chapter 7, Validation, Data Binding, and Type Conversion講解。controller級別的自定義數據綁定處理在the section called “Customizing WebDataBinder initialization”.講解。
數據綁定處理時候,也許會報錯,比如必須的field域未找到、類型轉換錯誤等。若要檢查這些錯誤,可在@ModelAttribute
參數后緊接着增加一個BindingResult
參數。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
使用BindingResult
時,可以探測到error,當發現error時,通常會跳轉到統一的視圖,即異常頁,Spring的<errors>
form 標簽可以渲染此類信息。
在數據綁定處理時,可以使用同一個BindingResult
調用自定義的validator校驗器,BindingResult
用於記錄數據綁定error。數據綁定和校驗errors能夠在同一個地方累計然后一並返回給user:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { new PetValidator().validate(pet, result); if (result.hasErrors()) { return "petForm"; } // ... }
或者,還可以使用JSR-303@Valid
注解自動校驗:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
詳情參看See Section 7.8, “Spring Validation” and Chapter 7, Validation, Data Binding, and Type Conversion
使用@SessionAttribute在HTTP會話中存儲model attribute
類注解`@SessionAttributes`聲明用於指定handler的session attributes。一般情況下,聲明用於存儲在session中的model attribute的name或type,用於在接下來的request中傳遞數據。
看樣例,指定一個model attribute name:
@Controller
@RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
指定redirect和flash屬性
默認情況下,在redirect URL中,所有的model attribute都應該作為URI template variable模板變量暴露。上下的屬性,包括原始類型、原始類型的集合/數組自動拼接到query 參數中。
在controller中,也許會包含額外的attribute,主要是用於渲染的目的(比如下拉菜單)。在redirect重定向場景中,為了精准控制,可通過在@RequestMapping
方法中聲明一個RedirectAttribute
類型的參數,使用該參數在RedirectView
中增加attribute。此時,若controller方法發生redirect重定向,RedirectAttributes
的內容就會被使用,否則,將使用Model
的內容。
RequestMappingHandlerAdapter
提供一個標志位"ignoreDefaultModelOnRedirect"
,表示默認的Model
內容在controller 方法redirect重定向時不會被使用。同時,controller方法應該聲明一個RedirectAttributes
類型的attribute,否則,將沒有attribute傳遞給RedirecView
。MVC xml配置和MVC Java config中,該值默認都是false
,這是為了向后兼容。當然了,新的應用中還是推薦設置為true
。
RedirectAttributes
接口也可用於flash attribute。不像其他redirect屬性那樣,拼接到redirect URL后,flash attribute會保存在HTTP session中(因此無需再URL中出現)。當flash attribute從session中移除時候,controller的model模型將為目標redirect URL自動接收這些flash attribute。詳情參看 Section 17.6, “Using flash attributes”
使用"application/x-www-form-urlencoded"數據
之前講解了`@ModelAttribute`,用於支持從瀏覽器客戶端發出表單提交request請求。它也支持非瀏覽器提交的request。當使用HTTP PUT request請求,情況則大不相同。瀏覽器可通過HTTP GET或者HTTP POST提交表單數據。非瀏覽器客戶端則可通過HTTP PUT提交表單數據。因為Servlet規范要求`ServletRequest.getParameter*()`系列方法只能通過HTTP POST訪問表單數據,而不是HTTP PUT,呃,處理起來有點困難。
為了支持HTTP PUT和PATCH request請求,spring-web
模塊提供了HttpPutFormContentFilter
過濾器,在web.xml
中配置
<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攔截了內容類型為application/x-form-urlencoded
的HTTP/PUT/PATCH的request,從request body讀取form數據,並且封裝ServletRequest
,這樣可以通過ServletRequest.getParameter*()
系列方法解析form數據。
http://docs.spring.io/autorepo/docs/spring/current/spring-framework-reference/html/images/note.png
由於
HttpPutFormContentFilter
消費了request body,所以不應該在給PUT和PATCH的URL配置處理器,比如依賴消費媒體類型application/x-www-form-urlencoded
配置的轉換器。它包括了@RequestBody MultiValueMap<String, String> and HttpEntity<MultiValueMap<String, String>>
使用@CookieValue注解映射cookie值
`@CookieValue`注解作用是將HTTP cookie值綁定到方法參數上。 假設,有一HTTP request攜帶了以下cookie JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 如何使用該注解綁定cookie值呢: ```java @RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) { //... } ``` 若方法參數類型不是`String`,則會自動進行類型轉換。 See[ the section called “Method Parameters And Type Conversion”. ](#mvc-ann-typeconversion)
在Servlet和Portlet環境中的注解handler處理器方法上支持此注解
@RequestHeader注解綁定header屬性
`@RequestHeader`注解作用是將request header綁定到方法參數上。
現在有一個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
,則會自動進行類型轉換。 See the section called “Method Parameters And Type Conversion”.
Spring MVC 類型轉換支持逗號分隔的字串轉為array/List。 比如使用
@RequestHeader("Accept")
注解的方法參數不僅可以String
類型,也可以是String[]
或者List<String
在Servlet和Portlet環境中的注解handler處理器方法上支持此注解
方法參數和類型轉換
從request parameters參數,path variable路徑變量,request header頭,cookie value餅干值中解析出的字串值,也許需要類型轉換,轉換相對應的綁定的方法參數的類型、或者是域類型(比如,將request parameter構造為`@ModelAttribute`)。如果需要綁定的對象類型不是 `String`,Spring會自動進行類型轉換。支持所有的簡單類型的轉換,比如int,long,Date等等。可以通過`WebDataBinder`更進一步的自定義轉換過程(see the [section called “Customizing WebDataBinder initialization](#mvc-ann-webdatabinder)”),或者是通過使用`FormattingConversionService `注冊`Formatters`(詳情參看"[Spring Field Formatting](#format)")
自定義WebDataBinder 初始化
使用Spring的`WebDataBinder`自定義request parameter參數綁定到PropertyEditors,可以在controller內方法上使用`@InitBinder`注解,或者是在`@ControllerAdvice`注解的類上使用`@InitBinder`注解方法,或者是提供自定義`WebBindingInitializer`。詳情參看 [“Advising controllers with the `@ControllerAdvice` annotation”](#mvc-ann-controller-advice) section for more details.
使用@InitBinder自定義數據綁定
使用`@InitBinder`注解controller的方法是一種在controller類內直接配置數據綁定的方式。`@InitBinder`注解的方法會初始化`WebDataBinder`,該`WebDataBinder`用於數據綁定,將從request解析出來的值填充到相應的綁定對象上。這種init-binder方法不支持command/form 命令/表單對象和相應的驗證結果對象,其他的`@Requestmapping`方法的所有的參數都支持。init-binder方法一定不能有返回值,即void方法。通常,參數包括`WebDataBinder`,該參數可以組合`WebRequest`或者`Java.util.Locale`,允許注冊context-specific editors上下文編輯器。
下例中使用@InitBinder
為所有的Java.util.Date
表單屬性配置了CustomeDateEditor
:
@Controller
public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... }
配置自定義WebBindingInitializer
為了更具體的控制數據綁定初始化,可以提供一個自定義的`WebBindingInitializer`接口實現,該實現設置給一個自定義bean`AnnotationMethodHandlerAdapter`,覆蓋其默認配置。
下面的樣例摘子PetClinic 應用,講解如何使用自定義的WebBindingInitializer
接口實現做配置,org.springframework.samples.petclinic.web.ClinicBindingInitializer
類配置了一些PetClinic controller需要使用的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
方法也可以在@ControllerAdvice
注解的類中定義,在這種情況下,@InitBinder
會應用於所有匹配上的controller。這是WebBindingInitializer
的另一個應用場景。詳情參看 the section called “Advising controllers with the @ControllerAdvice annotation” section for more details.
支持Response Header Last-Modified以實現緩存
`@RequestMapping`也許是想要支持HTTP Request的頭`Last-Modified`,因為在Servlet API中定義了`getLastModified`方法,以便緩存內容。它會計算request的latModified最后編輯的`long`值,與Request Header屬性`If-Modified-Since`做對比,如果為發生變化,則會返回304。參看代碼: ```java @RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) {
long lastModified = // 1. 應用指定的最后編輯時間
if (request.checkNotModified(lastModified)) {
// 2. 如果在request在有效期內,則直接返回,無需處理
return null;
}
// 3. 顯然已經過期了,此時執行真正的業務邏輯,為視圖准備數據
model.addAttribute(...);
return "myViewName";
}
有兩點要注意:`request.checkNotModified(lastModified)`和返回`null`。`request.checkNotModified(lastModified)`在返回`true`之前將response狀態設置為304。后者,返回`null`值,配合前者,會使Spring MVC不對request做進一步處理。
<h5 id='mvc-ann-controller-advice'>使用`@ControllerAdvice`注解增強controllers</h5>
`@ControllerAdvice`注解允許實現類通過classpath掃描方式自動探測。當使用MVC命名空間(即XML配置)或者MVC Java config時候,它會自動開啟。
`@ControllerAdvice`注解的類,能包含`@ExceptionHandler, @InitBinder, and @ModelAttribute`注解的方法,這些方法將會應用於所有匹配上的controller.
`@ControllerAdvic`也可以指定聲明應用范圍
```java
// 應用於所有使用了 @RestController注解的controller
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}
//應用於指定包內的所有controller
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}
// 應用於指定的類
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}
更多詳情請參看java doc文檔
Jackson 序列化視圖的支持
有時需要將對象序列化成HTTP response body。Spring支持此功能,內置了JSON視圖[Jackson's Serialization Views](http://wiki.fasterxml.com/JacksonJsonViews)就是干這個用的。
JSON視圖需要在controler的方法上增加@ResponseBody
注解或者令該方法返回ResponseEntity
類型值,@JsonView
用法非常簡單,只需設置該注解的一個參數用於指定view class或者view 接口即可。
@RestController
public class UserController { @RequestMapping(value = "/user", method = RequestMethod.GET) @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
可以指定多個類,但是在controller方法上的@JsonView
只支持指定一個類。要是非得指定多個類,可通過組合接口實現。
若方法只能返回視圖解決方案??,則只需在model中的指定視圖序列化類:
@Controller
public class UserController extends AbstractController { @RequestMapping(value = "/user", method = RequestMethod.GET) 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的支持
為了讓`ResponseBody`和`ResponseEntity`方法支持[JSONP](http://en.wikipedia.org/wiki/JSONP),需要聲明一個`@ControllerAdvice`bean,該bean繼承自`AbstractJsonpResponseBodyAdvice`,需要在構造參數中指明JSONP query 參數名: ```java @ControllerAdvice public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}
若是依賴視圖解決方案,當request query參數名中含有`jsonp`或者`callback`參數時,JSONP將自動開啟, query參數名也可以通過`jsonpParameterNames`屬性自定義。
<h4 id='mvc-ann-async'>異步請求處理</h4>
Spring MVC 3.2增加了基於Servlet 3的異步請求處理。可啟動單獨線程,並返回`java.util.concurrent.Callable`類型值,此時,Servlet容器主線程可以處理其他request。Spring MVC在單獨的線程內使用`TaskExecutor`調用`Callable`,當`Callable`返回時,Servlet容器將該值返回給相應的request並恢復該request進行處理。看樣例:
```java
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
//TODO,查看Spring MVC主Servlet容器的線程處理方式
還有一個選擇,使方法返回DeferredResult
實例。在這種情況下,return的值將會產生一個單獨的線程。當然了,線程並不能被Spring MVC感知。比如,返回值將會產生一個 外部事件,像JMS message,scheduled task等等。看樣例代碼:
@RequestMapping("/quotes") @ResponseBody public DeferredResult<String> quotes() { DeferredResult<String> deferredResult = new DeferredResult<String>(); // Save the deferredResult in in-memory queue ... return deferredResult; } // In some other thread... deferredResult.setResult(data);
當然了,如果沒有Servlet 3 異步處理相關支持,理解這些有些困難。看下面的知識點,有助於立即以上功能:
ServletRequest
通過request.startAsync()
方法切換為異步模式。如此做的主要影響,就是Servlet和Filters,退出的情況下保持response open,允許其他線程完成處理。- 調用
request.startAsync()
返回一個AsyncContext
,該實例可以進一步控制異步處理。比如,他能為方法提供dispatch
方法,應用線程中調用該方法則會將request dispatch分發回Servlet容器。異步dispatch 和 forward差不多,區別就是異步是應用線程分發給一個Servlet容器線程,forward是同一個容器線程中同步分發。 ServletRequest
提供了當前DispatcherType
的訪問,DispatcherType
能用來區別在request處理線程初始化時和處理異步dispatch時候,正在處理request的Servlet
還是Filter
下面講解使用Callable
異步request處理的事件次序:(1)Controller返回一個Callabel,(2)Spring MVC啟動啟動異步處理線程,並給TaskExecutor
提交一個Callable
(3)DispatcherServlet
和所有的Filter退出request處理線程,但是response保持打開,(4)Callable
產生一個result,Spring MVC分發request返回給Servlet容器,(5)DispatcherServlet
再次調用並處理由Callable
產生的異步結果。(2), (3), and (4)的執行速度依賴於當前的線程。
使用DeferredResult
進行異步request處理的事件的次序上上面的次序差不多。不過有以下不同之處:(1)Controller返回DeferredResult
並將其保存在可訪問的內存隊列中。(2)Spring MVC開始異步處理。(3)DispatcherServlet
和所有配置的Filter完成request處理線程但是response保持打開,(4)應用從一些線程中設置DiferredResult
,Spring MVC分派request回Servlet 容器(5)DispatcherServlet
再次調用並處理異步產生的result。
本章講解了異步request處理的主要機制,如何使用和為何使用不在本章講解范圍。詳情參看這些博客
異步處理的異常處理
如果controller方法中返回`Callable`時拋出異常,將會發生什么?效果與任意controller方法拋異常差不多。交由同controller中`@ExceptionHandler`注解方法處理,或者是配置的`HandlerExceptionResolver`實例處理
在服務器端,
Callable
拋異常時,SPring MVC依舊會將異常分派給Servlet 容器處理。唯一不同的地方就是執行Callable
的結果是一個Exception
,該異常必須使用配置好的HandlerExceptionResolver
實例處理。
當使用DeferredResult
時,還有一個選擇,通過調用setErrorResult(Object)
並且提供Exception
或者其他任意對象作為返回結果。如果返回結果是一個Exception
,他將會使用同controller中注解@ExceptionHandler
的方法處理,或者是配置的HandlerExceptionResolver
實例處理。
異步request 攔截
一個已存在的`HandlerInterceptor `能實現`AsyncHandlerInterceptor`,該接口提供了額外的方法`afterConcurrentHandlingStarted`。在異步處理開始之后和request處理線程初始化完畢之前調用。詳細信息參看`AsyncHandlerInterceptor `javadoc文檔
Further options for async request lifecycle callbacks are provided directly on DeferredResult, which has the methods onTimeout(Runnable) and onCompletion(Runnable). Those are called when the async request is about to time out or has completed respectively. The timeout event can be handled by setting the DeferredResult to some value. The completion callback however is final and the result can no longer be set.
Similar callbacks are also available with a Callable. However, you will need to wrap the Callable in an instance of WebAsyncTask and then use that to register the timeout and completion callbacks. Just like with DeferredResult, the timeout event can be handled and a value can be returned while the completion event is final.
You can also register a CallableProcessingInterceptor or a DeferredResultProcessingInterceptor globally through the MVC Java config or the MVC namespace. Those interceptors provide a full set of callbacks and apply every time a Callable or a DeferredResult is used.
Configuration for Async Request Processing
Servlet 3 Async Config
To use Servlet 3 async request processing, you need to update web.xml to version 3.0:
<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">
...
The DispatcherServlet and any Filter configuration need to have the true sub-element. Additionally, any Filter that also needs to get involved in async dispatches should also be configured to support the ASYNC dispatcher type. Note that it is safe to enable the ASYNC dispatcher type for all filters provided with the Spring Framework since they will not get involved in async dispatches unless needed.
[Warning] Note that for some Filters it is absolutely critical to ensure they are mapped to be invoked during asynchronous dispatches. For example if a filter such as the OpenEntityManagerInViewFilter is responsible for releasing database connection resources and must be invoked at the end of an async request. Below is an example of a propertly configured filter:
<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>
If using Servlet 3, Java based configuration, e.g. via WebApplicationInitializer, you’ll also need to set the "asyncSupported" flag as well as the ASYNC dispatcher type just like with web.xml. To simplify all this configuration, consider extending AbstractDispatcherServletInitializer or AbstractAnnotationConfigDispatcherServletInitializer, which automatically set those options and make it very easy to register Filter instances.
Spring MVC Async Config
The MVC Java config and the MVC namespace both provide options for configuring async request processing. WebMvcConfigurer has the method configureAsyncSupport while mvc:annotation-driven has an sub-element.
Those allow you to configure the default timeout value to use for async requests, which if not set depends on the underlying Servlet container (e.g. 10 seconds on Tomcat). You can also configure an AsyncTaskExecutor to use for executing Callable instances returned from controller methods. It is highly recommended to configure this property since by default Spring MVC uses SimpleAsyncTaskExecutor. The MVC Java config and the MVC namespace also allow you to register CallableProcessingInterceptor and DeferredResultProcessingInterceptor instances.
If you need to override the default timeout value for a specific DeferredResult, you can do so by using the appropriate class constructor. Similarly, for a Callable, you can wrap it in a WebAsyncTask and use the appropriate class constructor to customize the timeout value. The class constructor of WebAsyncTask also allows providing an AsyncTaskExecutor.
測試Controllers
`Spring-test`模塊為注解Controller提供了一流的支持,[詳情參看section 11.3.6,"Spring MVC Test Framework"](#spring-mvc-test-framework)
Handler mappings處理映射
Spring老版本中,用戶需要配置一個或者多個`HandlerMapping`beans,配置在應用上下文中用於分派request到合適的處理器中。使用注解controller以后,基本不用如此麻煩了,因為`RequestMappingHandlerMapping`自動查找在`@Controller`注解的bean 中的`@RequestMapping`注解。當然了,要記得,所有的`Handlermapping`類繼承自`AbstractHandlerMapping`具有以下屬性,可以自定義其行為: * `interceptors` 攔截器列表。攔截器在以下章節中討論[Section 17.4.1, “Intercepting requests with a HandlerInterceptor”](#mvc-handlermapping-interceptor) * `defaultHandler`默認處理器。若沒有匹配的處理器,則使用默認處理器處理 * `order`order屬性值(詳情參看`org.springframework.core.Ordered`接口),Spring將上下文中可用的處理器handler mapping排序,使用排序次序第一的處理器 * `alwaysUseFullPath`。若是`true`,Spring 在當前Servlet上下文中使用全路徑查找合適的handler處理器。如果`false`(默認),則使用當前的Servlet mapping的相對路徑。比如,如果Servlet做了映射`/testing/*`和設置了`alwaysUseFullPath`屬性為`true`,將會使用`/test/viewPage.html`,如果該屬性設置為`false`,則會使用`/viewPage.html` * `urlDecode`默認是`true`,Spring 2.5以后。如果你需要比較編碼后的路徑,則設置其為`false`。然而,`HttpServletRequest`總是會暴露解碼后的Servlet 路徑。注意,編碼后的路徑將不能匹配Servlet路徑
下面樣例展示了如何配置攔截器:
<beans>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <bean class="example.MyInterceptor"/> </property> </bean> <beans>
使用HandlerInterceptor攔截request
Spring的處理器映射機制包含攔截器處理,攔截器可以針對某些request做特殊處理,比如校驗。
映射處理器中的攔截器得實現org.springframework.web.servlet
包中的HandlerIntercepter
接口。該接口定義了3個方法:preHandle(...)
方法在相應的處理器執行之前調用。postHandler(..)
在handler執行之后調用。afterCompletion(..)
方法在request完成之后執行。這三個方法應該為所有類型的預處理和后處理提供了足夠的彈性。
preHandle(..)
方法返回一個boolean值。該值將決定中斷或者繼續request處理。·true
時,handler執行鏈繼續;false
時,DispatcherServlet
將認為攔截器本身已經處理好了request(比如,渲染合適的視圖),不會繼續執行其他攔截器和handler。
使用intercepters
屬性配置攔截器,將會應用於所有繼承於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; } }
所有經由此映射處理的request都將被TimeBaseAccessInterceptor
攔截。如果當前時間不是工作時間,用戶獎杯重定向到一個靜態頁面。
當使用
RequestMappingHandlerMapping
時,實際的handler是一個HandlerMethod
實例,HandlerMethod
定義了指定的controller方法用於處理request。
如你所見,Spring適配器類HandlerIntercepttorAdapter
可以更容易的集成HandlerInterceptor
接口
上例中,配置的攔截器將會引用於所有的request,這些request使用注解的controller方法處理。如果你要使用攔截器精准攔截URL路徑,可以使用MVC命名空間(xml)或者MVC Java config,或者生命
MappedInterceptor
類型的Spring bean。詳情參看Section 17.16.1, “Enabling the MVC Java Config or the MVC XML Namespace”.
注意,postHandle
方法並不適用於@ResponseBody
和ResponseEntity
方法。這種情況下HttpMessageConverter
將會在postHandle
方法之前對response進行寫操作並提交response,這令攔截器將不能再改變response,比如,增加一個header。此時應該使用ResponseBodyAdvice
接口實現類,或者將其聲明為@ControllerAdvice
bean,或者將其配置在RequestMappingHandlerAdapter
上。
解析視圖
所有的MVC框架都有視圖處理,Spring 也不例外,Spring視圖技術可以在瀏覽器中渲染一個模型而不捆綁任何指定的視圖技術。開箱即用,Spring支持JSPs,Velocity和XSLT,詳情參看[Cahpter18,View technologies](http://docs.spring.io/autorepo/docs/spring/current/spring-framework-reference/html/view.html),該章討論了如何繼承和使用大量不同的視圖技術。
Spring處理視圖,有兩個核心接口ViewResolver
和View
。ViewResolver
提供了視圖邏輯名和物理名之間的映射。View
接口主要用於request的准備工作和使用多種視圖技術處理request。
使用ViewResolver接口解析視圖
上一章[Section 17.3, “Implementing Controllers”](#mvc-controller)討論的,所有的controller中handler方法必須解析一個邏輯試圖名,要么指定(返回`String`,`View`或者`ModelAndView`),或者使用約定俗成。Spring中的Views通過一個邏輯視圖名定位,通過視圖解析器解析.Spring 提供了大量的視圖解析器。看清單:
Table 17.3. View resolvers
解析器 | 描述 |
---|---|
AbstractCachingViewResolver |
緩存views。通常,views在使用之前需要提前准備;繼承此解析器將獲得緩存 |
XmlViewResolver |
ViewResolver 的實現,接受一個xml配置,xml的DTD和Spring的bean的相同。默認配置文件是/WEB-INF/views.xml |
ResourceBundleViewResolver |
ViewResolver 的實現,在ResourceBundle 中使用bean定義,通過綁定base name指定。通常在properties文件中定義bundle,位於classpath中。默認的文件是views.properties 。 |
UrlBasedViewResolver |
直接將邏輯名轉換為URL,無需明確映射定義。如果邏輯視圖名與視圖資源相同,則無需mapping映射。 |
InternalResourceViewResolver |
UrlBasedViewResolver 的子類,支持InternalResourceView (說白了,就是Servlets和JSPs)和JstlView 的子類和TilesView 的子類。通過該解析器的setViewClass(..) 方法為所有的視圖指定試圖類。詳情參看UrlBasedViewResolver 的javadocs |
VelocityViewResolver / FreeMarkerViewResolver |
UrlBasedViewResolver 的子類,支持VelocityView (就是Velocity templates)或者FreemarkerView ,也分別支持他們的子類 |
ContentNegotiatingViewResolver |
基於request額文件名或者Accept header解析視圖。詳情參看See Section 17.5.4, “ContentNegotiatingViewResolver”. |
As an example, with JSP as a view technology, you can use the UrlBasedViewResolver. This view resolver translates a view name to a URL and hands the request over to the RequestDispatcher to render the view.
比如,使用JSP這種視圖技術時,可以使用UrlBasedViewResolver
,視圖解析器將視圖名轉換為一個URL並將request傳給RequestDispatcher 用來渲染視圖。
<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
作為邏輯視圖名,那么視圖解析器會將request攜帶跳轉到/WEB-INF/jsp/test.jsp
,跳轉過程中經過RequestDispatcher
如果組合了不同的視圖技術,則可以使用ResourceBundleViewResolver
:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> <property name="defaultParentView" value="parentView"/> </bean>
ResourceBundleViewResolver
通過basename 檢查ResouceBundle
,會嘗試解析每一個視圖,使用propertiy值[viewname].(class)
作為視圖類,property值[viewname].url
作為視圖url。下一張中將會講解覆蓋視圖技術,那一張有講,定衣服父視圖,properties中定義的所有視圖都會“繼承”它。這種方式則可以指定默認視圖類。
AbstractCachingViewResolver
的子類緩存他們解析過的視圖實例。在某些視圖技術中,緩存能提高性能。可通過設置cache
屬性值為false
關閉緩存。此外,有時必須得刷新緩存,比如Velocity模板編輯過了,使用方法removeFromCache(String viewName,Local loc)
視圖解析鏈
Spring支持多視圖解析器。因此可以鏈式使用解析器,比如,某些環境下覆蓋某些視圖。在應用上下文中增加一個或者多個視圖解析器就構成了視圖解析鏈,還可以設置`order`屬性指定順序,在解析鏈中,`order`值越大,則位置越靠后。
下面栗子中,解析鏈由2個解析器組成,其一是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拋出異常ServletException
。
視圖解析器規范:視圖解析器返回null則表明視圖未找到。也有一些特殊情,解析器不會這么做,因為在某些情況下,視圖解析器不能明確視圖是否存在。比如,InternalResourceViewResolver
內部使用RequestDispatcher
,分發完才能確定JSP是否存在,但是視圖解析去在分發完成之后不能再次執行。VelocityViewResolver
也有同樣的問題。預知哪些視圖解析器會報告視圖不存在請參看javadocs。因此,將InternalResourceViewResolver
不放置到解析鏈末尾的話會造成檢測不完全,因為InternalResourceViewResolver
總是會返回一個視圖。
重定向
之前提到的,Controller大多數會返回一個邏輯視圖名, 由解析器使用相應的視圖技術解析該邏輯視圖名。比如,JSP是通過Servlet或者JSP引擎處理,這個工作是由`InternalResourceViewResolver`和`InternalResourceView`共同完成,他們通過Servlet API的`RequestDispatcher.forward(..)`方法或者`RequestDispatcher.include()`方法完成內部跳轉或者包含。其他的視圖技術,比如Velocity、XSLT等都是將視圖直接寫入到response流中。
有時需要在視圖渲染之前,向客戶端發個HTTP重定向。比如,向cotroller發出POST
請求數據,將response委派給了另一個controller(比如成功提交數據),此時,內部跳轉forward意味着POST
來的數據也一並發給了另一個controller,這些數據可能會與其本身所期望接收的數據相沖突。在展示數據之前執行一個重定向,還有一個原因,就是消除用戶多次提交的數據。這種情況下,瀏覽器賢惠發起一個初始POST
;然后,接到一個response,重定向到另一個URL;最后,瀏覽器在重定向中發起GET
請求。因此,對於瀏覽器而言,當前頁面不是POST結果頁,而是GET
結果。最終的效果是,用戶不能通過刷新,重新POST
數據。刷新時,將會得到一個GET
結果頁,而不是重新發送初始POST
數據。
重定向視圖RedirectView
強制重定向的方式之一,是返回`RedirectView`實例。此時,`DispatcherServlet`不會使用常規視圖解析機制。因為已經指定了(重定向)視圖。
RedirectView
導致HttpServletResponse.sendRedirect()
的調用,返回客戶端一個重定向。默認情況下,推薦將模型屬性增加到重定向的URI模板變量中,其余的屬性,比如原始類型或者是原始類型集合/數組並自動添加到query parameter中 。
將原始類型數據作為query parameter,也許對於重定向的URL來說,非常有用。當然了,模型也可以包含額外的屬性用於渲染視圖,比如下拉菜單項,若要在重定向時不將這些額外的屬性添加到URL中,可以聲明RedirectAttributes
類型的controller方法參數,RedirectAttributes
中指定需要的屬性,當Controller重定向時,會使用該參數中的指定屬性,若沒有該參數則會使用model中的屬性。
注意,request中的URI模板變量,在重定向時,可以直接使用,不需要通過Model
或者RedirectAttributes
明確的增加的URL中,比如:
@RequestMapping(value = "/files/{path}", method = RequestMethod.POST) public String upload(...) { // ... return "redirect:files/{path}"; }
如果你使用RedirectView
重定向到本conroller中,推薦將重定向URL注入到controller中,這樣讓重定向和視圖名在一起,而不是硬編碼到controller中,下一章詳細討論。
重定向前綴redirect:
While the use of RedirectView works fine, if the controller itself creates the RedirectView, there is no avoiding the fact that the controller is aware that a redirection is happening. This is really suboptimal and couples things too tightly. The controller should not really care about how the response gets handled. In general it should operate only in terms of view names that have been injected it。
使用RedidrectView
造成緊耦合,controller最好是不知道響應如何處理。總之,controller應該進處理注入的邏輯視圖名。
redirect:
前綴就可以完成解耦工作。若返回的視圖名包含前綴redirect:
,UrlBasedViewResolver
會解析出,此處需要重定向。前綴冒號后面的部分將會作為重定向URL。
前綴重定向和使用RedirectView
效果是一樣的,但是controller只需關注邏輯視圖名。redirect:/maapp/some/resource
返回的是當前Servlet context 的相對路徑,redirect:http://myhost.com/some/arbitray/path
將會重定向到一個絕對路徑。
forward:前綴
Spring MVC也支持`forward:`前綴視圖名,它最終由`UrlBaseViewResolver`及其子類解析。它會根據冒號后面的部分的URL創建`InternalResourceView `(最終會運行`RequestDispatcher.forward()`)視圖。因此,對於`InternalResourceViewResolver`和`InternalResourceView`(比如JSP)來說,他沒什么用處。若是使用其他視圖技術時,但是需要強制跳轉到Servlet/JSP引擎處理的情況下,就是有用的了。(注意,可以用視圖鏈替代此種方案)
如果視圖名含有forward:
前綴,Spring MVC將不會再處理response的過程中做任何特殊處理。譯注,大概意思就是spring 默認就是使用forward解析視圖的,由此可見forward前綴基本就是沒啥用了
ContentNegotiatingViewResolver
`ContentNegotiatingViewResolver`本身不會解析視圖,但是可以委托給其他解析器,就像是客戶端指定響應那樣選擇該視圖。2種途徑: * 使用不同的URI指定資源類型,通常是將文件擴展名附加到URI中,比如,`http://www.example.com/users/fred.pdf`將會請求PDF的響應,`http://www.example.com/users/fred.xml`請求XML的響應 * 使用相同的URI和`Accept`HTTP request頭請求不同的資源類型,支持的媒體類型參看[media types](http://en.wikipedia.org/wiki/Internet_media_type)。比如,現有HTTP request,`http://www.example.com/users/fred`加`application/pdf`請求頭,將會請求pdf響應,`http://www.example.com/users/fred`加`text/xml `請求頭將會請求XML響應。這個策略也被稱為[content negotiation](http://en.wikipedia.org/wiki/Content_negotiation)
使用相同的URI和
Accept
這個策略有個問題,在瀏覽器內是否可使用HTML設置該頭.比如,在Firefox zhong ,它被固定位為Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
。因此,通常使用不同的URI指定資源類型
策略。
Spring 提供了ContentNegotiatingViewResolver
用於解析基於文件擴展名、Accept
header的rquest。ContentNegotiatingViewResolver
本身不會解析視圖,但是可以通過設置bean的ViewResolvers
指定於視圖解析鏈中。
ContentNegotiatingViewResolver
通過比較request media type(又叫content-type
)去選擇一個合適的視圖去處理request,request media type是該視圖所關聯的所有的ViewResolvers
能處理的media type。視圖列表中第一個與響應類型相容的視圖將會返回給客戶端。如果ViewResolver
視圖解析鏈不能提供想用的視圖,視圖將會通過DefaultViews
中查找。后者將會忽略邏輯視圖名。Accept
header可以包含通配符,比如text/*
,這種情況下,具有text/xml
的Content-Type的View
視圖是相容的。
接下來看看,如何配置ContentNegotiatingViewResolver
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="atom" value="application/atom+xml"/> <entry key="html" value="text/html"/> <entry key="json" value="application/json"/> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" /> </list> </property> </bean> <bean id="content" class="com.foo.samples.rest.SampleContentAtomView"/>
BeanNameViewResolver
基於bean的name返回視圖,InternalResourceViewResolver
處理視圖到jsp page的轉換(spring 如何查找並實例化一個視圖詳情參看"Resolving views with the ViewResolver interface")。content
是一個AbstractAtomFeedView
類型bean,返回Atom RSS種子。有關 Atom Feed representation更多信息,參看Atom Views章節。
上面的配置中,如果請求以.html
結尾,視圖解析器將會查抄一個帶有text/html
媒體類型media type的視圖。InternalResourceViewResolver
提供支持text/html
類型的視圖。如果請求以.atom
結尾,視圖解析器將會查找帶有application/atom+xml
media type媒體類型的視圖。如果視圖名是content
,BeanNameViewResolver
將會返回SampleContentAtomView
。如果request以.json
結尾,DefaultViews
將會返回MappingJackson2JsonView
實例而忽略視圖名。除此之外,客戶端request可以不用擴展名,而是使用Accept
header設置media-type,也會產生同樣的結果。
如果ViewResolvers list沒有明確配置
ContentNegotiatingViewResolver
,那么Spring MVC將會使用應用上下文中配置的ViewResolvers
下面展示,使用URI http://localhost/content.atom
或者http://localhost/content
加application/atom+xml的Accept
header,來返回Atom RSS feed種子的controller 代碼
@Controller
public class ContentController { private List<SampleContent> contentList = new ArrayList<SampleContent>(); @RequestMapping(value="/content", method=RequestMethod.GET) public ModelAndView getContent() { ModelAndView mav = new ModelAndView(); mav.setViewName("content"); mav.addObject("sampleContentList", contentList); return mav; } }
使用flash attributes
為了在另一個controller中使用本controller中的attributes ,得使用Flash attributes存儲`attributes `。常用於redirecting 重定向,比如,* Post/Redirect/Get *模式。 Flash attributes在重定向之前臨時保存(通常是在session中),在重定向之后可用,然后直接刪掉。
Spring MVC 的flash attributes有兩個核心類FlashMap
用於持有 flash attributes,FlashMapManager
用於存儲、檢索、管理FlashMap
實例。
Flash attribute 默認是開啟的,無需明確開啟,它不會引起session 創建。每一個request都會有一個從上個 request傳來的"input"FlashMap
,同時作為"output"FlashMap
傳給下一個request。在Spring MVC中任何地方,通過RequestContextUtils
類中的靜態方法,FlashMap
實例都是可以訪問的。
注解controller通常不直接使用FlashMap
譯注,擦,你在不早說,早說我就不翻這一段了,而是在@RequestMapping
方法接受一個RedirectAttributes
類型參數,使用該參數解決重定向場景中的falsh attribute。Flash attributes added via RedirectAttributes are automatically propagated to the "output" FlashMap. Similarly after the redirect attributes from the "input" FlashMap are automatically added to the Model of the controller serving the target URL.
**Matching requests to flash attributes**
The concept of flash attributes exists in many other Web frameworks and has proven to be exposed sometimes to concurrency issues. This is because by definition flash attributes are to be stored until the next request. However the very "next" request may not be the intended recipient but another asynchronous request (e.g. polling or resource requests) in which case the flash attributes are removed too early.
To reduce the possibility of such issues, RedirectView automatically "stamps" FlashMap instances with the path and query parameters of the target redirect URL. In turn the default FlashMapManager matches that information to incoming requests when looking up the "input" FlashMap.
This does not eliminate the possibility of a concurrency issue entirely but nevertheless reduces it greatly with information that is already available in the redirect URL. Therefore the use of flash attributes is recommended mainly for redirect scenarios .
構建URIs
Spring MVC provides a mechanism for building and encoding a URI using UriComponentsBuilder and UriComponents. Spring MVC提供了兩個類,`UriComponentsBuilder`和`UriComponents`,用於構建和編碼。
看樣例如何構建URI template String
UriComponents uriComponents = UriComponentsBuilder.fromUriString( "http://example.com/hotels/{hotel}/bookings/{booking}").build(); URI uri = uriComponents.expand("42", "21").encode().toUri();
注意UriComponents
是不可變類,如果需要,expand()
和encode()
會返回新的實例。 也可以使用URI 組件完成展開和編碼的工作
UriComponents uriComponents = UriComponentsBuilder.newInstance() .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build() .expand("42", "21") .encode();
在Servlet環境中,ServletUriComponentsBuilder
子類提供靜態工廠方法用於從Servlet request中拷貝出可用的URL信息。
HttpServletRequest request = ... // 重用 host, scheme, port, path and query string // 替換 the "accountId" query param ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}").build() .expand("123") .encode();
或者,也可以從RUL中拷貝一組可用信息,包括上下文路徑
// 重用 host, port and context path // 附加 "/accounts" 到path中 ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts").build()
如果DispatcherServlet
是通過name映射(比如,/main/*
),也可獲取映射的字面值。
// Re-use host, port, context path // Append the literal part of the servlet mapping to the path // Append "/accounts" to the path ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts").build()
為Controller和方法構建URI
Spring MVC提供了另外一種機制用於構建和編碼URI,在應用內定義用於鏈接到Controller 和方法。
給定Controller
@Controller
@RequestMapping("/hotels/{hotel}") public class BookingController { @RequestMapping("/bookings/{booking}") public String getBooking(@PathVariable Long booking) { // ... }
使用MvcUriComponentsBuilder
,前面的樣例就變成了現在這個樣子
UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController.class, "getBooking",21).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
MvcUriComponentsBuilder
也可以創建 "mock Controllers"模擬controller,因此相對於實際的ControllerAPI來說,可以通過編碼來創建URI
UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri()
根據視圖構建URI訪問 controller和方法
It is also useful to build links to annotated controllers from views (e.g. JSP). This can be done through a method on MvcUriComponentsBuilder which refers to mappings by name called fromMappingName.
As of 4.1 every @RequestMapping is assigned a default name based on the capital letters of the class and the full method name. For example, the method getFoo in class FooController is assigned the name "FC#getFoo". This naming strategy is pluggable by implementing HandlerMethodMappingNamingStrategy and configuring it on your RequestMappingHandlerMapping. Furthermore the @RequestMapping annotation includes a name attribute that can be used to override the default strategy.
[Note] The assigned request mapping names are logged at TRACE level on startup. The Spring JSP tag library provides a function called mvcUrl that can be used to prepare links to controller methods based on this mechanism.
For example given:
@RequestMapping("/people/{id}/addresses") public class MyController {
@RequestMapping("/{country}")
public HttpEntity getAddress(@PathVariable String country) { ... }
} The following JSP code can prepare a link:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> ... <a href="${s:mvcUrl('PC#getPerson').arg(0,'US')
Using locales
Most parts of Spring’s architecture support internationalization, just as the Spring web MVC framework does. DispatcherServlet enables you to automatically resolve messages using the client’s locale. This is done with LocaleResolver objects.
When a request comes in, the DispatcherServlet looks for a locale resolver, and if it finds one it tries to use it to set the locale. Using the RequestContext.getLocale() method, you can always retrieve the locale that was resolved by the locale resolver.
In addition to automatic locale resolution, you can also attach an interceptor to the handler mapping (see Section 17.4.1, “Intercepting requests with a HandlerInterceptor” for more information on handler mapping interceptors) to change the locale under specific circumstances, for example, based on a parameter in the request.
Locale resolvers and interceptors are defined in the org.springframework.web.servlet.i18n package and are configured in your application context in the normal way. Here is a selection of the locale resolvers included in Spring.
AcceptHeaderLocaleResolver
This locale resolver inspects the accept-language header in the request that was sent by the client (e.g., a web browser). Usually this header field contains the locale of the client’s operating system. Note that this resolver does not support time zone information.
CookieLocaleResolver
This locale resolver inspects a Cookie that might exist on the client to see if a Locale or TimeZone is specified. If so, it uses the specified details. Using the properties of this locale resolver, you can specify the name of the cookie as well as the maximum age. Find below an example of defining a CookieLocaleResolver.
Table 17.4. CookieLocaleResolver properties
Property | Default | Description |
---|---|---|
cookieName | classname + LOCALE | The name of the cookie |
cookieMaxAge | Integer.MAX_INT | The maximum time a cookie will stay persistent on the client. If -1 is specified, the cookie will not be persisted; it will only be available until the client shuts down their browser. |
cookiePath | / | Limits the visibility of the cookie to a certain part of your site. When cookiePath is specified, the cookie will only be visible to that path and the paths below it. |
SessionLocaleResolver
The SessionLocaleResolver allows you to retrieve Locale and TimeZone from the session that might be associated with the user’s request.
LocaleChangeInterceptor
You can enable changing of locales by adding the LocaleChangeInterceptor to one of the handler mappings (see Section 17.4, “Handler mappings”). It will detect a parameter in the request and change the locale. It calls setLocale() on the LocaleResolver that also exists in the context. The following example shows that calls to all *.view resources containing a parameter named siteLanguage will now change the locale. So, for example, a request for the following URL, http://www.sf.net/home.view?siteLanguage=nl will change the site language to Dutch. ```xml
/**/*.view=someController
<h3 id='mvc-themeresolver'>Using themes</h3>
<h4 id='mvc-themeresolver-introduction'>主題概述</h4>
spring MVC可以設置主題,因此增強用戶體驗。主題是一組靜態資源,是css和image,他們會影響應用的視覺效果。
<h4 id='mvc-themeresolver-defining'>定義themes</h4>
To use themes in your web application, you must set up an implementation of the org.springframework.ui.context.ThemeSource interface. The WebApplicationContext interface extends ThemeSource but delegates its responsibilities to a dedicated implementation. By default the delegate will be an org.springframework.ui.context.support.ResourceBundleThemeSource implementation that loads properties files from the root of the classpath. To use a custom ThemeSource implementation or to configure the base name prefix of the ResourceBundleThemeSource, you can register a bean in the application context with the reserved name themeSource. The web application context automatically detects a bean with that name and uses it.
When using the ResourceBundleThemeSource, a theme is defined in a simple properties file. The properties file lists the resources that make up the theme. Here is an example:
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
The keys of the properties are the names that refer to the themed elements from view code. For a JSP, you typically do this using the spring:theme custom tag, which is very similar to the spring:message tag. The following JSP fragment uses the theme defined in the previous example to customize the look and feel:
```xml
<%@ 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>
By default, the ResourceBundleThemeSource uses an empty base name prefix. As a result, the properties files are loaded from the root of the classpath. Thus you would put the cool.properties theme definition in a directory at the root of the classpath, for example, in /WEB-INF/classes. The ResourceBundleThemeSource uses the standard Java resource bundle loading mechanism, allowing for full internationalization of themes. For example, we could have a /WEB-INF/classes/cool_nl.properties that references a special background image with Dutch text on it.
Theme resolvers
After you define themes, as in the preceding section, you decide which theme to use. The DispatcherServlet will look for a bean named themeResolver to find out which ThemeResolver implementation to use. A theme resolver works in much the same way as a LocaleResolver. It detects the theme to use for a particular request and can also alter the request’s theme. The following theme resolvers are provided by Spring:
Table 17.5. ThemeResolver implementations Class | 描述 ------ | ------ FixedThemeResolver
| Selects a fixed theme, set using the defaultThemeName property. SessionThemeResolver
| The theme is maintained in the user’s HTTP session. It only needs to be set once for each session, but is not persisted between sessions. CookieThemeResolver
| The selected theme is stored in a cookie on the client.
Spring also provides a ThemeChangeInterceptor that allows theme changes on every request with a simple request parameter.
Spring對multipart支持(文件上傳)
簡介
spring內置支持multipart,在應用中支持處理文件上傳。使用插件`MultipartResolver`對象開啟對multipart支持,他們定義在`org.springframework.web.multipart`包。Spring 提供了一個基於`Commons FileUpload`的`MultipartResolver `實現,也提供了一個基於Sevlet3.0multipart request 解析。
默認情況下,Spring 沒有multipart 處理,因為一些開發者要自己處理multiparts。若要在Spring中開啟multipart處理,得在應用上下文中增加一個multipart resolver解析器。每個request都會被檢查是否包含multipart。如果為發現 multipart,request繼續。如果發現了multipart,在應用上下文中聲明的MultipartResolver
就會工作。然后,request中multipart的屬性處理和其他的request的屬性處理就相同了。
使用基於 Commons FileUpload的MultipartResolver
下面展示如何使用`CommonsMultipartResolver`: ```java
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="100000"/>
``` CommonsMultipartResolver, you need to use commons-fileupload.jar. 當然了,這是需要第三方jar包的,上例中的`CommonsMultipartResolver`,依賴`commons-fileupload.jar`。
When the Spring DispatcherServlet detects a multi-part request, it activates the resolver that has been declared in your context and hands over the request. The resolver then wraps the current HttpServletRequest into a MultipartHttpServletRequest that supports multipart file uploads. Using the MultipartHttpServletRequest, you can get information about the multiparts contained by this request and actually get access to the multipart files themselves in your controllers.
Spring DispatcherServlet
解析出有一個multi-part
request, Spring會激活已經在上下文中聲明的resolver解析器,並處理request。resolver則會包裹當前HttpServletRequest
為MultipartHttpServletRequest
,MultipartHttpServletRequest
支持multipart 文件上傳。使用MultipartHttpServletRequest
,可以在controller中獲取關於multiparts的源信息和訪問multipart文件本身。
在Servlet3.0中使用MultipartResolver
為了基於multipart解析 使用Servlet3.0,得在`web.xml`中的`DispatcherServlet`中配置`multipart-config`部分,或者編程式的使用`javax.servlet.MultipartConfigElement`,或者在自定義Servlet類中使用`javax.servlet.annotation.MultipartConfig`注解。可以配置最大文件尺寸、存儲位置,該配置是應用於Serlvet類級別,因為Servlet3.0不允許這些設置有MultipartResolver設置。
一旦Servlet 3.0 multipart解析通過上述任一中方式開啟,就可以使用StandartServletMultipartResolver
。
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean>
在form中處理文件上傳
request處理就像其他的request一樣,首先創建一個form,具有file input的form是允許用戶上傳文件的form。設置form屬性`enctype="multipart/form-data"` ,讓瀏覽器知道如何對multipart request進行編碼處理: ```html <title>Upload a file please</title>
Please upload a file
```
接下來創建一個controller,它可以處理文件上傳,該controller和常規注解@Controller
非常像,區別就是在方法參數中使用MultipartHttpServletRequest
或者MultipartFile
:
@Controller
public class FileUploadController { @RequestMapping(value = "/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
方法參數是如何與form表單中的input元素映射的。本例中,沒有使用byte[]
,但是在實際使用中可以將其存儲於數據庫、存儲於文件系統等等。
若是使用Servlet 3.0 multipart解析,則可以使用 javax.servlet.http.Part
作為方法參數:
@Controller
public class FileUploadController { @RequestMapping(value = "/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"; } }
處理來自於腳本客戶端的文件上傳request
也可以在非瀏覽器客戶端提交Multipart request。上面樣例中的配置頁同樣適用。當然了,不想瀏覽器那樣提交files和簡單的form域,腳本客戶端也可以發送指定content type的更復雜的數據,比如multipart request中包含一個file和一部分JSON格式的數據: ```text 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 ...
在Controller方法中使用`@RequestParam("meta-data") String metadata`訪問名為"meta-data"部分的數據。當然了,你可能更喜歡從request部分的中的JSON格式數據中解析出強類型對象,非常簡單,使用`HttpMessageConverter` 將`@RequestBody`轉換非multipart request為目標對象即可。
為了達到此目的,得使用`@RequestPart`注解替代`@RequestParam`注解。它允許你通過`HttpMessageConverter`將指定的multipart解析為該multipart request的header中的`Content-type`類型數據。
```java
@RequestMapping(value="/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
完成轉換。
處理異常
HandlerExceptionResolver
Spring 的`HandlerExceptionResolver `實現是處理controller執行期間發生的異常。`HandlerExceptionResolver `和exception mapping異常映射有些像,異常映射可以在`web.xml`中定義。當然了,spring也提供了更靈活的方式完成映射。比如,在拋出異常時提供執行handler的信息。因此,使用編程式的異常處理,在request forward跳轉之前,會有更多的選項可供操作(與Servlet 設置異常映射相比,具有相同的執行結果)。`HandlerExceptionResolver`接口中只有一個方法,`ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)`,該方法返回一個`ModelAndView`,除了提供該接口以外,Spring還提供了`SimpleMappingExceptionResolver `,還有`@ExceptionHandler`注解,他們都可用於異常處理。`SimpleMappingExceptionResolver `可以將制定異常映射到一個view 視圖名上,通過異常的class name映射。這個方式等價於Servlet API中的異常映射功能,但是Spring的異常映射可以實現更細粒度的來自不同的handler的異常映射。`@ExceptionHandler`注解可用於可能拋出異常的方法上。`@ExceptionHandler`方法,可能是在`@Controller`類內定義,應用於本類的方法,也可以在`@ControllerAdvice`類內定義,應用於多個`@Controller`類。下面詳細講述。
@ExceptionHandler
`HandlerExceptionResolver `接口和`SimpleMappingExceptionResolver `實現類允許request跳轉之前根據異常跳轉到指定視圖,同時提供一些配置可選項來完成一些邏輯。有些情況下,特別是基於`@ResponseBody`注解的方法,這種異常映射機制,是非常方便的操作響應:設置response的status,將錯誤內容寫到response中。
@ExceptionHandler
若是聲明在Controller
類中,則會處理本類及子類@RequestMapping
方法爆出的異常。也可以在ControllerAdvice
類中聲明@ExceptionHandler
方法,該方法將會處理多個Controller
類中的的@RequestMapping
。下例展示 controller本地@ExceptionHandler
方法
@Controller
public class SimpleController { // 此處省略10k個@RequestMapping方法 ... @ExceptionHandler(IOException.class) public ResponseEntity<String> handleIOException(IOException ex) { // 構造responseEntity return responseEntity; } }
@ExceptionHandler
的value也可以設置為一個異常類型數組。如果拋出的異常,匹配數組元素之一,@ExceptionHandler
注解的方法則被調用。如果注解為設置value,則會使用參數列表中的異常類型作為匹配源。 和@RequestMapping
方法很像,@ExceptionHandler
的參數和返回值都是非常靈活的。比如,方法中可直接訪問HttpServletRequest
和PortletRequest
,當然他們得在相應的環境中才行,HttpServletRequest
要在Servlet 環境中,PortletRequest
得在Portlet 環境中。返回值可以是String
,被解析為視圖名;可以是ModelAndView
對象;可以是ResponseEntity
,或者可以使用@ResponseBody
注解,通過message converter消息轉換器將返回值轉換后寫入到response流中。
處理標准的Spring MVC 異常
Spring MVC可拋出非常豐富的異常。`SimpleMappingExceptionResolver `可以非常容易的將異常映射到默認錯誤視圖。當然了,有些客戶端需要的不是錯誤視圖,而是解析response中的錯誤code編碼。Spring異常可以被解析為客戶端錯誤(4xx)或者服務器端錯誤(5xx)。 `DefaultHandlerExceptionResolver `將Spring MVC 異常翻譯成指定的錯誤編碼。該類是默認注冊的。下面列出有此類解析的異常與錯誤碼對應關系
Exception | HTTP Status Code |
---|---|
BindException |
400 (錯誤請求) |
ConversionNotSupportedException |
500 (服務器內部錯誤) |
HttpMediaTypeNotAcceptableException |
406 (Not Acceptable) |
HttpMediaTypeNotSupportedException |
415 (Unsupported Media Type) |
HttpMessageNotReadableException |
400 (Bad Request) |
HttpMessageNotWritableException |
500 (Internal Server Error) |
HttpRequestMethodNotSupportedException |
405 (Method Not Allowed) |
MethodArgumentNotValidException |
400 (Bad Request) |
MissingServletRequestParameterException |
400 (Bad Request) |
MissingServletRequestPartException |
400 (Bad Request) |
NoHandlerFoundException |
404 (Not Found) |
NoSuchRequestHandlingMethodException |
404 (Not Found) |
TypeMismatchException |
400 (Bad Request) |
DefaultHandlerExceptionResolver
透明的設置response的status,用戶無需關心。當然了,它會終止向response body寫入的錯誤信息。可以提前准備ModelAndView,然后到視圖中去渲染錯誤信息,通過配置ContentNegotiatingViewResolver
,MappingJackson2JsonView
等等。還可以用@ExceptionHandler
替代。
若是喜歡通過@ExceptionHandler
向response寫入錯誤內容則可繼承ResponseEntityExceptionHandler
。通過@ControllerAdvice
提供@ExceptionHandler
方法可以非常方便去處理標准Spring MVC異常,並返回ResponseEntity
。這樣就允許自定義response,並將錯誤信息寫入response。詳情參看ResponseEntityExceptionHandler
javadocs
使用@Response注解業務異常
對於業務異常,可以使用`@ResponseStatus`注解。當有異常拋出時,`ResponseStatusExceptionResolver `會根據response的status去處理。`DispatcherServlet `默認會注冊`ResponseStatusExceptionResolver`使其可用。
自定義默認的Servlet容器Error頁面
當response的status被設置為錯誤碼,並且response的body為空,通常Servlet 容器會渲染一個HTML將錯誤內容格式化輸出。在`web.xml`中聲明一個``,就是給容器配置了默認的錯誤頁。直到Servlet 3以前,該元素必須被映射到一個指定狀態碼或者是異常類型。Servlet 3以后則不需要映射。 ```xml /error ```
新機器測試 Note that the actual location for the error page can be a JSP page or some other URL within the container including one handled through an @Controller method: 注意,錯誤頁可能是個JSP,也可能是其他URL路徑,比如@Controller
的方法: 在controller中,錯誤碼和錯誤信息,通過request attribute在HttpServletResponse
中讀寫。
@Controller
public class ErrorController { @RequestMapping(value="/error", produces="application/json") @ResponseBody 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; } }
或者是在JSP中
<%@ page contentType="application/json" pageEncoding="UTF-8"%> { status:<%=request.getAttribute("javax.servlet.error.status_code") %>, reason:<%=request.getAttribute("javax.servlet.error.message") %> }
WEB Secuity
The Spring Security project provides features to protect web applications from malicious exploits. Check out the reference documentation in the sections on "CSRF protection", "Security Response Headers", and also "Spring MVC Integration". Note that using Spring Security to secure the application is not necessarily required for all features. For example CSRF protection can be added simply by adding the CsrfFilter and CsrfRequestDataValueProcessor to your configuration. See the Spring MVC Showcase for an example.
Another option is to use a framework dedicated to Web Security. HDIV is one such framework and integrates with Spring MVC.
慣例優於配置
開發過程中,有很多規范和慣例,他們都有各自的成因。Spring MVC支持“慣例優於配置”。“慣例優於配置”是指,如果你遵守命名規范,那么將省去大量的配置文件,比如:handler mapping,view resolvers,`ModelAndView`實例等等。 對於快速開發原型非常有利,也有利於編碼規范。
Controller命名規范ControllerClassNameHandlerMapping
`ControllerClassNameHandlerMapping `類是一個`HandlerMapping`實現, 用於處理request url和`Controller`實例之間的映射處理。
考慮下面的Controller
類,注意類名
public class ViewShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // 實現不重要 } }
Here is a snippet from the corresponding Spring Web MVC configuration file: 下面是相應的配置文件
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewShoppingCart" class="x.y.z.ViewShoppingCartController"> <!-- 注入依賴... --> </bean>
ControllerClassNameHandlerMapping
在應用上下文中發現Controller
bean,將其類名中的 Controller
解掉,剩余的部分作為其能處理的映射的路徑配置,因此 ViewShoppingCartController
將處理/viewshoppingcart*
路徑的請求。
接下來看看更多的例子,然后總結一下主要內容和中心思想(主義,URL中都是小寫的,Controller
類是駝峰式的)
WelcomController
處理/welcome*
URLHomeController
處理 ·/home*
URLIndexController
處理/index*
URLRegisterController
處理/register*
URL
MultiActionController
處理類的情況中,映射生成規則略復雜些。假設,下列Controller
是MultiActionController
。
AdminController
映射/admin/*
URLCatalogController
映射/catalog/*
URL
由此看出,若是按照管理命名Controller
實現為xxxController
, ControllerClassNameHandlerMapping
將使你省去大部分配置。
ControllerClassNameHandlerMapping
繼承自AbstractHandlermapping
,因此你可定義HandlerIntercepter
實例等各種handlerMapping組件。
The Model ModelMap (ModelAndView)
`ModelMap`類是一個`Map`,可攜帶Objects,這些對象用於在`View`中展示。假如現在有一個`Controller`實現,注意附加到`ModelAndView`中的對象並為設置相關聯的名字。
public class DisplayShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { List cartItems = // get a List of CartItem objects User user = // get the User doing the shopping ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- the logical view name mav.addObject(cartItems); <-- look ma看么, no name沒有name, just the object只有對象 mav.addObject(user); <-- and again ma又來啦啦! return mav; } }
ModelAndView
使用了一個ModelMap
類,ModelMap
類是一個自定義Map
實現,該類在增加對象時會自動為對象生成一個key。生成的key由增加的對象決定,比如User
類的對象,將使用短類名作為對象的key。參考以下生成規則:
x.y.User
實例,user
x.y.Registration
實例,registration
x.y.Foo
實例,foo
java.util.HashMap
,hashMap
。hashMap
不太直觀,也許你需要明確指定其namenull
將導致IllegalArgumentException
。如果要增加的對象可能是null
,那么應該為其指定一個name(也就是key)
**沒有自動復雜數據處理**
Spring Web MVC的慣例優於配置不支持復雜數據處理。也就是說,當你給`ModelAndView`增加一個`Person`的`List`的時候,不會生成`people`的name
這是根據“最少意外原則”決定的。
x.y.User[]
數組,包含0個或者多個x.y.User
元素,產生的key為UserList
x.y.Foo[]
數組,0個或者多個,fooList
java.util.ArrayList
類型List包含1個以上元素,產生userList
java.util.HashSet
,0個或者多個元素,產生fooList
- 空
java.Util.Arraylist
(0個元素),調用addObject(...)
時候,將不會有任何操作,也就是說該空List不能增加到模型中。
視圖 - RequestToViewNameTranslator
`RequestToViewNameTranslator `接口的作用是,在沒有邏輯視圖名被明確指定時,它將提供一個邏輯視圖名。它有一個實現,`DefaultRequestToViewNameTranslator`類。
DefaultRequestToViewNameTranslator
使用方法如下
public class RegistrationController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // 處理request... ModelAndView mav = new ModelAndView(); // 如果需要將數據寫入model... return mav; // 注意,沒有設置View ,也沒設置邏輯視圖名 } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!-- this bean with the well known name generates view names for us --> <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/> <bean class="x.y.RegistrationController"> <!-- inject dependencies as necessary --> </bean> <!-- maps request URLs to Controller names --> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
handleRequest(..)
方法未設置View以及邏輯視圖名,然后就返回了ModelAndView
。DefaultRequestToViewNameTranslator
將會根據request的URL生成一個邏輯視圖名。上例中,如果RegistrationController
聯結了ControllerClassNameHandlerMapping
,那么http://localhost/registration.html
的請求將會生成一個registration
的邏輯視圖名,該視圖名將會通過InternalResourceViewResolver
被解析到視圖/WEB-INF/jsp/registration.jsp
。
不需要明確定義
DefaultRequestToViewNameTranslator
,可通過Spring Web MVCDispatcherServlet
去實例化DefaultRequestToViewNameTranslator
。
更多詳情參看 javadocs
ETag support
An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine change in content at a given URL. It can be considered to be the more sophisticated successor to the Last-Modified header. When a server returns a representation with an ETag header, the client can use this header in subsequent GETs, in an If-None-Match header. If the content has not changed, the server returns 304: Not Modified.
Support for ETags is provided by the Servlet filter ShallowEtagHeaderFilter. It is a plain Servlet Filter, and thus can be used in combination with any web framework. The ShallowEtagHeaderFilter filter creates so-called shallow ETags (as opposed to deep ETags, more about that later).The filter caches the content of the rendered JSP (or other content), generates an MD5 hash over that, and returns that as an ETag header in the response. The next time a client sends a request for the same resource, it uses that hash as the If-None-Match value. The filter detects this, renders the view again, and compares the two hashes. If they are equal, a 304 is returned. This filter will not save processing power, as the view is still rendered. The only thing it saves is bandwidth, as the rendered response is not sent back over the wire.
You configure the ShallowEtagHeaderFilter in web.xml:
<filter>
<filter-name>etagFilter</filter-name> <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class> </filter> <filter-mapping> <filter-name>etagFilter</filter-name> <servlet-name>petclinic</servlet-name> </filter-mapping>
使用代碼實例化容器
In a Servlet 3.0+ environment, you have the option of configuring the Servlet container programmatically as an alternative or in combination with a web.xml file. Below is an example of registering a DispatcherServlet:
在Servlet3.0環境中,還可以編程式的配置Servlet容器,或者是與web.xml
結合使用。下面看樣例,如何注冊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容器。WebApplicationInitializer
有一個抽象基類實現AbstractDispatcherServletInitializer
,實現該抽象類非常簡單,繼承它,實現servlet映射方法和DispatcherServlet
配置的定位方法即可
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[] { "/" }; } }
上面的樣例中使用了基於java的Spring配置。若是使用基於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() }; } }
AbstractDispatcherServletInitializer
中的方法isAsyncSupported
是唯一能開啟DispatcherServlet
支持的異步的地方。默認他是true
。
配置Spring MVC
Section 17.2.1, “Special Bean Types In the WebApplicationContext” and Section 17.2.2, “Default DispatcherServlet Configuration” explained about Spring MVC’s special beans and the default implementations used by the DispatcherServlet. In this section you’ll learn about two additional ways of configuring Spring MVC. Namely the MVC Java config and the MVC XML namespace.
Section 17.2.1, “Special Bean Types In the WebApplicationContext” and Section 17.2.2, “Default DispatcherServlet Configuration” 這兩章解釋了Spring MVC中的特殊beans 和用於DispatcherServlet
的默認實現。本章講解,另外兩種方式配置Spring MVC。分別是基於MVC的Java配置和MVC XML配置
基於Java配置和基於XML配置都提供了想死的默認配置,用於覆蓋DispatcherServlet
默認實現。 之所以這么做,是為了減少重復工作,因為多數應用都使用相同的配置,也是為了提供更高層級的抽象配置,降低學習曲線,使用戶僅需要很少的關於配置的背景知識就可以使用Spring MVC了。
MVC基於java的配置比基於XML的配置要更容易些,也有更細粒度的控制。
開啟MVC Java配置、開啟MVC XML配置
在`@Configuration` 注解的類上使用注解`@EnableWebMvc`即可開啟MVC Java配置。 ```java @Configuration @EnableWebMvc public class WebConfig {
}
若是使用XML達到同樣的效果,得在DispatchServlet上下文中使用`mvc:annotation-driven`元素(如果沒有DispatchServlet 環境則是在root context根環境中)
```xml
<?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 />
</beans>
The above registers a RequestMappingHandlerMapping, a RequestMappingHandlerAdapter, and an ExceptionHandlerExceptionResolver (among others) in support of processing requests with annotated controller methods using annotations such as @RequestMapping, @ExceptionHandler, and others.