Spring學(xué)習(xí)手冊(cè)(16)—— 定義Controller

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í),我們按照如下順序選擇最佳匹配:

  1. 含有更少數(shù)量URL參數(shù)和通配符的獲得最佳匹配:如"/hotels/{hotel}/"含有1個(gè)URL參數(shù)和1個(gè)通配符,而"/hotels/{hotel}/"含有1個(gè)URL參數(shù)和2個(gè)通配符。因此若URL匹配該兩種模式,則最終匹配到"/hotels/{hotel}/";
  2. 若兩個(gè)URL模版擁有相同數(shù)量的URL參數(shù)和通配符,則URL模版最長(zhǎng)的為最佳匹配:如"/foo/bar"和"/foo/"擁有相同數(shù)量的URL參數(shù)和通配符,但是由于"/foo/bar*"更長(zhǎng)(更多的字符),所以其為最佳匹配;
  3. 當(dāng)URL模版含有相同的URL參數(shù)和通配符數(shù)量且長(zhǎng)度相等時(shí),則更少通配符的為最佳匹配:如“/hotels/{hotel}”和“/hotels/*”比較,前者為最佳匹配;
特殊規(guī)則
  1. 默認(rèn)映射路徑/**將弱于任何一個(gè)模式,如“/api/{a}//{c}”為最佳匹配;
  2. 如前綴模版/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ù)類型以及返回類型等)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,362評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,805評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,896評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,062評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,175評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,489評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,826評(píng)論 18 139
  • 翻譯自Spring官方文檔 4.1.2版本 相關(guān)文章: Spring參考手冊(cè) 1 Spring Framework...
    liycode閱讀 703評(píng)論 0 2
  • Spring的模型-視圖-控制器(MVC)框架是圍繞一個(gè)DispatcherServlet來(lái)設(shè)計(jì)的,這個(gè)Servl...
    alexpdh閱讀 2,665評(píng)論 0 3
  • 1、Spring MVC請(qǐng)求流程 (1)初始化:(對(duì)DispatcherServlet和ContextLoderL...
    拾壹北閱讀 1,963評(píng)論 0 12
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,922評(píng)論 6 342