一、SpringMVC 使用 @PathVariable、@RequestParam、@RequestHeader、@CookieValue 等來解決參數獲取問題。
1. @PathVariable:映射 URL 綁定的占位符,可以借助於傳入到方法參數列表中的 @PathVariable 注解獲取到 URL 映射中的參數值。如:
<a href="handler01/1">test pathvariable</a>
@RequestMapping("/handler01/{id}") public String testPathVariable(@PathVariable("id") String id) { System.out.println("id:" + id); return "success"; }
說明:URL 綁定占位符使 SpringMVC 對 REST 提供了支持。對於具體的 SpringMVC 的 REST 風格的例子會在以後的文章裡介紹。
2.@RequestParam
官方文檔是這樣描述的:
* Annotation which indicates that a method parameter should be bound to a web
* request parameter. Supported for annotated handler methods in Servlet and
* Portlet environments.
*
* <p>If the method parameter type is {@link Map} and a request parameter name
* is specified, then the request parameter value is converted to a {@link Map}
* assuming an appropriate conversion strategy is available.
*
* <p>If the method parameter is {@link java.util.Map Map<String, String>} or
* {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}
* and a parameter name is not specified, then the map parameter is populated
* with all request parameter names and values.
說明一下:
(1)該注解表明 web 請求參數綁定到目標 handler 方法的入參。
(2)如果方法的入參類型是一個 Map,不包含泛型類型,並且請求參數名稱是被指定的
(如:public String testRequestParam5(@RequestParam("userName") Map map)),請求參數會被轉換為一個 Map,前提是存在轉換策略。
這裡所說的轉換策略,通常是指 請求參數 到 Map 的類型轉換,如請求參數為 userName=a|12,b|34 這樣的數據,需要通過一個轉換策略(類型轉換器)
來完成 a|12,b|34 到 map 的轉換。在我們一般開發的過程中,不包含這種情況。是一種擴展。關於類型轉換會在後面的文章中介紹。
(3)如果方法的入參是一個 Map 且指定了泛型類型 Map<String,String> 或者是 org.springframework.util.MultiValueMap 類型的 MultiValueMap<String, String>
並且沒有指定請求參數,那麼這個 Map 類型的參數會將所有的請求參數名稱和值填充(populate)到其中。
如:
請求:<a href="testRequestParam4?userName=jack&age=23">test request param4</a>
handler 方法:
@RequestMapping("/testRequestParam4") public String testRequestParam4(@RequestParam Map<String, String> map) { System.out.println("map:" + map); return "success"; }
控制台輸出:
map:{userName=jack, age=23}
上面整體介紹了 @RequestParam,下面詳細看看它的API:
包含三個屬性:
(1)value 屬性,默認為 ""
官方文檔說明:
The name of the request parameter to bind to.
解釋的已經很明白了,不再贅述。
(2)required 屬性,默認為 true
官方文檔說明:Whether the parameter is required.
見名知意,該請求參數是否是必須的。為 true 的請求下,若請求參數中沒有,則會拋出一個異常。為 false 的情況下,如果請求參數中沒有,則方法入參對應值為 null。
另外,提供一個 defaultValue 屬性,則會是此屬性設置為 false。
(3)defaultValue 屬性
當沒有提供對應的請求參數,或者請求參數為空時,會使用此屬性對應的值。當設置此屬性的時候,會將 required 屬性設置為 false。
下面提供幾個常見請求情況的例子:
(1)請求為:<a href="testRequestParam?userName=abc">test request param</a>
handler 方法:
@RequestMapping("/testRequestParam") public String testRequstParam01(@RequestParam("userName") String userName) { System.out.println("userName: " + userName); return "success"; }
(2)請求為:<a href="testRequestParam2?userName=jack&userName=lucy">test request param2</a>
handler 方法:
@RequestMapping("/testRequestParam2") public String testRequestParam02(@RequestParam("userName") List<String> userNames) { System.out.println("userNames:" + userNames); return "success"; }
控制台輸出:
userNames:[jack, lucy]
(3)請求為:<a href="testRequestParam4?userName=jack&age=23">test request param4</a>
handler 方法:
@RequestMapping("/testRequestParam4") public String testRequestParam4(@RequestParam Map<String, String> map) { System.out.println("map:" + map); return "success"; }
控制台輸出:
map:{userName=jack, age=23}
主要就分為這三種情況,其中第一種最為常用,第二種和第三種很少能想到,若能想到的話,能為我們開發節省不少時間。
3.@RequestHeader
官方文檔中是這樣描述的:
Annotation which indicates that a method parameter should be bound to a web request header.
Supported for annotated handler methods in Servlet and Portlet environments.
和 @RequestParam 描述類似,只不過綁定的是 web 請求頭信息到方法入參。
定義的三個屬性和 @RequestParam 一樣,默認值和使用的方法也一樣。由於用的比較少,這裡只做一個例子說明:
請求:<a href="testRequestHeader">test request header</a>
handler 方法:
@RequestMapping("/testRequestHeader") public String testRequestHeader(@RequestHeader(value = "Accept", required = false) String accept) { System.out.println("accept:" + accept); return "success"; }
控制台輸出:
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
4.@CookieValue
官方文檔描述:
Annotation which indicates that a method parameter should be bound to an HTTP cookie.
Supported for annotated handler methods in Servlet and Portlet environments.
綁定一個 http cookie 到方法的入參,其中 value 屬性表明要入參的 cookie 的 key。
默認值和使用方式和 @RequestParam 類似。
例子:
請求:<a href="testCookieValue">test cookie value</a>
handler 方法:
@RequestMapping("/testCookieValue") public String testCookieValue(@CookieValue(value = "JSESSIONID", required = false) String sessionId) { System.out.println("sessionId:"+sessionId); return "success"; }
控制台輸出:
sessionId:9D16BDF7063E1BFD9A0C052F1B109A0D
5.綁定請求參數到方法入參處的 bean 對象。
先看兩個例子:
(1)綁定請求參數到 bean
請求:包括 get 和 post 請求方式提交的情況。
<a href="testBean?personName=jack&age=23">test bean</a> <form action="testBean" method="post"> <label> personName:<input type="text" name="personName"/> </label> <label> age:<input type="text" name="age"/> </label> <input type="submit" value="submit"/> </form>
handler 方法:
@RequestMapping("/testBean") public String testBean(Person person) { System.out.println(person);//Person{personName='jack', age='23'} return "success"; }
發現不論是通過 get 方式,還是post 方式,都可以將對應的請求參數注入到對應的 bean 中。
(2)綁定請求參數到級聯的 bean
bean 的結構:
/** * @author solverpeng * @create 2016-08-04-9:43 */ public class Employee { private String empName; private Address address; public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "Employee{" + "empName='" + empName + '\'' + ", address=" + address + '}'; } } Employee /** * @author solverpeng * @create 2016-08-04-9:43 */ public class Address { private String addressName; public String getAddressName() { return addressName; } public void setAddressName(String addressName) { this.addressName = addressName; } @Override public String toString() { return "Address{" + "addressName='" + addressName + '\'' + '}'; } } Address請求:同樣包含 get 請求 和 post 請求
<a href="testBeanCascade?empName=jack&address.addressName=beijing">test bean cascade</a> <form action="testBeanCascade" method="post"> <label> empName:<input type="text" name="empName"/> </label> <label> Address:<input type="text" name="address.addressName"/> </label> <input type="submit" value="submit"/> </form>
handler 方法:
@RequestMapping("/testBeanCascade") public String testBeanCascade(Employee employee) { System.out.println(employee);//Employee{empName='jack', address=Address{addressName='beijing'}} return "success"; }
是如何綁定的呢?翻源碼過程如下:
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#invokeHandlerMethod
ExtendedModelMap implicitModel = new BindingAwareModelMap();
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
第一步:發現在 result 中已經包含了注入的 bean。所以注入是在methodInvoker.invokeHandlerMethod() 方法中做的。
第二步:org.springframework.web.bind.annotation.support.HandlerMethodInvoker#invokeHandlerMethod
Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
第三步:org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments
doBind(binder, webRequest, validate, validationHints, !assignBindingResult);// 這裡進行的綁定
第四步:org.springframework.web.bind.ServletRequestDataBinder#bind
doBind(mpvs);
第五步:org.springframework.validation.DataBinder#doBind
this.applyPropertyValues(mpvs);
最終發現,是在 DataBinder 這個類的 doBind() 方法中進行的綁定。在翻源碼的過程中,發現 resolveHandlerArguments() 方法值得大家看一看,不論水平高低,
其實真正解決 SpringMVC 參數問題就是在這個方法中解決的。
總結一下:Spring MVC 會按請求參數名和 POJO 屬性名進行自動匹配,自動為該對象填充屬性值。支持級聯屬性。
二、SpringMVC 解決 Servlet 資源獲取問題
1. SpringMVC 使用 Servlet 資源作為方法的入參來解決 Servlet 資源獲取問題。
2.可以作為入參的 Servlet 資源有:HttpServletRequest、HttpServletResponse、HttpSession、Locale、InputStream、OutputStream、Reader、Writer
3.例子:
使用 HttpServletRequest 作為入參
請求:<a href="testServletAPI">test servlet api</a>
handler 方法:
@RequestMapping("/testServletAPI") public String testServletAPI(HttpServletRequest request) { String id = request.getSession().getId(); System.out.println("sessionId:" + id); return "success"; }
控制台輸出:
sessionId:E369037AF3DC276BA78539F0AF5C044B
其他的 Servlet 資源這裡就不在贅述。
三、總結
SpringMVC 使用 @PathVariable 來獲取 @RequestMapping 中占位符的值,為 REST 風格的程序的編寫提供了支持。使用 @RequestParam 能接收絕大部分請求參數,同時提供了類型
轉換這種擴展。使用 @RequestHeader 來映射請求頭信息。使用 @CookieValue 來映射 http cookie 信息。同時還支持模型的注入。也可以獲取到原生的 servlet 資源。即在目標的方法處,
我們可以獲取到任何我們想要的資源,SpringMVC 對這個過程進行了簡化,使開發更加便捷,靈活。