第六章 註解式控制器詳解
6.1、註解式控制器簡介
一、Spring2.5之前,我們都是通過實現Controller介面或其實現來定義我們的處理器類。已經
@Deprecated
。
二、Spring2.5引入註解式處理器支援,通過
@Controller
和
@RequestMapping
註解定義我們的處理器類。
並且提供了一組強大的註解:
需要通過處理器對映
DefaultAnnotationHandlerMapping
和處理器介面卡
AnnotationMethodHandlerAdapter
來開啟支援@Controller 和@RequestMapping註解的處理器。
@Controller:
用於標識是處理器類;
@RequestMapping:
請求到處理器功能方法的對映規則;
@RequestParam:
請求引數到處理器功能處理方法的方法引數上的繫結;
@ModelAttribute:
請求引數到命令物件的繫結;
@SessionAttributes:
用於宣告session級別儲存的屬性,放置在處理器類上,通常列出
模型屬性(如@ModelAttribute)
對應的名稱,
則這些屬性會透明的儲存到session中;
@InitBinder:
自定義資料繫結註冊支援,用於將請求引數轉換到命令物件屬性的對應型別;
三、Spring3.0引入RESTful架構風格支援(通過@PathVariable註解和一些其他特性支援),且又引入了
更多的註解支援:
@CookieValue:
cookie資料到處理器功能處理方法的方法引數上的繫結;
@RequestHeader:
請求頭(header)資料到處理器功能處理方法的方法引數上的繫結;
@RequestBody:
請求的body體的繫結(通過HttpMessageConverter進行型別轉換);
@ResponseBody:
處理器功能處理方法的返回值作為響應體(通過HttpMessageConverter進行型別轉換);
@ResponseStatus:
定義處理器功能處理方法/異常處理器返回的狀態碼和原因;
@ExceptionHandler:
註解式宣告異常處理器;
@PathVariable:
請求URI中的模板變數部分到處理器功能處理方法的方法引數上的繫結,
從而支援RESTful架構風格的URI;
四、Spring3.1使用新的
HandlerMapping
和
HandlerAdapter
來支援@Contoller和@RequestMapping
註解處理器。
新的@Contoller和@RequestMapping註解支援類:處理器對映
RequestMappingHandlerMapping
和
處理器介面卡RequestMappingHandlerAdapter組合來代替Spring2.5開始的處理器對映DefaultAnnotationHandlerMapping和處理器介面卡AnnotationMethodHandlerAdapter,
提供更多的擴充套件點。
接下來,我們一起開始學習基於註解的控制器吧。
②、④、⑥一般是可變的,因此我們可以這些資訊進行請求到處理器的功能處理方法的對映,
因此請求的對映分為如下幾種:
URL路徑對映:使用URL對映請求到處理器的功能處理方法;
請求方法對映限定:如限定功能處理方法只處理GET請求;
請求引數對映限定:如限定只處理包含“abc”請求引數的請求;
請求頭對映限定:如限定只處理“Accept=application/json”的請求。
接下來看看具體如何對映吧。
6.2、入門
(1、控制器實現
package cn.javass.chapter6.web.controller;
//省略import
@Controller // 或 @RequestMapping //①將一個POJO類宣告為處理器
public class HelloWorldController {
@RequestMapping(value = "/hello") //②請求URL到處理器功能處理方法的對映
public ModelAndView helloWorld() {
//1、收集引數
//2、繫結引數到命令物件
//3、呼叫業務物件
//4、選擇下一個頁面
ModelAndView mv = new ModelAndView();
//新增模型資料 可以是任意的POJO物件
mv.addObject("message", "Hello World!");
//設定邏輯檢視名,檢視解析器會根據該名字解析到具體的檢視頁面
mv.setViewName("hello");
return mv; //○3 模型資料和邏輯檢視名
}
}
可以通過在一個POJO類上放置@Controller或@RequestMapping,即可把一個POJO類變身為處理器;
@RequestMapping(value = "/hello")
請求URL(/hello) 到 處理器的功能處理方法的對映;
模型資料和邏輯檢視名的返回。
現在的處理器無需實現/繼承任何介面/類,只需要在相應的類/方法上放置相應的註解說明下即可,
非常方便。
(2、Spring配置檔案chapter6-servlet.xml
(2.1、HandlerMapping和HandlerAdapter的配置
如果您使用的是Spring3.1之前版本,開啟註解式處理器支援的配置為:
DefaultAnnotationHandlerMapping
和AnnotationMethodHandlerAdapter。
<!—Spring3.1之前的註解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<!—Spring3.1之前的註解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
如果您使用的Spring3.1開始的版本,建議使用
RequestMappingHandlerMapping
和RequestMappingHandlerAdapter。
<!--Spring3.1開始的註解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--Spring3.1開始的註解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
下一章我們介紹DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter
與RequestMappingHandlerMapping和RequestMappingHandlerAdapter 的區別。
(2.2、檢視解析器的配置
還是使用之前的org.springframework.web.servlet.view.InternalResourceViewResolver。
(2.3、處理器的配置
<!-- 處理器 -->
<bean class="cn.javass.chapter6.web.controller.HelloWorldController"/>
只需要將處理器實現類註冊到spring配置檔案即可,spring的DefaultAnnotationHandlerMapping或RequestMappingHandlerMapping
能根據註解@Controller或@RequestMapping自動發現。
(2.4、檢視頁面(/WEB-INF/jsp/hello.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello World</title>
</head>
<body>
${message}
</body>
</html>
${message}:表示顯示由HelloWorldController處理器傳過來的模型資料。
(4、啟動伺服器測試
位址列輸入http://localhost:9080/springmvc-chapter6/hello,我們將看到頁面顯示“Hello World!”,
表示成功了。
整個過程和我們第二章中的Hello World 類似,只是處理器的實現不一樣。接下來我們來看一下具體流程吧。
6.3、執行流程
在spring3.1之後DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter
將被RequestMappingHandlerMapping和RequestMappingHandlerAdapter 代替。
和第二章唯一不同的兩處是:
1、HandlerMapping實現:使用DefaultAnnotationHandlerMapping(spring3.1之前)或RequestMappingHandlerMapping(spring3.1)
替換之前的BeanNameUrlHandlerMapping。
註解式處理器對映會掃描spring容器中的bean,發現bean實現類上擁有
@Controller或@RequestMapping註解的bean,
並將它們作為處理器。
2、HandlerAdapter實現:使用AnnotationMeth
odHandlerAdapter(spring3.1之前)或RequestMappingHandlerAdapter(spring3.1)替換之前的SimpleControllerHandlerAdapter。
好了到此我們知道Spring如何發現處理器、如何呼叫處理的功能處理方法了,接下來我們詳細學習下如何定義處理器、如何進行請求到功能處理方法的定義。
6.4、處理器定義
6.4.1、@Controller
@Controller
public class HelloWorldController {
……
}
推薦使用這種方式宣告處理器,它和我們的@Service、@Repository很好的對應了我們常見的三層開發架構的元件。
6.4.2、@RequestMapping
@RequestMapping
public class HelloWorldController {
……
}
這種方式也是可以工作的,但如果在類上使用@ RequestMapping註解一般是用於窄化功能處理方法的對映的,詳見6.4.3。
package cn.javass.chapter6.web.controller;
@Controller
@RequestMapping(value="/user") //①處理器的通用對映字首
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相對於①處的對映進行窄化
public ModelAndView helloWorld() {
//省略實現
}
}
6.4.3、窄化請求對映
package cn.javass.chapter6.web.controller;
@Controller
@RequestMapping(value="/user") //①處理器的通用對映字首
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相對於①處的對映進行窄化
public ModelAndView helloWorld() {
//省略實現
}
}
①類上的@RequestMapping(value="/user") 表示處理器的通用請求字首;
②處理器功能處理方法上的是對①處對映的窄化。
因此http://localhost:9080/springmvc-chapter6/hello2 無法對映到HelloWorldController2的 helloWorld功能處理方法;而http://localhost:9080/springmvc-chapter6/user/hello2是可以的。
窄化請求對映可以認為是方法級別的@RequestMapping繼承類級別的@RequestMapping。
窄化請求對映還有其他方式,如在類級別指定URL,而方法級別指定請求方法型別或引數等等,
到此,我們知道如何定義處理器了,接下來我們需要學習如何把請求對映到相應的功能處理方法進行請求處理。
6.5、請求對映
處理器定義好了,那接下來我們應該定義功能處理方法,接收使用者請求處理並選擇檢視進行渲染。
首先我們看一下圖6-1:
http請求資訊包含六部分資訊:
①請求方法,如GET或POST,表示提交的方式;
②URL,請求的地址資訊;
③協議及版本;
④請求頭資訊(包括Cookie資訊);
⑤回車換行(CRLF);
⑥請求內容區(即請求的內容或資料),如表單提交時的引數資料、URL請求引數(?abc=123 ?後邊的)等。
想要了解HTTP/1.1協議,請訪問http://tools.ietf.org/html/rfc2616。
那此處我們可以看到有①、②、④、⑥一般是可變的,因此我們可以這些資訊進行請求到
處理器的功能處理方法的對映,因此請求的對映分為如下幾種:
URL路徑對映:使用URL對映請求到處理器的功能處理方法;
請求方法對映限定:如限定功能處理方法只處理GET請求;
請求引數對映限定:如限定只處理包含“abc”請求引數的請求;
請求頭對映限定:如限定只處理“Accept=application/json”的請求。
接下來看看具體如何對映吧。
6.5.1、URL路徑對映
6.5.1.1、普通URL路徑對映
@RequestMapping(value={"/test1", "/user/create"}):多個URL路徑可以對映到同一個處理器的功能處理方法。
6.5.1.2、URI模板模式對映
@RequestMapping(value="/users/{userId}"):{×××}佔位符, 請求的URL可以是 “/users/123456”或
“/users/abcd”,通過6.6.5講的通過@PathVariable可以提取URI模板模式中的{×××}中的×××變數。
@RequestMapping(value="/users/{userId}/create"):這樣也是可以的,請求的URL可以是“/users/123/create”。
@RequestMapping(value="/users/{userId}/topics/{topicId}"):這樣也是可以的,請求的URL可以是“/users/123/topics/123”。
6.5.1.3、Ant風格的URL路徑對映
@RequestMapping(value="/users/**"):可以匹配“/users/abc/abc”,但“/users/123”將會被【URI模板模式對映中的“/users/{userId}”模式優先對映到】。
@RequestMapping(value="/product?"):可匹配“/product1”或“/producta”,但不匹配“/product”或“/productaa”;
@RequestMapping(value="/product*"):可匹配“/productabc”或“/product”,但不匹配“/productabc/abc”;
@RequestMapping(value="/product/*"):可匹配“/product/abc”,但不匹配“/productabc”;
@RequestMapping(value="/products/**/{productId}"):可匹配“/products/abc/abc/123”或“/products/123”,也就是Ant風格和URI模板變數風格可混用;
此處需要注意的是【4.14中提到的最長匹配優先】,Ant風格的模式請參考4.14。
6.5.1.4、正則表示式風格的URL路徑對映
從Spring3.0開始支援正則表示式風格的URL路徑對映,格式為{變數名:正則表示式},這樣我們就可以通過6.6.5講的通過@PathVariable提取模式中的{×××:正則表示式匹配的值}中的×××變量了。
@RequestMapping(value="/products/{categoryCode:\\d+}-{pageNumber:\\d+}"):可以匹配“/products/123-1”,但不能匹配“/products/abc-1”,這樣可以設計更加嚴格的規則。
正則表示式風格的URL路徑對映是一種特殊的URI模板模式對映:
URI模板模式對映是{userId},不能指定模板變數的資料型別,如是數字還是字串;
正則表示式風格的URL路徑對映,可以指定模板變數的資料型別,可以將規則寫的相當複雜。
6.5.1.5、組合使用是“或”的關係
如 @RequestMapping(value={"/test1", "/user/create"}) 組合使用是或的關係,即“/test1”或“/user/create”請求URL路徑都可以對映到@RequestMapping指定的功能處理方法。
以上URL對映的測試類為:cn.javass.chapter6.web.controller.mapping.MappingController.java。
到此,我們學習了Spring Web MVC提供的強大的URL路徑對映,而且可以實現非常複雜的URL規則。Spring Web MVC不僅僅提供URL路徑對映,還提供了其他強大的對映規則。接下來我們看一下請求方法對映限定吧。
6.5.2、請求方法對映限定
一般我們熟悉的表單一般分為兩步:第一步展示,第二步提交,如4.9、SimpleFormController那樣,那如何通過@RequestMapping來實現呢?
6.5.2.1、請求方法對映限定
我們熟知的,展示表單一般為GET請求方法;提交表單一般為POST請求方法。但6.5.1節講的URL路徑對映方式對任意請求方法是全盤接受的,因此我們需要某種方式來告訴相應的功能處理方法只處理如GET請求方法的請求或POST請求方法的請求。
接下來我們使用@RequestMapping來實現SimpleFormController的功能吧。
package cn.javass.chapter6.web.controller.method;
//省略import
@Controller
@RequestMapping("/customers/**") //①處理器的通用對映字首
public class RequestMethodController {
@RequestMapping(value="/create", method = RequestMethod.GET)//②類級別的@RequestMapping窄化
public String showForm() {
System.out.println("===============GET");
return "customer/create";
}
@RequestMapping(value="/create", method = RequestMethod.POST)//③類級別的@RequestMapping窄化
public String submit() {
System.out.println("================POST");
return "redirect:/success";
}
}
①處理器的通用對映字首(父路徑):表示該處理器只處理匹配“/customers/**”的請求;
②對類級別的@RequestMapping進行窄化,表示showForm可處理匹配“/customers/**/create”且請求方法為“GET”的請求;
③對類級別的@RequestMapping進行窄化,表示submit可處理匹配“/customers/**/create”且請求方法為“POST”的請求。
6.5.2.2、組合使用是“或”的關係
@RequestMapping(value="/methodOr", method = {RequestMethod.POST, RequestMethod.GET}):即請求方法可以是 GET 或 POST。
提示:
1、一般瀏覽器只支援GET、POST請求方法,如想瀏覽器支援PUT、DELETE等請求方法只能模擬,稍候章節介紹。
2、除了GET、POST,還有HEAD、OPTIONS、PUT、DELETE、TRACE。
3、DispatcherServlet預設開啟對 GET、POST、PUT、DELETE、HEAD的支援;
4、如果需要支援OPTIONS、TRACE,請新增DispatcherServlet在web.xml的初始化引數:dispatchOptionsRequest 和 dispatchTraceRequest 為true。
請求方法的詳細使用請參考RESTful架構風格一章。
以上請求方法對映限定測試類為:cn.javass.chapter6.web.controller.method.RequestMethodController。
6.5.3、請求引數資料對映限定
6.5.3.1、請求資料中有指定引數名
package cn.javass.chapter6.web.controller.parameter;
//省略import
@Controller
@RequestMapping("/parameter1") //①處理器的通用對映字首
public class RequestParameterController1 {
//②進行類級別的@RequestMapping窄化
@RequestMapping(params="create", method=RequestMethod.GET)
public String showForm() {
System.out.println("===============showForm");
return "parameter/create";
}
//③進行類級別的@RequestMapping窄化
@RequestMapping(params="create", method=RequestMethod.POST)
public String submit() {
System.out.println("================submit");
return "redirect:/success";
}
}
②@RequestMapping(params="create", method=RequestMethod.GET) :表示請求中有“create”的引數名且請求方法為“GET”即可匹配,如可匹配的請求URL“http://×××/parameter1?create”;
③@RequestMapping(params="create", method=RequestMethod.POST):表示請求中有“create”的引數名且請求方法為“POST”即可匹配;
此處的create請求引數名表示你請求的動作,即你想要的功能的一個標識,常見的CRUD(增刪改查)我們可以使用如下請求引數名來表達:
◇(create請求引數名 且 GET請求方法) 新增頁面展示、(create請求引數名 且 POST請求方法)新增提交;
◇(update請求引數名 且 GET請求方法) 新增頁面展示、(update請求引數名 且 POST請求方法)新增提交;
◇(delete請求引數名 且 GET請求方法) 新增頁面展示、(delete請求引數名 且 POST請求方法)新增提交;
◇(query請求引數名 且 GET請求方法) 新增頁面展示、(query請求引數名 且 POST請求方法) 新增提交;
◇(list請求引數名 且 GET請求方法) 列表頁面展示;
◇(view請求引數名 且 GET請求方法) 檢視單條記錄頁面展示。
6.5.3.2、請求資料中沒有指定引數名
//請求引數不包含 create引數名 @RequestMapping(params="!create", method=RequestMethod.GET)//進行類級別的@RequestMapping窄化
@RequestMapping(params="!create", method=RequestMethod.GET):表示請求中沒有“create”引數名且請求方法為“GET”即可匹配,如可匹配的請求URL“http://×××/parameter1?abc”。
6.5.3.3、請求資料中指定引數名=值
package cn.javass.chapter6.web.controller.parameter;
//省略import
@Controller
@RequestMapping("/parameter2") //①處理器的通用對映字首
public class RequestParameterController2 {
//②進行類級別的@RequestMapping窄化
@RequestMapping(params="submitFlag=create", method=RequestMethod.GET)
public String showForm() {
System.out.println("===============showForm");
return "parameter/create";
}
//③進行類級別的@RequestMapping窄化
@RequestMapping(params="submitFlag=create", method=RequestMethod.POST)
public String submit() {
System.out.println("===============submit");
return "redirect:/success";
}
}
②@RequestMapping(params="submitFlag=create", method=RequestMethod.GET):表示請求中有“submitFlag=create”請求引數且請求方法為“GET”即可匹配,如請求URL為http://×××/parameter2?submitFlag=create;
③@RequestMapping(params="submitFlag=create", method=RequestMethod.POST):表示請求中有“submitFlag=create”請求引數且請求方法為“POST”即可匹配;
此處的submitFlag=create請求引數表示你請求的動作,即你想要的功能的一個標識,常見的CRUD(增刪改查)我們可以使用如下請求引數名來表達:
◇(submitFlag=create請求引數名 且 GET請求方法) 新增頁面展示、(submitFlag=create請求引數名 且 POST請求方法) 新增提交;
◇(submitFlag=update請求引數名 且 GET請求方法) 新增頁面展示、(submitFlag=update請求引數名 且 POST請求方法) 新增提交;
◇(submitFlag=delete請求引數名 且 GET請求方法) 新增頁面展示、(submitFlag=delete請求引數名 且 POST請求方法) 新增提交;
◇(submitFlag=query請求引數名 且 GET請求方法) 新增頁面展示、(submitFlag=query請求引數名 且 POST請求方法) 新增提交;
◇(submitFlag=list請求引數名 且 GET請求方法) 列表頁面展示;
◇(submitFlag=view請求引數名 且 GET請求方法) 檢視單條記錄頁面展示。
6.5.3.4、請求資料中指定引數名!=值
<span style="color:#000000">//請求引數submitFlag 不等於 create
@RequestMapping(params="submitFlag!=create", method=RequestMethod.GET)
</span>
@RequestMapping(params="submitFlag!=create", method=RequestMethod.GET):表示請求中的引數“submitFlag!=create”且請求方法為“GET”即可匹配,如可匹配的請求URL“http://×××/parameter1?submitFlag=abc”。
6.5.3.5、組合使用是“且”的關係
<span style="color:#000000">@RequestMapping(params={"test1", "test2=create"}) //②進行類級別的@RequestMapping窄化</span>
@RequestMapping(params={"test1", "test2=create"}):表示請求中的有“test1”引數名 且 有“test2=create”引數即可匹配,如可匹配的請求URL“http://×××/parameter3?test1&test2=create。
以上請求引數資料對映限定測試類為:cn.javass.chapter6.web.controller.method包下的RequestParameterController1、RequestParameterController2、RequestParameterController3。