一、控制器定義
控制器提供訪問應用程序的行為,通常通過服務接口定義或注解定義兩種方法實現。 控制器解析用戶的請求並將其轉換為一個模型。在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> <groupId>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"; }
運行結果:
當然還有關於路徑匹配的規則,特殊的優先級高過一般的,更多規則可以參考官方幫助。
ANT通配符有三種:
2.1.6、@RequestMapping 來處理多個 URI
你可以將多個請求映射到一個方法上去,只需要添加一個帶有請求路徑值列表的 @RequestMapping 注解就行了。
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = { "", "/page", "page*", "view/*",
"**/msg" }) String indexMultipleMapping() { return "Hello from index multiple mapping."; } }
如你在這段代碼中所看到的,@RequestMapping 支持統配符以及ANT風格的路徑。前面這段代碼中,如下的這些 URL 都會由 indexMultipleMapping() 來處理:
localhost:8080/home
localhost:8080/home/
localhost:8080/home/page
localhost:8080/home/pageabc
localhost:8080/home/view/
localhost:8080/home/view/view
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則正常了,如下所示:
//謂詞類型 @RequestMapping(value = "/action6",method=RequestMethod.GET) public String action6(Model model) { model.addAttribute("message", "請求謂詞只能是GET"); return "foo/index"; }
Spring MVC 的 @RequestMapping 注解能夠處理 HTTP 請求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的請求默認都會是 HTTP GET 類型的。
為了能降一個請求映射到一個特定的 HTTP 方法,你需要在 @RequestMapping 中使用 method 來聲明 HTTP 請求所使用的方法類型,如下所示:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(method = RequestMethod.GET) String get() { return "Hello from get"; } @RequestMapping(method = RequestMethod.DELETE) String delete() { return "Hello from delete"; } @RequestMapping(method = RequestMethod.POST) String post() { return "Hello from post"; } @RequestMapping(method = RequestMethod.PUT) String put() { return "Hello from put"; } @RequestMapping(method = RequestMethod.PATCH) String patch() { return "Hello from patch"; } }
在上述這段代碼中, @RequestMapping 注解中的 method 元素聲明了 HTTP 請求的 HTTP 方法的類型。
所有的處理處理方法會處理從這同一個 URL( /home)進來的請求, 但要看指定的 HTTP 方法是什么來決定用哪個方法來處理。
例如,一個 POST 類型的請求 /home 會交給 post() 方法來處理,而一個 DELETE 類型的請求 /home 則會由 delete() 方法來處理。
你會看到 Spring MVC 將使用這樣相同的邏輯來映射其它的方法。
2.3、consumes屬性指定請求的Content-Type
@RequestMapping 注解的 produces 和 consumes 這兩個元素來縮小請求映射類型的范圍,達到處理生產和消費對象的目的。
指定處理請求的提交內容類型(Content-Type),例如application/json, text/html,收窄請求范圍,如果用戶發送的請求內容類型不匹配則方法不會響應請求,具體使用如下代碼所示:
package com.zhangguo.springmvc02.controllers; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/home") public class HomeController { // 請求內容類型必須為text/html,注意瀏覽器默認沒有指定Content-type @RequestMapping(value = "/action8",consumes="text/html") public String action8(Model model) { model.addAttribute("message", "請求的提交內容類型(Content-Type)是text/html"); return "foo/index"; } }
在action8的注解中約束發送到服務器的Content-Type必須是text/html類型,如果類型不一致則會報錯(415),測試結果如下:
從兩個圖的對比可以看出當內容類型為text/plain時報客戶端錯誤415,當內容類型為text/html時則響應正常,響應的結果如下:
請求的提交內容類型(Content-Type)是text/html
注意:可以使用!號,如consumes="!text/html"
2.4、produces屬性指定響應的Content-Type,約束Accept類型
指定返回的內容類型,僅當request請求頭中的(Accept)類型中包含該指定類型才返回,方法才處理客戶端的請求否則會報406錯誤,常用設置如下:
produces = "text/plain" //客戶端只接收純文本
produces = {"text/plain", "application/*"} //客戶端接收純文本與application/*類型的內容
produces = "application/json; charset=UTF-8" //客戶端接收json且編碼為utf-8
//客戶端接收json且編碼為utf-8,多數瀏覽器Accept設置的為*/*,接收任意類型 @RequestMapping(value = "/action9",produces="application/json; charset=UTF-8") public String action9(Model model) { model.addAttribute("message", "客戶端可以接收的類型是application/json; charset=UTF-8"); return "foo/index"; }
運行結果:
注意:可以使用!號,如produces="!text/html"
2.5、params屬性指定請求中必須有特定參數與值
映射請求的參數,收窄請求范圍。可以限制客戶端發送到服務器的請求參數為某些特定值或不為某些值,如下代碼所示:
//請求的參數必須包含id=215與name不等於abc @RequestMapping(value = "/action10",params={"id=215","name!=abc"}) public String action10(Model model) { model.addAttribute("message", "請求的參數必須包含id=215與name不等於abc"); return "foo/index"; }
運行結果如下:
name的值如沒有指定也是通過的;可以使用不等於;
2.6、headers屬性指定請求中必須有特定header值
映射請求頭部,收窄請求范圍。約束客戶端發送的請求頭部信息中必須包含某個特定的值或不包含某個值,作用范圍明顯大於前面講過的幾種,示例代碼如下:
//請求頭部信息中必須包含Host=localhost:8088 @RequestMapping(value = "/action11",headers="Host=localhost:8088") public String action11(Model model) { model.addAttribute("message", "請求頭部信息中必須包含Host=localhost:8088"); return "foo/index"; }
運行結果:
修改Host為8087時運行就正常了:
這里同樣可以使用!號;可以使用通配符如:Content-Type="application/*"
2.7、name屬性指定名稱
為當前映射指定一個名稱,不常用,一般不會指定。
2.8、path屬性指定路徑
先看源碼中的path與value,定義如下:
@AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {};
從Spring 4.2開始引入了@AliasFor注解,可以實現屬性的別名,如value本身並沒有特定的含義,而path會更加具體,能見名知義,通俗說可以認為兩者在使用中是一樣的如:@RequestMapping("/foo")} 與 @RequestMapping(path="/foo")相同。示例代碼如下:
//映射訪問路徑為/action12或/myaction,指定映射名稱為actionTest @RequestMapping(path ={"/action12","/myaction"},name="actionTest") public String action12(Model model) { model.addAttribute("message", "映射訪問路徑為/action12或/myaction,指定映射名稱為actionTest"); return "foo/index"; }
運行結果:
2.9、@RequestMapping 快捷方式
Spring 4.3 引入了方法級注解的變體,也被叫做 @RequestMapping 的組合注解。組合注解可以更好的表達被注解方法的語義。它們所扮演的角色就是針對 @RequestMapping 的封裝,而且成了定義端點的標准方法。
例如,@GetMapping 是一個組合注解,它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一個快捷方式。
方法級別的注解變體有如下幾個:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMappin
如下面兩個action就是基本等價的:
@RequestMapping(value = "/action3",method = RequestMethod.GET) public String action3(Model model){ model.addAttribute("msg","action3 get請求"); return "hi"; } @GetMapping("/action4") public String action4(Model model){ model.addAttribute("msg","action4 get請求"); return "hi"; }
action4的寫法要簡單一些,GetMapping與RequestMapping的具體用法一樣,源碼如下:

