HelloSpringMVC文章帶我們一起學(xué)習(xí)如何使用了SpringMVC框架:創(chuàng)建Web項(xiàng)目工程、增加項(xiàng)目依賴Jar包、定義web.xml和HelloWeb-servler.xml配置文件、定義控制器(Controller)和定義視圖文件,最終將該項(xiàng)目部署到Tomcat服務(wù)器上,我們完成了第一個(gè)SpringMVC項(xiàng)目。本文我們深入學(xué)習(xí)如何定義一個(gè)Controller。
一 、控制器(Controller)
通過(guò)服務(wù)器接口來(lái)定義應(yīng)用程序行為,而控制器(后稱Controller)為用戶提供訪問(wèn)這些行為的功能。Controller將攔截用戶輸入并通過(guò)邏輯處理后轉(zhuǎn)換為模型(Model),這些模型將會(huì)被視圖(View)渲染成用戶可見(jiàn)的展現(xiàn)方式。Spring可以讓我們輕易實(shí)現(xiàn)各種類型的Controller。
Spring2.5之后提供了一套基于注解的方式來(lái)定義和實(shí)現(xiàn)Controller功能,這里我們也主要學(xué)習(xí)注解的方式定義和實(shí)現(xiàn)Controller。
為了讓SpringMVC自動(dòng)檢測(cè)并實(shí)例化我們用注解定義的Controller,我們需要在增加以下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="..."/>
<!-- ... -->
</beans>
其中base-package
為你使用注解定義的的Controller的包路徑。
二、定義Controller
2.1 最簡(jiǎn)單的Controller
@Controller
注解可以指定一個(gè)類作為服務(wù)器的控制器(Controller),當(dāng)然我們還需要@RequestMapping
注解來(lái)映射具體的控制器處理方法。有了這兩個(gè)注解我們就可以完成最簡(jiǎn)單的一個(gè)Controller的定義了。
@Controller
public class HelloWorldController {
@RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
如上,我們用@Controller
將HelloWorldController標(biāo)示為一個(gè)Controller,然后使用@RequestMapping
將helloWorld方法與訪問(wèn)請(qǐng)求路徑/helloWorld
關(guān)聯(lián),這樣當(dāng)用戶使用http://127.0.0.1:8080/helloWorld
訪問(wèn)網(wǎng)站時(shí)(我們假設(shè)部署在本機(jī)且應(yīng)用部署在根目錄),將會(huì)被helloWorld方法處理,在Model內(nèi)填充HelloWorld數(shù)據(jù),并且返回一個(gè)String,其值為"helloWorld",它將被影射到名為helloWorld的視圖View(我們后面會(huì)學(xué)習(xí)到)。
2.2 請(qǐng)求方法映射(@RequestMapping
)
我們使用@RequestMapping
注解將URLs的請(qǐng)求路徑映射到一個(gè)類或者一個(gè)特定的處理方法上。也就是說(shuō)@RequestMapping
注解可以使用在類上也可以使用在具體的方法上。作用在類上的@RequestMapping
注解使得所有請(qǐng)求必需基于該路徑,而方法級(jí)別的注解路徑進(jìn)一步縮小匹配范圍,當(dāng)然我們的方法級(jí)別的注解上使用請(qǐng)求方式篩選出此次請(qǐng)求對(duì)應(yīng)的處理方法。也許但從文字上描述不容易理解,接下來(lái)我們使用一個(gè)具體的例子來(lái)理解:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(path = "/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(path = "/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
以上代碼出自PetCare項(xiàng)目,我們來(lái)一起學(xué)習(xí)分析下這段代碼。
- 類級(jí)別的注解
@RequestMapping("/appointments")
注解在AppointmentsController類上,該控制器將匹配所有以/appointments
開(kāi)始的請(qǐng)求路徑; - 方法級(jí)別的注解
@RequestMapping(method = RequestMethod.GET)
作用在get()方法上,則請(qǐng)求路徑為/appointments
的GET請(qǐng)求會(huì)被影射到該方法上; - 方法級(jí)別的注解
@RequestMapping(path = "/new", method = RequestMethod.GET)
作用在getNewForm()上,則請(qǐng)求路徑為/appointments/new
的GET請(qǐng)求會(huì)被影射到該方法上; - 方法級(jí)別的注解
@RequestMapping(method = RequestMethod.POST)
作用在add()方法上,則請(qǐng)求路徑為/appointments
的POST請(qǐng)求會(huì)被該方法處理;
......
也許你已經(jīng)注意到@RequestMapping(path = "/{day}", method = RequestMethod.GET)
的注解,這個(gè)會(huì)在后面請(qǐng)求參數(shù)進(jìn)行講解,這里可暫時(shí)不用太在意。
當(dāng)然,類級(jí)別的@RequestMapping
注解并不是必須需要的,當(dāng)我們不使用類級(jí)別的注解是,則所有的方法級(jí)別的注解的路徑就變成了絕對(duì)路徑了,不再使用相對(duì)路徑方式。如下代碼所示:
@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());
}
}
如上代碼所示,請(qǐng)求路徑/
會(huì)由welcomeHandler()方法處理,而請(qǐng)求路徑/vets
會(huì)由vetsHandler()方法處理。也許你注意到上面的所有的@RrequestMapping
沒(méi)有設(shè)置GET或POST,這種情況下,會(huì)匹配所有類型請(qǐng)求。
2.3 組合的@RequestMapping變量
Spring 4.3引入了方法級(jí)別的組合@RequestMapping變量,它能極大的簡(jiǎn)化影射配置并且在語(yǔ)義更易理解:
-
@GetMapping
:相當(dāng)于@RequestMapping( method = RequestMethod.GET)
-
@PostMapping
:相當(dāng)于@RequestMapping( method = RequestMethod.POST)
-
@PutMapping
: Restful風(fēng)格下的Put請(qǐng)求,相當(dāng)于@RequestMapping(method= RequestMethod.PUT)
-
@DeleteMapping
:Restful風(fēng)格下的Delete請(qǐng)求,相當(dāng)于@RequestMapping(method= RequestMethod.DELETE)
-
@PatchMapping
:相當(dāng)于@RequestMapping(method= RequestMethod.PATCH)
在大多數(shù)的Web應(yīng)用中,GetMapping和PostMapping使用更為普遍。
三、高級(jí)映射路徑參數(shù)使用
本節(jié)我們講學(xué)習(xí)如何從 @RequestMapping
里設(shè)置的URL中獲取我們想得到的信息。主要采用@PathVariable
和@MatrixVariable
兩種方式實(shí)現(xiàn)。
3.1 使用@PathVariable
獲取URL模版參數(shù)
1. 最簡(jiǎn)單的方式
我們可以使用URL模版
(URL templates)方便的獲取URL中的某一部分,如我們使用@RequestMapping(/owners/{userId})
注解某一Controller的方法,當(dāng)我們使用如下URL訪問(wèn)服務(wù)時(shí)http://www.example.com/owners/fred
,(假設(shè)我們服務(wù)部署域名為www.example.com),那么userId的參數(shù)的值就為fred。讓我們看個(gè)例子,來(lái)進(jìn)一步理解。
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
如上代碼所示,當(dāng)訪問(wèn).../owners/fred
時(shí),findOwner方法的參數(shù)ownerId的值將為fred。我們使用@PathVariable
注解標(biāo)示方法的參數(shù),并且明確指明該參數(shù)對(duì)應(yīng)URL中的ownerId
所代表的值。當(dāng)然,當(dāng)應(yīng)用編譯包含debugging信息且URL中的模版名和方法參數(shù)名相同時(shí),可省略@PathVariable
的明確指明,如下所示:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
當(dāng)然像@ RequestMapping
可以作用于類上,我們同樣可以從類上獲得URL模版值信息,如下代碼所示:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
2. URL中存在多個(gè)模版時(shí)
當(dāng)URL中存在多個(gè)模版時(shí),我們可以在方法中使用多個(gè)@PathVariable
來(lái)標(biāo)示形參,如下代碼所示:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
當(dāng)我們具體的訪問(wèn)路徑被影射到findPet方法上時(shí),URL中的{ownerId}和{petId}位置的值會(huì)自動(dòng)映射到findPet方法上參數(shù)ownerId和petId的值。
當(dāng)然,當(dāng)URL中模版過(guò)多時(shí),我們可以使用任意數(shù)量的@PathVariable
來(lái)解決,但是這樣導(dǎo)致方法參數(shù)列表過(guò)長(zhǎng),我們可以使用如下方式來(lái)解決該問(wèn)題:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable Map<String,String> urlTemplates, Model model) {
String ownerId = urlTemplates.get("ownerId");
String petId = urlTemplates.get("petId");
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
我們使用@PathVariable
注解來(lái)注解Map<String,String>
參數(shù),這樣Spring會(huì)自動(dòng)把所有的URL模版填充到該Map中,key為模版名,value為值。
3.URL模版上使用正則表達(dá)式
有時(shí)候我們需要更明確的URL模版值信息,如我們提交的請(qǐng)求路徑為/spring-web/spring-web-3.0.5.jar
,我們希望獲得版本號(hào)和后綴信息,我們可以使用如下方式來(lái)獲取:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}
如上代碼,參數(shù)version將被'3.0.5'填充,參數(shù)extension將被'jar'填充。
4. 最佳路徑匹配
當(dāng)一個(gè)URL匹配多個(gè)模版時(shí),我們按照如下順序選擇最佳匹配:
- 含有更少數(shù)量URL參數(shù)和通配符的獲得最佳匹配:如"/hotels/{hotel}/"含有1個(gè)URL參數(shù)和1個(gè)通配符,而"/hotels/{hotel}/"含有1個(gè)URL參數(shù)和2個(gè)通配符。因此若URL匹配該兩種模式,則最終匹配到"/hotels/{hotel}/";
- 若兩個(gè)URL模版擁有相同數(shù)量的URL參數(shù)和通配符,則URL模版最長(zhǎng)的為最佳匹配:如"/foo/bar"和"/foo/"擁有相同數(shù)量的URL參數(shù)和通配符,但是由于"/foo/bar*"更長(zhǎng)(更多的字符),所以其為最佳匹配;
- 當(dāng)URL模版含有相同的URL參數(shù)和通配符數(shù)量且長(zhǎng)度相等時(shí),則更少通配符的為最佳匹配:如“/hotels/{hotel}”和“/hotels/*”比較,前者為最佳匹配;
特殊規(guī)則
- 默認(rèn)映射路徑
/**
將弱于任何一個(gè)模式,如“/api/{a}//{c}”為最佳匹配; - 如前綴模版
/public/**
弱于任何一個(gè)不含郵兩個(gè)通配符的模版,如“/public/path3/{a}//{c}”相對(duì)于“/public/**”,前者為最佳匹配。
3.2 Matrix Variables
RFC 3986中定義URL中的某一段路徑中可包含一系列鍵值對(duì)。這些鍵值對(duì)可在任意路徑后面跟隨,多個(gè)鍵值對(duì)件可食用";"(分號(hào))隔開(kāi),如:"/cars;color=red;year=2012"。當(dāng)一個(gè)鍵(名)含有多個(gè)值時(shí),可私用","(逗號(hào))隔開(kāi),如"color=red,green,blue"或者使用"color=red;color=green;color=blue"方式。
接下來(lái)讓我們通過(guò)幾個(gè)例子來(lái)掌握如何使用Matrix Variables:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
如上代碼,我們使用@MatrixVariable
來(lái)獲取到請(qǐng)求路徑中q的值。
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
如上代碼就略顯復(fù)雜了些,該URL路徑中不同的路徑部門中包含相同名字的q,這個(gè)時(shí)候我們@MatrixVariable
中使用name來(lái)表明獲取名為q的值,且路徑由pathVar來(lái)定義。
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
如上代碼,我們可以為q設(shè)置默認(rèn)值,當(dāng)請(qǐng)求路徑中不包含名為q的信息時(shí),則q將被賦值默認(rèn)值。
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 11, "s" : 23]
}
我們同樣可以將@MatrixVariable
來(lái)注解Map類型的參數(shù),這樣Spring會(huì)自動(dòng)將所有的Matrix 值填充到該Map。當(dāng)然如果你在@MatrixVariable
中設(shè)置pathVar,則只會(huì)填充改路徑塊下的Matrix值。
<?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>
如上,我們使用<mvc:annotation-driven enable-matrix-variables="true"/>
使得Spring支持matrix variables
。
四、總結(jié)
本文我們學(xué)習(xí)了Controller的定義以及使用@RequestMapping
定義映射方法。除了簡(jiǎn)單的將請(qǐng)求路徑映射到制定的Controller外,我們可以通過(guò)使用路徑模版和Matrix Variables方式來(lái)使處理方法獲得請(qǐng)求路徑傳入的某些值。接下來(lái)我們將詳細(xì)學(xué)習(xí)@RequestMapping定義的映射方法(允許的參數(shù)類型以及返回類型等)。