Spring MVC 學習總結(二)——控制器定義與@RequestMapping詳解
一、控制器定義
控制器提供訪問應用程式的行為,通常通過服務介面定義或註解定義兩種方法實現。 控制器解析使用者的請求並將其轉換為一個模型。在Spring MVC中一個控制器可以包含多個Action(動作、方法)。
1.1、實現介面Controller定義控制器
Controller是一個介面,處在包org.springframework.web.servlet.mvc下,介面中只有一個未實現的方法,具體的介面如下所示:
package org.springframework.web.servlet.mvc; import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; //實現該介面的類獲得控制器功能與型別, 解析使用者的請求並將其轉換為一個模型 public interface Controller { //處理請求且返回一個模型與檢視物件 ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }
在自定義控制器前先建立一個基於maven的web專案,新增包的依賴,pom.xml檔案如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupIdView Code>com.zhangguo</groupId> <artifactId>SpringMVC02</artifactId> <version>0.0.1</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!--Spring框架核心庫 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP應用程式介面 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> </dependencies> </project>
如果不配置scope,會把jar包釋出,會跟容器裡的jar包衝突、scope要設定為provided,由容器提供,不會發布(或者不配這兩個依賴,在專案的Java BuildPath的Libraries裡新增Server Runtime)目前scope可以使用5個值:
compile:預設值,適用於所有階段,會隨著專案一起釋出。
provided:類似compile,期望JDK、容器或使用者會提供這個依賴。如servlet.jar。
runtime:只在執行時使用,如JDBC驅動,適用執行和測試階段。test,只在測試時使用,用於編譯和執行測試程式碼。不會隨專案釋出。
system:類似provided,需要顯式提供包含依賴的jar,Maven不會在Repository中查詢它。
建立一個名為Foo的類,實現介面Controller,重寫handleRequest方法,程式碼如下:
package com.zhangguo.springmvc02.controllers; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; /* * 定義控制器 */ public class FooController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //返回一個模型檢視物件,指定路徑,指定模型的名稱為message,值為一段字串 return new ModelAndView("foo/index", "message", "Hello,我是通過實現介面定義的一個控制器"); } }
在WEB-INF/views/foo目錄下建立一個名為index.jsp的檢視,內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Foo</title> </head> <body> ${message} </body> </html>
修改springmvc-servlet.xml配置檔案,增加一個控制器bean的宣告,具體內容如下:
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 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-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <!-- 自動掃描包,實現支援註解的IOC --> <context:component-scan base-package="com.zhangguo.springmvc02" /> <!-- Spring MVC不處理靜態資源 --> <mvc:default-servlet-handler /> <!-- 支援mvc註解驅動 --> <mvc:annotation-driven /> <!-- 檢視解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 字首 --> <property name="prefix" value="/WEB-INF/views/" /> <!-- 字尾 --> <property name="suffix" value=".jsp" /> </bean> <bean name="/foo" class="com.zhangguo.springmvc02.controllers.FooController"></bean> </beans>
基中name是訪問路徑,class是自定義的控制器的全名稱。執行後的結果如下:
小結:實現介面Controller定義控制器是較老的辦法,缺點是:一個控制器中只有一個Action,如果要多個Action則需要定義多個Controller;定義的方式比較麻煩;Spring 2.5以後採用註解的方式定義解決這引起問題。
1.2、使用註解@Controller定義控制器
org.springframework.stereotype.Controller註解型別用於宣告Spring類的例項是一個控制器(在講IOC時還提到了另外3個註解);Spring可以使用掃描機制來找到應用程式中所有基於註解的控制器類,為了保證Spring能找到你的控制器,需要在配置檔案中宣告元件掃描。
建立一個名了Bar的類,定義為一個控制器,類的具體實現如下:
package com.zhangguo.springmvc02.controllers; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; /** * 定義控制器 */ //BarController類的例項是一個控制器,會自動新增到Spring上下文中 @Controller public class BarController { //對映訪問路徑 @RequestMapping("/bar") public String index(Model model){ //Spring MVC會自動例項化一個Model物件用於向檢視中傳值 model.addAttribute("message", "這是通過註解定義的一個控制器中的Action"); //返回檢視位置 return "foo/index"; } }
還要需要修改Spring mvc配置檔案,啟用自動元件掃描功能,在beans中增加如下配置:
<!-- 自動掃描包,實現支援註解的IOC --> <context:component-scan base-package="com.zhangguo.springmvc02" />
base-package屬性用於指定掃描的基礎包,可以縮小掃描的範圍。執行結果如下:
小結:從程式碼與執行結果可以看出BarController與FooController同時都指定了一個檢視foo/index.jsp,但是頁面結果的結果是不一樣的,從這裡可以看出檢視是被複用的,而控制器與檢視之間是弱偶合關係。
二、@RequestMapping詳解
@RequestMapping註釋用於對映url到控制器類或一個特定的處理程式方法。可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作為父路徑。該註解共有8個屬性,註解原始碼如下:
package org.springframework.web.bind.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.Callable; import org.springframework.core.annotation.AliasFor; /** * 用於對映url到控制器類或一個特定的處理程式方法. */ //該註解只能用於方法或型別上 @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { /** * 指定對映的名稱 */ String name() default ""; /** * 指定請求的路徑對映,指定的地址可以是uri模板,別名為path */ @AliasFor("path") String[] value() default {}; /** 別名為value,使用path更加形象 * 只有用在一個Servlet環境:路徑對映URI(例如“/myPath.do”)。 * Ant風格的路徑模式,同時也支援(例如,“/myPath/*.do”)。在方法層面,在主要的對映在型別級別表示相對路徑(例如,“edit.do”) * 的支援。路徑對映的URI可能包含佔位符(例如“/$ {}連線”) */ @AliasFor("value") String[] path() default {}; /** * 指定請求謂詞的型別如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. 收窄請求範圍 The * HTTP request methods to map to, narrowing the primary mapping: GET, POST, * HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. */ RequestMethod[] method() default {}; /** * 對映請求的引數,收窄請求範圍 The parameters of the mapped request, narrowing the * primary mapping. */ String[]params() default {}; /** * 對映請求頭部,收窄請求範圍 The headers of the mapped request, narrowing the primary * mapping. RequestMapping(value = "/something", headers = * "content-type=text/*") */ String[] headers() default {}; /** * 指定處理請求的提交內容型別(Content-Type),例如application/json, text/html,收窄請求範圍 The * consumable media types of the mapped request, narrowing the primary * mapping. */ String[] consumes() default {}; /** * 指定返回的內容型別,僅當request請求頭中的(Accept)型別中包含該指定型別才返回 The producible media types * of the mapped request, narrowing the primary mapping. produces = * "text/plain" produces = {"text/plain", "application/*"} produces = * "application/json; charset=UTF-8" */ String[] produces() default {}; }
從上面的原始碼可以發現除了name基本都是陣列型別,在設定時我們可以指定單個值,如@RequestMapping(value="/foo");也可以同時指定多個值如:@RequestMapping(value={"/foo","/bar"})。
2.1、value 屬性指定對映路徑或URL模板
指定請求的實際地址,指定的地址可以是URL模板,正則表示式或路徑佔位,該屬性與path互為別名關係,@RequestMapping("/foo")} 與 @RequestMapping(path="/foo")相同。該屬性是使用最頻繁,最重要的一個屬性,如果只指定該屬性時可以把value略去。Spring Framework 4.2引入了一流的支援宣告和查詢註釋屬性的別名。@AliasFor註解可用於宣告一雙別名屬性,來給註解的屬性起別名, 讓使用註解時, 更加的容易理解(比如給value屬性起別名, 更容易讓人理解)。先看一個官網的示例:
@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"; } }
2.1.1、指定具體路徑字元
2.1.1.1 只註解方法
@Controller public class FooBarController { @RequestMapping("/action1") public String action1(){ return "foo/index"; } }
訪問路徑:http://localhost:8087/SpringMVC02/action1
2.1.1.2 同時註解類與方法
@Controller @RequestMapping("/foobar") public class FooBarController { @RequestMapping("/action1") public String action1(){ return "foo/index"; } }
訪問路徑:http://localhost:8087/SpringMVC02/foobar/action1
需要先指定類的路徑再指定方法的路徑
2.1.1.3 當value為空值
註解在方法上時,如果value為空則表示該方法為類下預設的Action。
@Controller @RequestMapping("/foobar") public class FooBarController { @RequestMapping("/action1") public String action1(Model model){ //在模型中新增屬性message值為action1,渲染頁面時使用 model.addAttribute("message", "action1"); return "foo/index"; } @RequestMapping public String action2(Model model){ //在模型中新增屬性message值為action2,渲染頁面時使用 model.addAttribute("message", "action2"); return "foo/index"; } }
訪問action2的路徑是:http://localhost:8087/SpringMVC02/foobar,如果加上action2就錯誤了。
註解在類上時,當value為空值則為預設的控制器,可以用於設定專案的起始頁。
@Controller @RequestMapping public class FooBarController { @RequestMapping("/action1") public String action1(Model model){ //在模型中新增屬性message值為action1,渲染頁面時使用 model.addAttribute("message", "action1"); return "foo/index"; } @RequestMapping public String action2(Model model){ //在模型中新增屬性message值為action2,渲染頁面時使用 model.addAttribute("message", "action2"); return "foo/index"; } }
訪問路徑:http://localhost:8087/SpringMVC02/,同時省去了控制器名與Action名稱,可用於歡迎頁。
訪問action1的路徑是:http://localhost:8087/SpringMVC02/action1
2.1.2、路徑變數佔位,URI模板模式
在Spring MVC可以使用@PathVariable 註釋方法引數的值繫結到一個URI模板變數。
@RequestMapping("/action3/{p1}/{p2}") public String action3(@PathVariable int p1,@PathVariable int p2,Model model){ model.addAttribute("message", p1+p2); return "foo/index"; }
執行結果:
使用路徑變數的好處:使路徑變得更加簡潔;獲得引數更加方便,框架會自動進行型別轉換。通過路徑變數的型別可以約束訪問引數,如果型別不一樣,則訪問不到action,如這裡訪問是的路徑是/action3/1/a,則路徑與方法不匹配,而不會是引數轉換失敗。
2.1.3、正則表示式模式的URI模板
@RequestMapping(value="/action4/{id:\\d{6}}-{name:[a-z]{3}}") public String action4(@PathVariable int id,@PathVariable String name,Model model){ model.addAttribute("message", "id:"+id+" name:"+name); return "foo/index"; }
正則要求id必須為6位的數字,而name必須為3位小寫字母,訪問結果如下:
2.1.4、矩陣變數@MatrixVariable
矩陣變數可以出現在任何路徑段,每個矩陣變數用“;”分隔。例如:“/汽車;顏色=紅;年=2012”。多個值可以是“,”分隔“顏色=紅、綠、藍”或變數名稱可以重複“顏色=紅;顏色=綠色;顏色=藍”,如下所示:
// GET /pets/42;q=11;r=22 @RequestMapping(value = "/pets/{petId}") public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
// 矩陣變數 @RequestMapping(value = "/action5/{name}") public String action5(Model model, @PathVariable String name, //路徑變數,用於獲得路徑中的變數name的值 @MatrixVariable String r, @MatrixVariable(required = true) String g, //引數g是必須的 @MatrixVariable(defaultValue = "99", required = false) String b) { //引數b不是必須的,預設值是99 model.addAttribute("message", name + " is #" + r + g + b); return "foo/index"; } //Get http://localhost:8087/SpringMVC02/action5/the%20book%20color;r=33;g=66 //the book color is #336699
預設是不允許使用矩陣變數的,需要設定配置文中的RequestMappingHandlerMapping的屬性removeSemicolonContent為false;在annotation-driven中增加屬性enable-matrix-variables="true",修改後的springmvc-servlet.xml檔案如下:
<!-- 支援mvc註解驅動 --> <mvc:annotation-driven enable-matrix-variables="true" /> <!-- 配置對映媒體型別的策略 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="removeSemicolonContent" value="false" /> </bean>
訪問結果如下:
2.1.5、Ant風格路徑模式
@RequestMapping註解也支援ant風格的路徑模式,如/myPath/*.do,/owners/*/pets/{petId},示例程式碼如下:
//Ant風格路徑模式 @RequestMapping(value = "/action6/*.do") public String action6(Model model){ model.addAttribute("message","Ant風格路徑模式"); return "foo/index"; }
執行結果:
當然還有關於路徑匹配的規則,特殊的優先順序高過一般的,更多規則可以參考官方幫助。
2.2、method屬性指定謂詞型別
用於約束請求的謂詞型別,可以收窄請求範圍。指定請求謂詞的型別如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE,如下程式碼所示:
//謂詞型別 @RequestMapping(value = "/action6",method={RequestMethod.POST,RequestMethod.DELETE}) public String action6(Model model) { model.addAttribute("message", "請求謂詞只能是POST與DELETE"); return "foo/index"; }
要訪問action7請求謂詞型別必須是POST或者為DELETE,當我們從瀏覽器的URL欄中直接請求時為一個GET請求,則結果是405,如下所示:
如果將POST修改為GET則正常了,如下所示:
//謂詞型別