/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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; /** * Annotation for mapping web requests onto specific handler classes and/or * handler methods. Provides a consistent style between Servlet and Portlet * environments, with the semantics adapting to the concrete environment. * * <p><b>NOTE:</b> The set of features supported for Servlets is a superset * of the set of features supported for Portlets. The places where this applies * are marked with the label "Servlet-only" in this source file. For Servlet * environments there are some further distinctions depending on whether an * application is configured with {@literal "@MVC 3.0"} or * {@literal "@MVC 3.1"} support classes. The places where this applies are * marked with {@literal "@MVC 3.1-only"} in this source file. For more * details see the note on the new support classes added in Spring MVC 3.1 * further below. * * <p>Handler methods which are annotated with this annotation are allowed to * have very flexible signatures. They may have parameters of the following * types, in arbitrary order (except for validation results, which need to * follow right after the corresponding command object, if desired): * <ul> * <li>Request and/or response objects (Servlet API or Portlet API). * You may choose any specific request/response type, e.g. * {@link javax.servlet.ServletRequest} / {@link javax.servlet.http.HttpServletRequest} * or {@link javax.portlet.PortletRequest} / {@link javax.portlet.ActionRequest} / * {@link javax.portlet.RenderRequest}. Note that in the Portlet case, * an explicitly declared action/render argument is also used for mapping * specific request types onto a handler method (in case of no other * information given that differentiates between action and render requests). * <li>Session object (Servlet API or Portlet API): either * {@link javax.servlet.http.HttpSession} or {@link javax.portlet.PortletSession}. * An argument of this type will enforce the presence of a corresponding session. * As a consequence, such an argument will never be {@code null}. * <i>Note that session access may not be thread-safe, in particular in a * Servlet environment: Consider switching the * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setSynchronizeOnSession * "synchronizeOnSession"} flag to "true" if multiple requests are allowed to * access a session concurrently.</i> * <li>{@link org.springframework.web.context.request.WebRequest} or * {@link 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. * <li>{@link java.util.Locale} for the current request locale * (determined by the most specific locale resolver available, * i.e. the configured {@link org.springframework.web.servlet.LocaleResolver} * in a Servlet environment and the portal locale in a Portlet environment). * <li>{@link java.io.InputStream} / {@link java.io.Reader} for access * to the request's content. This will be the raw InputStream/Reader as * exposed by the Servlet/Portlet API. * <li>{@link java.io.OutputStream} / {@link java.io.Writer} for generating * the response's content. This will be the raw OutputStream/Writer as * exposed by the Servlet/Portlet API. * <li>{@link org.springframework.http.HttpMethod} for the HTTP request method</li> * <li>{@link PathVariable @PathVariable} annotated parameters (Servlet-only) * for access to URI template values (i.e. /hotels/{hotel}). Variable values will be * converted to the declared method argument type. By default, the URI template * will match against the regular expression {@code [^\.]*} (i.e. any character * other than period), but this can be changed by specifying another regular * expression, like so: /hotels/{hotel:\d+}. * Additionally, {@code @PathVariable} can be used on a * {@link java.util.Map Map<String, String>} to gain access to all * URI template variables. * <li>{@link MatrixVariable @MatrixVariable} annotated parameters (Servlet-only) * for access to name-value pairs located in URI path segments. Matrix variables * must be represented with a URI template variable. For example /hotels/{hotel} * where the incoming URL may be "/hotels/42;q=1". * Additionally, {@code @MatrixVariable} can be used on a * {@link java.util.Map Map<String, String>} to gain access to all * matrix variables in the URL or to those in a specific path variable. * <li>{@link RequestParam @RequestParam} annotated parameters for access to * specific Servlet/Portlet request parameters. Parameter values will be * converted to the declared method argument type. Additionally, * {@code @RequestParam} can be used on a {@link java.util.Map Map<String, String>} or * {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>} * method parameter to gain access to all request parameters. * <li>{@link RequestHeader @RequestHeader} annotated parameters for access to * specific Servlet/Portlet request HTTP headers. Parameter values will be * converted to the declared method argument type. Additionally, * {@code @RequestHeader} can be used on a {@link java.util.Map Map<String, String>}, * {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or * {@link org.springframework.http.HttpHeaders HttpHeaders} method parameter to * gain access to all request headers. * <li>{@link RequestBody @RequestBody} annotated parameters (Servlet-only) * for access to the Servlet request HTTP contents. The request stream will be * converted to the declared method argument type using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. Such parameters may optionally be annotated with {@code @Valid} * and also support access to validation results through an * {@link org.springframework.validation.Errors} argument. * Instead a {@link org.springframework.web.bind.MethodArgumentNotValidException} * exception is raised. * <li>{@link RequestPart @RequestPart} annotated parameters * (Servlet-only, {@literal @MVC 3.1-only}) * for access to the content * of a part of "multipart/form-data" request. The request part stream will be * converted to the declared method argument type using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. Such parameters may optionally be annotated with {@code @Valid} * and support access to validation results through a * {@link org.springframework.validation.Errors} argument. * Instead a {@link org.springframework.web.bind.MethodArgumentNotValidException} * exception is raised. * <li>{@link SessionAttribute @SessionAttribute} annotated parameters for access * to existing, permanent session attributes (e.g. user authentication object) * as opposed to model attributes temporarily stored in the session as part of * a controller workflow via {@link SessionAttributes}. * <li>{@link RequestAttribute @RequestAttribute} annotated parameters for access * to request attributes. * <li>{@link org.springframework.http.HttpEntity HttpEntity<?>} parameters * (Servlet-only) for access to the Servlet request HTTP headers and contents. * The request stream will be converted to the entity body using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. * <li>{@link java.util.Map} / {@link org.springframework.ui.Model} / * {@link org.springframework.ui.ModelMap} for enriching the implicit model * that will be exposed to the web view. * <li>{@link org.springframework.web.servlet.mvc.support.RedirectAttributes} * (Servlet-only, {@literal @MVC 3.1-only}) 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). {@code RedirectAttributes} is used instead of the * implicit model if the method returns a "redirect:" prefixed view name or * {@code RedirectView}. * <li>Command/form objects to bind parameters to: as bean properties or fields, * with customizable type conversion, depending on {@link InitBinder} methods * and/or the HandlerAdapter configuration - see the "webBindingInitializer" * property on RequestMappingHandlerMethodAdapter. * Such command objects along with their validation results will be exposed * as model attributes, by default using the non-qualified command class name * in property notation (e.g. "orderAddress" for type "mypackage.OrderAddress"). * Specify a parameter-level {@link ModelAttribute @ModelAttribute} annotation for * declaring a specific model attribute name. * <li>{@link org.springframework.validation.Errors} / * {@link org.springframework.validation.BindingResult} validation results * for a preceding command/form object (the immediate preceding argument). * <li>{@link org.springframework.web.bind.support.SessionStatus} status handle * for marking form processing as complete (triggering the cleanup of session * attributes that have been indicated by the {@link SessionAttributes @SessionAttributes} * annotation at the handler type level). * <li>{@link org.springframework.web.util.UriComponentsBuilder} * (Servlet-only, {@literal @MVC 3.1-only}) * for preparing a URL relative to the current request's host, port, scheme, * context path, and the literal part of the servlet mapping. * </ul> * * <p><strong>Note:</strong> Java 8's {@code java.util.Optional} is supported * as a method parameter type with annotations that provide a {@code required} * attribute (e.g. {@code @RequestParam}, {@code @RequestHeader}, etc.). The use * of {@code java.util.Optional} in those cases is equivalent to having * {@code required=false}. * * <p>The following return types are supported for handler methods: * <ul> * <li>A {@code ModelAndView} object (Servlet MVC or Portlet MVC), * with the model implicitly enriched with command objects and the results * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * <li>A {@link org.springframework.ui.Model Model} object, with the view name implicitly * determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator} * and the model implicitly enriched with command objects and the results * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * <li>A {@link java.util.Map} object for exposing a model, * with the view name implicitly determined through a * {@link org.springframework.web.servlet.RequestToViewNameTranslator} * and the model implicitly enriched with command objects and the results * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * <li>A {@link org.springframework.web.servlet.View} object, with the * model implicitly determined through command objects and * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.Model} argument (see above). * <li>A {@link String} value which is interpreted as view name, * with the model implicitly determined through command objects and * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.ModelMap} argument * (see above). * <li>{@link ResponseBody @ResponseBody} annotated methods (Servlet-only) * for access to the Servlet response HTTP contents. The return value will * be converted to the response stream using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. * <li>An {@link org.springframework.http.HttpEntity HttpEntity<?>} or * {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object * (Servlet-only) to access to the Servlet response HTTP headers and contents. * The entity body will be converted to the response stream using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. * <li>An {@link org.springframework.http.HttpHeaders HttpHeaders} object to * return a response with no body.</li> * <li>A {@link Callable} which is used by Spring MVC to obtain the return * value asynchronously in a separate thread transparently managed by Spring MVC * on behalf of the application. * <li>A {@link org.springframework.web.context.request.async.DeferredResult} * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. * <li>A {@link org.springframework.util.concurrent.ListenableFuture} * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. * <li>A {@link java.util.concurrent.CompletionStage} (implemented by * {@link java.util.concurrent.CompletableFuture} for example) * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. * <li>A {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter} * can be used to write multiple objects to the response asynchronously; * also supported as the body within {@code ResponseEntity}.</li> * <li>An {@link org.springframework.web.servlet.mvc.method.annotation.SseEmitter} * can be used to write Server-Sent Events to the response asynchronously; * also supported as the body within {@code ResponseEntity}.</li> * <li>A {@link org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody} * can be used to write to the response asynchronously; * also supported as the body within {@code ResponseEntity}.</li> * <li>{@code void} if the method handles the response itself (by * writing the response content directly, declaring an argument of type * {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse} * / {@link javax.portlet.RenderResponse} for that purpose) * or if the view name is supposed to be implicitly determined through a * {@link org.springframework.web.servlet.RequestToViewNameTranslator} * (not declaring a response argument in the handler method signature; * only applicable in a Servlet environment). * <li>Any other return type will be considered as single model attribute * to be exposed to the view, using the attribute name specified through * {@link ModelAttribute @ModelAttribute} at the method level (or the default attribute * name based on the return type's class name otherwise). The model will be * implicitly enriched with command objects and the results of * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * </ul> * * <p><b>NOTE:</b> {@code @RequestMapping} will only be processed if an * an appropriate {@code HandlerMapping}-{@code HandlerAdapter} pair * is configured. This is the case by default in both the * {@code DispatcherServlet} and the {@code DispatcherPortlet}. * However, if you are defining custom {@code HandlerMappings} or * {@code HandlerAdapters}, then you need to add * {@code DefaultAnnotationHandlerMapping} and * {@code AnnotationMethodHandlerAdapter} to your configuration.</code>. * * <p><b>NOTE:</b> Spring 3.1 introduced a new set of support classes for * {@code @RequestMapping} methods in Servlet environments called * {@code RequestMappingHandlerMapping} and * {@code RequestMappingHandlerAdapter}. They are recommended for use and * even required to take advantage of new features in Spring MVC 3.1 (search * {@literal "@MVC 3.1-only"} in this source file) and going forward. * The new support classes are enabled by default from the MVC namespace and * with use of the MVC Java config ({@code @EnableWebMvc}) but must be * configured explicitly if using neither. * * <p><b>NOTE:</b> When using controller interfaces (e.g. for AOP proxying), * make sure to consistently put <i>all</i> your mapping annotations - such as * {@code @RequestMapping} and {@code @SessionAttributes} - on * the controller <i>interface</i> rather than on the implementation class. * * @author Juergen Hoeller * @author Arjen Poutsma * @author Sam Brannen * @since 2.5 * @see GetMapping * @see PostMapping * @see PutMapping * @see DeleteMapping * @see PatchMapping * @see RequestParam * @see RequestAttribute * @see PathVariable * @see ModelAttribute * @see SessionAttribute * @see SessionAttributes * @see InitBinder * @see org.springframework.web.context.request.WebRequest * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter * @see org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { /** * Assign a name to this mapping. * <p><b>Supported at the type level as well as at the method level!</b> * When used on both levels, a combined name is derived by concatenation * with "#" as separator. * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy */ String name() default ""; /** * The primary mapping expressed by this annotation. * <p>In a Servlet environment this is an alias for {@link #path}. * For example {@code @RequestMapping("/foo")} is equivalent to * {@code @RequestMapping(path="/foo")}. * <p>In a Portlet environment this is the mapped portlet modes * (i.e. "EDIT", "VIEW", "HELP" or any custom modes). * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings inherit * this primary mapping, narrowing it for a specific handler method. */ @AliasFor("path") String[] value() default {}; /** * In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do"). * Ant-style path patterns are also supported (e.g. "/myPath/*.do"). * At the method level, relative paths (e.g. "edit.do") are supported within * the primary mapping expressed at the type level. Path mapping URIs may * contain placeholders (e.g. "/${connect}") * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings inherit * this primary mapping, narrowing it for a specific handler method. * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE * @since 4.2 */ @AliasFor("value") String[] path() default {}; /** * The HTTP request methods to map to, narrowing the primary mapping: * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings inherit * this HTTP method restriction (i.e. the type-level restriction * gets checked before the handler method is even resolved). * <p>Supported for Servlet environments as well as Portlet 2.0 environments. */ RequestMethod[] method() default {}; /** * The parameters of the mapped request, narrowing the primary mapping. * <p>Same format for any environment: a sequence of "myParam=myValue" style * expressions, with a request only mapped if each such parameter is found * to have the given value. Expressions can be negated by using the "!=" operator, * as in "myParam!=myValue". "myParam" style expressions are also supported, * with such parameters having to be present in the request (allowed to have * any value). Finally, "!myParam" style expressions indicate that the * specified parameter is <i>not</i> supposed to be present in the request. * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings inherit * this parameter restriction (i.e. the type-level restriction * gets checked before the handler method is even resolved). * <p>In a Servlet environment, parameter mappings are considered as restrictions * that are enforced at the type level. The primary path mapping (i.e. the * specified URI value) still has to uniquely identify the target handler, with * parameter mappings simply expressing preconditions for invoking the handler. * <p>In a Portlet environment, parameters are taken into account as mapping * differentiators, i.e. the primary portlet mode mapping plus the parameter * conditions uniquely identify the target handler. Different handlers may be * mapped onto the same portlet mode, as long as their parameter mappings differ. */ String[] params() default {}; /** * The headers of the mapped request, narrowing the primary mapping. * <p>Same format for any environment: a sequence of "My-Header=myValue" style * expressions, with a request only mapped if each such header is found * to have the given value. Expressions can be negated by using the "!=" operator, * as in "My-Header!=myValue". "My-Header" style expressions are also supported, * with such headers having to be present in the request (allowed to have * any value). Finally, "!My-Header" style expressions indicate that the * specified header is <i>not</i> supposed to be present in the request. * <p>Also supports media type wildcards (*), for headers such as Accept * and Content-Type. For instance, * <pre class="code"> * @RequestMapping(value = "/something", headers = "content-type=text/*") * </pre> * will match requests with a Content-Type of "text/html", "text/plain", etc. * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings inherit * this header restriction (i.e. the type-level restriction * gets checked before the handler method is even resolved). * <p>Maps against HttpServletRequest headers in a Servlet environment, * and against PortletRequest properties in a Portlet 2.0 environment. * @see org.springframework.http.MediaType */ String[] headers() default {}; /** * The consumable media types of the mapped request, narrowing the primary mapping. * <p>The format is a single media type or a sequence of media types, * with a request only mapped if the {@code Content-Type} matches one of these media types. * Examples: * <pre class="code"> * consumes = "text/plain" * consumes = {"text/plain", "application/*"} * </pre> * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches * all requests with a {@code Content-Type} other than "text/plain". * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings override * this consumes restriction. * @see org.springframework.http.MediaType * @see javax.servlet.http.HttpServletRequest#getContentType() */ String[] consumes() default {}; /** * The producible media types of the mapped request, narrowing the primary mapping. * <p>The format is a single media type or a sequence of media types, * with a request only mapped if the {@code Accept} matches one of these media types. * Examples: * <pre class="code"> * produces = "text/plain" * produces = {"text/plain", "application/*"} * produces = "application/json; charset=UTF-8" * </pre> * <p>It affects the actual content type written, for example to produce a JSON response * with UTF-8 encoding, {@code "application/json; charset=UTF-8"} should be used. * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches * all requests with a {@code Accept} other than "text/plain". * <p><b>Supported at the type level as well as at the method level!</b> * When used at the type level, all method-level mappings override * this produces restriction. * @see org.springframework.http.MediaType */ String[] produces() default {}; }
2.9.1、小黃鴨調試法
場景一:我們都有過向別人(甚至可能向完全不會編程的人)提問及解釋編程問題的經歷,但是很多時候就在我們解釋的過程中自己卻想到了問題的解決方案,然后對方卻一臉茫然。
場景二:你的同行跑來問你一個問題,但是當他自己把問題說完,或說到一半的時候就想出答案走了,留下一臉茫然的你。
其實上面兩種場景現象就是所謂的小黃鴨調試法(Rubber Duck Debuging),又稱橡皮鴨調試法,它是我們軟件工程中最常使用調試方法之一。
此概念據說來自《程序員修煉之道》書中的一個故事,傳說程序大師隨身攜帶一只小黃鴨,在調試代碼的時候會在桌上放上這只小黃鴨,然后詳細地向鴨子解釋每行代碼,然后很快就將問題定位修復了。
——2018-10-24(程序猿節)
3、示例下載
https://zhangguo5.coding.net/public/SpringMVCDemo/SpringMVCDemo/git
4、視頻
https://www.bilibili.com/video/av16991874/
5、作業
5.1、重現文中所有示例
5.2、完成個人項目前台頁面與后台頁面設計
5.3、預習第三章內容、完成任務指導手冊中Spring MVC部分的內容