相關(guān)文章:
- Spring參考手冊(cè) 1 Spring Framework簡(jiǎn)介和典型的Web應(yīng)用程序
- Spring參考手冊(cè) 2 核心技術(shù)
- Spring參考手冊(cè) 3 校驗(yàn)峦筒,數(shù)據(jù)綁定和類型轉(zhuǎn)換
- Spring參考手冊(cè) 4 面向切面編程
- Spring參考手冊(cè) 5 數(shù)據(jù)訪問
一癌佩、Web MVC framework
1.1 Spring Web MVC framework簡(jiǎn)介
Spring Web model-view-controller (MVC) framework的核心是一個(gè)DispatcherServlet
跨嘉,這個(gè)調(diào)度器負(fù)責(zé)分配請(qǐng)求到處理器貌夕,可實(shí)現(xiàn)配置處理器映射涩盾、視圖解析缕粹、本地化雅倒、時(shí)區(qū)和主題解析還有上傳文件的支持别惦。默認(rèn)的處理器基于@Controller
和@RequestMapping
注解,提供了一個(gè)很靈活的處理方法嘁字。在Spring 3.0的簡(jiǎn)介可了解到恩急,@Controller
機(jī)制還允許你通過@PathVariable
注解和其他特性創(chuàng)建RESTful網(wǎng)址和應(yīng)用程序。
1.2 DispatcherServlet
Spring web MVC framework與其他許多MCV框架類似纪蜒,請(qǐng)求驅(qū)動(dòng)衷恭,圍繞一個(gè)中心Servlet,由它分配請(qǐng)求到控制器并提供其他功能來促進(jìn)web應(yīng)用程序開發(fā)纯续。Spring的DispatcherServlet
然而做的要更多攘滩,它已經(jīng)完全與Spring IoC容器整合,這樣就允許你使用Spring的其他特性侨把。
Spring Web MVC DispatcherServlet
的請(qǐng)求處理工作流通過下面的圖示來說明盐固。懂設(shè)計(jì)模式的讀者會(huì)認(rèn)出DispatcherServlet
使用的是一種前端控制器設(shè)計(jì)模式。
DispatcherServlet
是一個(gè)實(shí)實(shí)在在的Servlet
倦炒,這樣它可以被聲明在web.xml
里显沈。你需要映射你希望DispatcherServlet
處理的請(qǐng)求。這是一個(gè)標(biāo)準(zhǔn)的Java EE Servlet配置:
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
</web-app>
所有以/example
開始的請(qǐng)求都將會(huì)被名為example的DispatcherServlet
實(shí)例處理逢唤。在Servlet 3.0+環(huán)境拉讯,你還可以通過編程方式來配置Servlet容器,下面的代碼基本上與上面的web.xml
是等價(jià)的:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/example/*");
}
}
在初始化一個(gè)DispatcherServlet
時(shí)鳖藕,Spring MVC會(huì)查找在WEB-INF文件夾下的一個(gè)名為[servlet-name]-servlet.xml的文件并在創(chuàng)建bean魔慷,重寫全局范圍內(nèi)相同名字的bean。
假設(shè)有如下DispatcherServlet
配置:
<web-app>
<servlet>
<servlet-name>golfing</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>golfing</servlet-name>
<url-pattern>/golfing/*</url-pattern>
</servlet-mapping>
</web-app>
你需要一個(gè)文件/WEB-INF/golfing-servlet.xml
著恩,它將會(huì)包含所有Spring Web MVC特定的組件(beans)院尔。也可以通過一個(gè)Servlet初始化參數(shù)來改變這個(gè)位置纹烹。
針對(duì)單一DispatcherServlet場(chǎng)景也可以只有一個(gè)根上下文:
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
1.2.1 DispatcherServlet處理順序
在你設(shè)置一個(gè)DispatcherServlet
后,當(dāng)一個(gè)請(qǐng)求進(jìn)入DispatcherServlet
會(huì)開始處理請(qǐng)求:
WebApplicationContext
被搜索并且綁定請(qǐng)求作為一個(gè)屬性這樣controller和其他元素在處理過程中可以使用它召边。默認(rèn)它被綁定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
key下铺呵。本地化解析器被綁定到這個(gè)請(qǐng)求。如果你不需要本地化解析那你就不需要它隧熙。
主題解析器被綁定到這個(gè)請(qǐng)求片挂。如果你不使用主題你可以忽略它。
如果你指定了一個(gè)multipart文件解析器贞盯,這個(gè)請(qǐng)求將會(huì)被檢查音念;如果找到了multipart這個(gè)請(qǐng)求會(huì)被包裝成一個(gè)
MultipartHttpServletRequest
來進(jìn)行進(jìn)一步處理。(比如上傳文件時(shí))一個(gè)適當(dāng)?shù)奶幚砥鞅凰阉黪锔摇H绻业搅司玩溄拥教幚砥鱽頊?zhǔn)備一個(gè)model或者渲染闷愤。
一個(gè)model被返回,視圖被渲染
1.3 實(shí)現(xiàn)控制器
在Spring 2.5里已經(jīng)介紹了一種基于注解的編程模型件余,使用像@RequestMapping
, @RequestParam
, @ModelAttribute
這樣的注解讥脐。通過這種形式實(shí)現(xiàn)控制器不需要繼承指定的父類或者實(shí)現(xiàn)指定的接口。此外它們通常也不直接依賴Servlet APIs啼器。
@Controller
public class HelloWorldController {
@RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
如你所見旬渠,@Controller
和 @RequestMapping
注解允許靈活的方法名。@Controller
和 @RequestMapping
和許多其他注解構(gòu)成了Spring MVC實(shí)現(xiàn)的基礎(chǔ)端壳。
1.3.1 使用@Controller定義一個(gè)控制器
@Controller
注解標(biāo)明一個(gè)指定的類扮演控制器的角色告丢。Spring不要求你繼承任何父控制器類或者引用Servlet API。但是如果需要你仍然可以引用Servlet的特性损谦。
你可以在DispatcherServlet上下文中使用標(biāo)準(zhǔn)的Spring bean定義規(guī)則明確的定義一個(gè)控制器bean岖免。但是@Controller
也允許自動(dòng)檢測(cè),自動(dòng)注冊(cè)為bean照捡。
為了自動(dòng)檢測(cè)被注解的控制器颅湘,你需要在配置文件添加組件掃描:
<?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>
1.3.2 使用@RequestMapping映射請(qǐng)求
你可以使用@RequestMapping
注解來映射URL就像這個(gè)/appointments
,既可以在類上也可以在指定方法上麻敌。
下面這個(gè)例子展示了Spring MVC中使用注解的控制器:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(value="/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(value="/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
這個(gè)例子里@RequestMapping
被用到了很多地方栅炒。第一個(gè)用例實(shí)在類級(jí)別掂摔,表示所有處理方法都在/appointments
路徑下术羔。get()
方法將請(qǐng)求映射更加細(xì)化:它只接收GET請(qǐng)求,HTTP GET /appointments
會(huì)調(diào)用這個(gè)方法乙漓。getNewForm()
聯(lián)合了HTTP方法定義和路徑级历,那么GET請(qǐng)求appointments/new
將會(huì)被這個(gè)方法處理。
getForDay()
方法展示了@RequestMapping
的另一種用法:URI 模版叭披。(詳見下一章)
@RequestMapping
定義在類級(jí)別并不是必須的寥殖。沒有了它所有路徑都是絕對(duì)的不是相對(duì)的玩讳。下面的例子展示了一個(gè)多處理活動(dòng)的控制器:
@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,PUT嚼贡,POST等等熏纯,因?yàn)?code>@RequestMapping默認(rèn)會(huì)映射所有HTTP方法。使用@RequestMapping(method = RequestMethod.POST)
縮小了映射粤策。
URI Template模式
URI templates 可以被用于在@RequestMapping
方法中方便的訪問URL中被選中的一部分樟澜。
一個(gè)URI Template是一個(gè)類似URI的字符串,包含了一個(gè)或多個(gè)變量的名字叮盘。當(dāng)你替換了這些變量模版就會(huì)變成一個(gè)URI秩贰。例如http://www.example.com/users/{userId}包含變量userId。指定fred為變量的值就變成http://www.example.com/users/fred
在Spring MVC你可以用@PathVariable
注解來綁定一個(gè)URI template 的變量:
@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";
}
當(dāng)一個(gè)請(qǐng)求/owners/fred
到來后柔吼,String ownerId的值就是fred毒费。
你還可以指定URI template變量的名字:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
// implementation omitted
}
當(dāng)URI template變量的名字與方法參數(shù)的名字相同時(shí)可以省略@PathVariable
注解中的值。
一個(gè)方法可以有任意數(shù)量的@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";
}
當(dāng)一個(gè)@PathVariable
注解被用在一個(gè)Map<String, String>
參數(shù)愈魏,這個(gè)map會(huì)被所有URI template變量填充觅玻。
路徑模式
除了URI template外,@RequestMapping
注解還支持Ant-style路徑培漏,例如/myPath/*.do
串塑。也支持URI template變量和Ant-style聯(lián)合使用,例如:/owners/*/pets/{petId}
北苟。
消費(fèi)媒體類型
你可以通過指定一系列的消費(fèi)媒體類型縮窄主映射范圍桩匪。只有請(qǐng)求頭的Content-Type
與指定的媒體類型匹配時(shí)請(qǐng)求才會(huì)被接受,例如:
@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}
只有當(dāng)請(qǐng)求頭的Content-Type
包含application/json
時(shí)這個(gè)請(qǐng)求才會(huì)被接受并處理友鼻。消費(fèi)類型表達(dá)式可以是否定的傻昙,例如!text/plain
,這將會(huì)匹配所有Content-Type
不包含text/plain
的請(qǐng)求彩扔。
生產(chǎn)媒體類型
你可以通過指定一系列的生成媒體類型縮窄主映射范圍妆档。只有請(qǐng)求頭的Accept
與其中一個(gè)媒體類型匹配時(shí)請(qǐng)求才會(huì)被接受,例如:
@Controller
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
}
與消費(fèi)媒體類型類似虫碉,生產(chǎn)類型表達(dá)式可以是否定的贾惦,例如!text/plain
,這將會(huì)匹配所有Accept
不包含text/plain
的請(qǐng)求敦捧。
請(qǐng)求參數(shù)和請(qǐng)求頭
你可以通過請(qǐng)求參數(shù)條件縮窄請(qǐng)求映射范圍须板,例如"myParam", "!myParam"或者"myParam=myValue"。前兩個(gè)是測(cè)試參數(shù)是否存在的兢卵,第三個(gè)是指定一個(gè)參數(shù)的值习瑰。下面是一個(gè)例子:
@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
}
}
這個(gè)例子里,只有請(qǐng)求的參數(shù)里有myParam
并且它的值為myParam時(shí)才會(huì)被這個(gè)方法處理秽荤。同樣的方法可以用來測(cè)試請(qǐng)求頭:
@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
}
}
1.3.3 定義@RequestMapping處理方法
一個(gè)@RequestMapping
處理方法可以擁有一個(gè)非常靈活的簽名甜奄。
支持的參數(shù)類型
下面是支持的方法參數(shù):
- Request 或者 response對(duì)象 (Servlet API)
- Session 對(duì)象 (Servlet API)柠横,這個(gè)參數(shù)永遠(yuǎn)不會(huì)是
null
-
org.springframework.web.context.request.WebRequest
或者org.springframework.web.context.request.NativeWebRequest
。允許通用的請(qǐng)求參數(shù)訪問课兄,就像request/session屬性訪問一樣牍氛,但是沒有被束縛于Servlet/Portlet API -
java.util.Locale
當(dāng)前請(qǐng)求的本地化 - 用來訪問請(qǐng)求內(nèi)容的
java.io.InputStream
/java.io.Reader
- 用來生產(chǎn)響應(yīng)內(nèi)容的
java.io.OutputStream
/java.io.Writer
-
org.springframework.http.HttpMethod
HTTP請(qǐng)求方法 -
java.security.Principal
當(dāng)前已驗(yàn)證的用戶 -
@PathVariable
注解的參數(shù) -
@MatrixVariable
注解的參數(shù) -
@RequestParam
注解的參數(shù) -
@RequestHeader
注解的參數(shù),用來訪問HTTP請(qǐng)求頭烟阐。 -
@RequestBody
注解的參數(shù)糜俗,用來訪問HTTP請(qǐng)求體 -
@RequestPart
注解的參數(shù),用來訪問一個(gè)請(qǐng)求的multipart/form-data部分 -
HttpEntity<?>
參數(shù)用來訪問HTTP請(qǐng)求頭和內(nèi)容曲饱。 -
java.util.Map
/org.springframework.ui.Model
/org.springframework.ui.ModelMap
傳遞值給web視圖 -
org.springframework.web.servlet.mvc.support.RedirectAttributes
在請(qǐng)求重定向時(shí)傳遞屬性悠抹,如果一個(gè)方發(fā)返回一個(gè)"redirect:"前綴的視圖或者RedirectView
-
@ModelAttribute
注解,用來綁定請(qǐng)求的參數(shù)到一個(gè)實(shí)體類扩淀,例如一個(gè)User類 -
org.springframework.validation.Errors
/org.springframework.validation.BindingResult
上面實(shí)體類的校驗(yàn)結(jié)果 -
org.springframework.web.bind.support.SessionStatus
標(biāo)記表單處理狀態(tài)為完成 -
org.springframework.web.util.UriComponentsBuilder
構(gòu)建當(dāng)前請(qǐng)求的host, port, scheme, context path
需要注意的是Errors
或者 BindingResult
需要緊跟著實(shí)體類楔敌。這個(gè)例子將不會(huì)正常工作:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
下面這個(gè)例子才是正確的:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
支持的返回類型
下面是支持的返回類型:
- 一個(gè)
ModelAndView
對(duì)象 - 一個(gè)
Model
對(duì)象 - 一個(gè)
Map
對(duì)象 - 一個(gè)
View
對(duì)象 - 一個(gè)
String
對(duì)象,它可以被理解為一個(gè)本地視圖名稱 -
void
驻谆,如果方法自己處理響應(yīng) - 如果一個(gè)方法上有
@ResponseBody
注解卵凑,返回類型將被寫到響應(yīng)體內(nèi),如果配置了JSON轉(zhuǎn)換器胜臊,返回的內(nèi)容還會(huì)被轉(zhuǎn)換成JSON字符串勺卢。 - 一個(gè)
HttpEntity<?>
或者ResponseEntity<?>
對(duì)象,可以用來訪問HTTP響應(yīng)頭和內(nèi)容 - 一個(gè)
HttpHeaders
對(duì)象象对,返回一個(gè)沒有響應(yīng)體的response - 一個(gè)
Callable<?>
可以被返回當(dāng)應(yīng)用程序想在一個(gè)Spring MVC管理的線程里生產(chǎn)異步的返回值 - 一個(gè)
DeferredResult<?>
或者ListenableFuture<?>
可以被返回當(dāng)應(yīng)用程序想在一個(gè)它自己選擇的線程里生產(chǎn)異步的返回值 - 任何其他的返回值都會(huì)被當(dāng)做一個(gè)model的屬性暴露給視圖
使用@RequestParam綁定請(qǐng)求參數(shù)
在controller里使用@RequestParam
注解可以綁定一個(gè)請(qǐng)求參數(shù)到一個(gè)方法的參數(shù)黑忱。
下面的展示了使用方法:
@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";
}
// ...
}
這個(gè)注解的參數(shù)默認(rèn)是必須存在的,但是你可以指定它為可選的勒魔,例如:@RequestParam(value="petId", required=false)
如果方法參數(shù)的類型不是String
甫煞,那么將會(huì)自動(dòng)進(jìn)行類型轉(zhuǎn)換。
如果@RequestParam
注解使用在一個(gè)Map<String, String>
或者 MultiValueMap<String, String>
參數(shù)上冠绢,請(qǐng)求里所有的參數(shù)都會(huì)被填充進(jìn)來抚吠。
使用@RequestBody注解映射請(qǐng)求體
@RequestBody
注解聲明一個(gè)方法的參數(shù)被綁定了HTTP請(qǐng)求體的值。
@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
HttpMessageConverter
負(fù)責(zé)轉(zhuǎn)換HTTP請(qǐng)求信息到一個(gè)對(duì)象也可以把一個(gè)對(duì)象轉(zhuǎn)換為HTTP響應(yīng)體弟胀。
下面是幾個(gè)默認(rèn)的HttpMessageConverter
:
-
ByteArrayHttpMessageConverter
轉(zhuǎn)換 byte 數(shù)組 -
StringHttpMessageConverter
轉(zhuǎn)換 字符串 -
FormHttpMessageConverter
轉(zhuǎn)換表單數(shù)據(jù) 到/從 一個(gè)MultiValueMap<String, String>
-
SourceHttpMessageConverter
轉(zhuǎn)換 到/從javax.xml.transform.Source
JSON轉(zhuǎn)換器(譯者增加):
-
FastJsonHttpMessageConverter
使用FastJson轉(zhuǎn)換
相關(guān)配置:
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json</value>
</list>
</property>
<property name="features">
<list>
<value>WriteMapNullValue</value>
<value>QuoteFieldNames</value>
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list >
<ref bean="fastJsonHttpMessageConverter" />
</list>
</property>
</bean>
使用@ResponseBody注解映射響應(yīng)體
與@RequestBody
相似楷力。這個(gè)注解表明返回類型要被直接寫入響應(yīng)體中:
@RequestMapping(value = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
return "Hello World";
}
當(dāng)然Spring會(huì)使用HttpMessageConverter
轉(zhuǎn)換對(duì)象,然后才寫入相應(yīng)體孵户,如果配置的是FastJsonHttpMessageConverter
那么對(duì)象將會(huì)被轉(zhuǎn)換成JSON字符串寫入響應(yīng)體萧朝。
使用@RestController注解創(chuàng)建REST Controllers
簡(jiǎn)單的講就是將一個(gè)Controller所有的@RequestMapping
方法自動(dòng)加上@ResponseBody
。
使用@RequestHeader注解映射請(qǐng)求頭
@RequestHeader
注解允許一個(gè)方法參數(shù)綁定一個(gè)請(qǐng)求頭延届。
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
當(dāng)@RequestHeader
注解在一個(gè)Map<String, String>
, MultiValueMap<String, String>
, 或者 HttpHeaders
時(shí)會(huì)填充所有請(qǐng)求頭剪勿。
使用@ControllerAdvice注解為controller增加全局處理
@ControllerAdvice
注解是一個(gè)組件注解,允許實(shí)現(xiàn)的類被自動(dòng)發(fā)現(xiàn)方庭。當(dāng)使用MVC命名空間時(shí)它會(huì)自動(dòng)啟用厕吉。它注解的
類里可以包含@ExceptionHandler
, @InitBinder
和@ModelAttribute
注解的方法,這些方法將會(huì)應(yīng)用到相應(yīng)的@RequestMapping
方法上械念。
@ControllerAdvice
注解還允許匹配controller的一部分子集:
// 匹配所有@RestController注解的Controller
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}
// 匹配指定包下的Controller
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}
// 匹配指定Controller類
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}
1.4 解析視圖
所有的MVC框架針對(duì)于web應(yīng)用程序都提供了一種方法來處理視圖头朱。Spring提供了視圖解析器,允許你在瀏覽器中渲染模型而不用你嘗試使用一個(gè)特別的視圖技術(shù)龄减。Spring允許你使用JSP, Velocity templates and XSLT等視圖项钮。
有兩個(gè)接口對(duì)Spring處理視圖很重要,它們是ViewResolver
和View
希停。ViewResolver
在視圖名稱和真實(shí)視圖之間提供了一個(gè)映射烁巫。View
接口引導(dǎo)傳遞請(qǐng)求到一個(gè)視圖。
1.4.1 視圖解析和ViewResolver接口
Spring提供了不少視圖解析器宠能,這個(gè)表格列出了它們的大部分亚隙;一些例子緊隨其后。
ViewResolver | 描述 |
---|---|
AbstractCachingViewResolver |
抽象視圖解析緩存視圖违崇。通常視圖需要準(zhǔn)備后才能使用阿弃;拓展這個(gè)視圖解析器可以提供緩存 |
XmlViewResolver |
允許將配置寫在XML文件里,默認(rèn)的配置文件是/WEB-INF/views.xml
|
ResourceBundleViewResolver |
使用一個(gè)properties文件配置視圖羞延。默認(rèn)的文件名views.properties
|
UrlBasedViewResolver |
根據(jù)邏輯名稱匹配視圖資源 |
InternalResourceViewResolver |
UrlBasedViewResolver 的子類渣淳,支持InternalResourceView
|
VelocityViewResolver / FreeMarkerViewResolver
|
支持返回模板引擎的視圖 |
ContentNegotiatingViewResolver |
根據(jù)請(qǐng)求文件名或者Accept 頭解析視圖 |
例如,如果使用JSP作為視圖伴箩,你可以使用UrlBasedViewResolver
入愧。這個(gè)視圖解析器將一個(gè)視圖名轉(zhuǎn)化為一個(gè)URL,傳遞請(qǐng)求到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>
當(dāng)返回一個(gè)字符串test
作為邏輯視圖名稱時(shí)砂客,這個(gè)視圖解析器會(huì)傳遞請(qǐng)求到RequestDispatcher
,它會(huì)將請(qǐng)求送到/WEB-INF/jsp/test.jsp
當(dāng)你在web應(yīng)用程序中聯(lián)合不同的視圖技術(shù)時(shí),可以使用ResourceBundleViewResolver
:
<bean id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
<property name="defaultParentView" value="parentView"/>
</bean>
ResourceBundleViewResolver
根據(jù)basename屬性指定的值檢查ResourceBundle
呵恢。它會(huì)使用[viewname].(class)
屬性作為視圖類鞠值,使用[viewname].url
屬性作為視圖url。views.properties
默認(rèn)路徑在WEB-INF/classes
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp
productList.(class)=org.springframework.web.servlet.view.velocity.VelocityView
productList.url=/WEB-INF/template/test2.vm
還有一個(gè)種方式來使用多視圖技術(shù)渗钉,Spring支持多個(gè)視圖解析器彤恶,它會(huì)按照配置的順序依次匹配合適的視圖。通過order
屬性可以指定順序鳄橘,order
值越大順序越靠后声离,下面是個(gè)例子:
<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>
如果到最后Spring一個(gè)視圖解析都沒有匹配上就會(huì)拋出一個(gè)ServletException
。
1.4.2 重定向視圖
特殊的redirect:
前綴允許你完成重定向操作瘫怜。如果你返回的一個(gè)視圖名字擁有redirect:
前綴术徊,UrlBasedViewResolver
(及其子類)會(huì)識(shí)別出這個(gè)前綴,剩下的視圖名字會(huì)被當(dāng)做重定向的URL鲸湃。一個(gè)邏輯視圖名像這樣的:redirect:/myapp/some/resource
會(huì)被重定向到當(dāng)前的Servlet上下文中赠涮,而redirect:http://myhost.com/some/arbitrary/path
這個(gè)會(huì)重定向到指定的URL子寓。
1.4.3 轉(zhuǎn)發(fā)視圖
幾乎與重定向一樣,只不過使用forward:
前綴笋除。
1.5 使用flash attributes
Flash attributes提供了一種方式斜友,讓一個(gè)請(qǐng)求存儲(chǔ)屬性來讓另一個(gè)請(qǐng)求使用。通常用于重定向時(shí)垃它,例如Post/Redirect/Get模式鲜屏。Flash attributes在重定向前被臨時(shí)存儲(chǔ),當(dāng)重定向后被置為可用并被立即移除国拇。
Spring MVC有兩個(gè)主要的抽象支持flash attributes洛史。FlashMap
被用來承載flash attributes,FlashMapManager
被用來存儲(chǔ)酱吝,取回和管理FlashMap
實(shí)例也殖。
使用注解的controllers通常不需要直接使用FlashMap
。一個(gè)@RequestMapping
方法可以接收一個(gè)RedirectAttributes
類型的參數(shù)掉瞳,在重定向場(chǎng)景使用它添加flash attributes毕源。
1.6 構(gòu)建URIs
Spring MVC提供了一種機(jī)制用來構(gòu)建并且編碼一個(gè)URI使用UriComponentsBuilder
和UriComponents
。
例如你可以拓展和編碼一個(gè)URI模版字符串:
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://example.com/hotels/{hotel}/bookings/{booking}").build();
URI uri = uriComponents.expand("42", "21").encode().toUri();
注意UriComponents
是不可變的陕习,expand()
和 encode()
操作返回一個(gè)新的實(shí)例霎褐。
你還可以使用一個(gè)單獨(dú)的URI components:
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
在一個(gè)Servlet環(huán)境ServletUriComponentsBuilder
子類提供了一個(gè)靜態(tài)工廠方法從一個(gè)Servlet requests中復(fù)制可用的URL信息:
HttpServletRequest request = ...
// 復(fù)用 host, scheme, port, path and query string
// 替換 the "accountId" 查詢參數(shù)
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
1.7 Spring 文件上傳支持
Spring內(nèi)置的multipart支持在web應(yīng)用程序處理文件上傳。Spring針對(duì)使用Apache Commons FileUpload的用戶提供了一個(gè)MultipartResolver
實(shí)現(xiàn)该镣,另一個(gè)實(shí)現(xiàn)是針對(duì)使用Servlet 3.0 multipart request parsing的用戶冻璃。
默認(rèn)的,Spring不處理文件上傳损合,因?yàn)橐恍╅_發(fā)者想自己處理文件上傳省艳。你可以通過添加一個(gè)multipart解析器到web應(yīng)用程序的上下文中來啟用Spring文件上傳處理。每一個(gè)請(qǐng)求都會(huì)被檢查是否包含文件上傳的部分嫁审。如果沒有找到文件上傳的部分跋炕,請(qǐng)求將會(huì)繼續(xù)。如果在一個(gè)請(qǐng)求中找到了文件上傳部分律适,MultipartResolver
將會(huì)被使用辐烂。經(jīng)過處理后請(qǐng)求里的文件上傳的屬性像其他別的屬性被對(duì)待。
1.7.1 MultipartResolver與Commons FileUpload
下面的例子展示了如何配置CommonsMultipartResolver
:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大的文件尺寸(bytes) -->
<property name="maxUploadSize" value="100000"/>
</bean>
你還需要添加Commons FileUpload的依賴:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
當(dāng)Spring DispatcherServlet
發(fā)現(xiàn)一個(gè)上傳文件的請(qǐng)求捂贿,它會(huì)激活配置的MultipartResolver
并把請(qǐng)求傳遞過去纠修。解析器會(huì)包裝當(dāng)前的HttpServletRequest
成為一個(gè)MultipartHttpServletRequest
來支持文件上傳。使用MultipartHttpServletRequest
你可以獲得文件上傳數(shù)據(jù)的相關(guān)信息也可以訪問到文件上傳的數(shù)據(jù)厂僧。
1.7.2 MultipartResolver與Servlet 3.0
為了使用基于Servlet 3.0的上傳文件處理你需要在web.xml
中使用"multipart-config"
標(biāo)記DispatcherServlet
扣草。還有編程式、使用javax.servlet.annotation.MultipartConfig
注解自定義Servlet。
一旦配置好后你需要添加StandardServletMultipartResolver
到你的Spring配置:
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>
(只要不是關(guān)于Spring的配置一般都沒有例子辰妙,感興趣可以自行搜索)
1.7.3 處理從表單上傳的文件
首先創(chuàng)建一個(gè)表單來允許用戶上傳文件:
<html>
<head>
<title>Upload a file please</title>
</head>
<body>
<h1>Please upload a file</h1>
<form method="post" action="/form" enctype="multipart/form-data">
<input type="text" name="name"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>
</body>
</html>
下一步創(chuàng)建一個(gè)controller處理文件上傳鹰祸。
@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";
}
}
當(dāng)使用Servlet 3.0文件上傳解析時(shí)你還可以使用javax.servlet.http.Part
作為方法參數(shù):
@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";
}
}
1.8 處理異常
@ExceptionHandler
注解可以在一個(gè)controller里或者一個(gè)有@ControllerAdvice
注解的類里處理異常。定義在一個(gè)controller里只對(duì)那個(gè)controller里@RequestMapping
方法產(chǎn)生的異常有效上岗;定義在@ControllerAdvice
類時(shí)對(duì)所有的controller生效福荸。下面這個(gè)例子只對(duì)該controller本地生效:
@Controller
public class SimpleController {
// @RequestMapping 方法省略 ...
@ExceptionHandler(IOException.class)
public ResponseEntity<String> handleIOException(IOException ex) {
// prepare responseEntity
return responseEntity;
}
}
下面是在@ControllerAdvice
注解的類中使用:
@ControllerAdvice
public class SimpleExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleIOException(Exception ex) {
// prepare responseEntity
return responseEntity;
}
}
所有Controller產(chǎn)生的異常都會(huì)被捕獲蕴坪。
自定義默認(rèn)Servlet容器錯(cuò)誤頁面
你可以在web.xml
聲明一個(gè)<error-page>
元素:
<error-page>
<location>/error</location>
</error-page>
或者指定一個(gè)HTTP狀態(tài)碼:
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
注意:實(shí)際的地址可以是一個(gè)JSP頁面或者容器內(nèi)其他URL包括一個(gè)@Controller
方法肴掷。
@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;
}
}
1.9 配置Spring MVC
1.9.1 啟用MVC
Java config方式
@Configuration
@EnableWebMvc
public class WebConfig {
}
MVC XML Namespace方式
<?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>
1.9.2 自定義默認(rèn)配置
為了使用Java自定義默認(rèn)配置你僅僅需要實(shí)現(xiàn)WebMvcConfigurer
接口或者繼承WebMvcConfigurerAdapter
類并重寫你需要的方法。
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
protected void addFormatters(FormatterRegistry registry) {
// Add formatters and/or converters
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Configure the list of HttpMessageConverters to use
}
}
自定義<mvc:annotation-driven />
默認(rèn)配置:
<mvc:annotation-driven conversion-service="conversionService">
<mvc:message-converters>
<bean class="org.example.MyHttpMessageConverter"/>
<bean class="org.example.MyOtherHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<list>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyOtherFormatter"/>
</list>
</property>
</bean>
1.9.3 攔截器
你可以配置HandlerInterceptors
或者WebRequestInterceptors
應(yīng)用在所有即將到來的請(qǐng)求背传,或者只匹配特殊的URL路徑呆瞻。
一個(gè)用Java注冊(cè)攔截器的例子:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/").excludePathPatterns("/admin/");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
或者在XML文件中使用<mvc:interceptors>
元素:
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
<mvc:interceptor>
<mvc:mapping path="/"/>
<mvc:exclude-mapping path="/admin/"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="org.example.SecurityInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
1.9.4 視圖控制器
這個(gè)一個(gè)快捷方式定義一個(gè)ParameterizableViewController
當(dāng)執(zhí)行時(shí)立即跳轉(zhuǎn)到一個(gè)視圖。使用它在靜態(tài)視圖的場(chǎng)景径玖,當(dāng)在視圖生成相應(yīng)之前沒有Java controller邏輯執(zhí)行痴脾。
一個(gè)Java配置例子,使/
請(qǐng)求跳轉(zhuǎn)到一個(gè)叫做home
的視圖:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
同樣的配置梳星,在XML中使用<mvc:view-controller>
元素:
<mvc:view-controller path="/" view-name="home"/>
1.9.5 視圖解析器
配置一個(gè)velocity模版引擎視圖解析器:
<bean id="velocityViewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="suffix" value=".vm" />
<property name="contentType" value="text/html;charset=utf-8" />
<property name="exposeSessionAttributes" value="true" />
<property name="allowSessionOverride" value="true" />
<property name="toolboxConfigLocation" value="/WEB-INF/Toolbox.xml" />
</bean>
配置一個(gè)JSP視圖解析器:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/view/"
p:suffix=".jsp"/>
1.9.6 靜態(tài)Resources服務(wù)
這個(gè)選項(xiàng)允許在一個(gè)特殊的URL路徑下請(qǐng)求靜態(tài)資源赞赖,由ResourceHttpRequestHandler
服務(wù)于任何列表中的Resource
位置。這提供了一種方便的方法從位置服務(wù)靜態(tài)資源而不是從web應(yīng)用程序根目錄冤灾,包括classpath的位置前域。處理器還會(huì)正確的計(jì)算Last-Modified
頭,這樣對(duì)于那些已經(jīng)緩存在客戶端的資源一個(gè)304
狀態(tài)碼將會(huì)被返回韵吨,避免不必要的間接開銷匿垄。
例如使用/resources/**
URL模式來服務(wù)靜態(tài)資源,靜態(tài)資源文件目錄是web應(yīng)用程序根目錄下的public-resources
文件夾归粉,使用Java配置:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
}
}
用XML配置:
<mvc:resources mapping="/resources/**" location="/public-resources/"/>
設(shè)置一個(gè)1年的有效時(shí)間:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926);
}
}
XML配置:
<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>
mapping
屬性必須是一個(gè)可以被SimpleUrlHandlerMapping
使用的Ant模式椿疗,location
必須指定一個(gè)或多個(gè)有效的資源文件夾地址。多個(gè)資源位置可以使用逗號(hào)分割的一列值糠悼。例如使用web應(yīng)用程序的根目錄和/META-INF/public-web-resources/
:
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "classpath:/META-INF/public-web-resources/");
}
}
使用XML配置:
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/public-web-resources/"/>
當(dāng)一個(gè)新版本應(yīng)用程序被部署時(shí)靜態(tài)資源可能改變届榄,推薦你合并一個(gè)版本字符串到映射模式,強(qiáng)制客戶端請(qǐng)求新版本的資源倔喂。版本化的URL已經(jīng)被內(nèi)置在框架里铝条,可以通過在資源處理器里配置一個(gè)資源鏈來啟用。
內(nèi)置的VersionResourceResolver
可以被配置為不同的策略滴劲。例如攻晒,一個(gè)FixedVersionStrategy
可以使用一個(gè)屬性、一個(gè)日期或者其他的東西來作為版本班挖。一個(gè)ContentVersionStrategy
使用一個(gè)由資源的內(nèi)容生產(chǎn)的MD5字符串作為版本鲁捏。
ContentVersionStrategy
是一個(gè)不錯(cuò)的默認(rèn)的選擇,除了有些請(qǐng)求不能使用(例如,與JavaScript module loaders)给梅。你可以為不同的URL模式配置不同的版本策略假丧。請(qǐng)牢記根據(jù)內(nèi)容來計(jì)算版本的做法是消耗昂貴的,因此資源鏈緩存在產(chǎn)品環(huán)境一定要開啟动羽。
Java配置示例:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/")
.addResourceLocations("/public-resources/")
.resourceChain(true).addResolver(
new VersionResourceResolver().addContentVersionStrategy("/"));
}
}
XML示例:
<mvc:resources mapping="/resources/" location="/public-resources/">
<mvc:resource-chain>
<mvc:resource-cache />
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/"/>
</mvc:version-resolver>
</mvc:resolvers>
</mvc:resource-chain>
</mvc:resources>
為了使上面配置生效包帚,應(yīng)用程序必須使用版本渲染URL。最簡(jiǎn)單的方式是配置ResourceUrlEncodingFilter
包裝response重寫encodeURL
方法运吓。這個(gè)對(duì)JSP, FreeMarker, Velocity, 和其他任何調(diào)用response encodeURL
方法的視圖技術(shù)都有效渴邦。
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.