SpringMVC註解驅動的控制器詳解
Spring2.5引入註解式處理器支援,通過@Controller和@RequestMapping註解定義我們的處理器類。並且提供了一組強大的註解需要通過處理器對映DefaultAnnotationHandlerMapping和處理器介面卡AnnotationMethodHandlerAdapter來開啟支援@Controller和@RequestMapping註解的處理器。
@Controller:用於標識是處理器類;
@RequestMapping:請求到處理器功能方法的對映規則;
@RequestParam:請求引數到處理器功能處理方法的方法引數上的繫結;
@ModelAttribute:請求引數到命令物件的繫結;
@InitBinder:自定義資料繫結註冊支援,用於將請求引數轉換到命令物件屬性的對應型別;
一、檢視返回
1、返回String物件
@RequestMapping(value ="/index")//相對於根目錄的路徑
public String test(ModelMap model) {
model.addAttribute("message", "呼叫FirstController的test方法");
return "index";//指定頁面要跳轉的view檢視路徑
}
直接返回檢視的名稱,結果:2、返回ModelAndView物件
結果:@RequestMapping("/index1") public ModelAndView test2() { ModelAndView modelAndView = new ModelAndView(); ///modelAndView.setView(new RedirectView("index1")); modelAndView.setViewName("index1");//指定頁面要跳轉的view檢視路徑 modelAndView.addObject("message", "呼叫FirstController的test1方法");//第二個引數:指定了要項前臺傳遞的引數,在前臺可以這樣取值 ${sp_ids } return modelAndView; }
其中
modelAndView.setViewName("index1");
也可以寫成:modelAndView.setView(new RedirectView("index1"));
ModelAndView()
這個構造方法構造出來的ModelAndView不能直接使用,應為它沒有指定view,也沒有繫結對應的model物件。當然,model物件不是必須的,但是view確實必須的。
用這個構造方法構造的例項主要用來在以後往其中加view設定和model物件。
給ModelAndView
例項設定view的方法有兩
個:setViewName(String viewName) 和 setView(View view)。前者是使用view
name,後者是使用預先構造好的View物件。其中前者比較常用。事實上View是一個介面,而不是一個可以構造的具體類,我們只能通過其他途徑來獲取
View的例項。對於view
name,它既可以是jsp的名字,也可以是tiles定義的名字,取決於使用的ViewNameResolver如何理解這個view name。
如何獲取View的例項以後再研究。
而對應如何給ModelAndView
例項設定model則比較複雜。有三個方法可以使用:
addObject(Object modelObject) addObject(String modelName, Object modelObject) addAllObjects(Map modelMap)
二、@RequestMapping
對於各種註解而言,排第一的當然是“@Controller”,表明某類是一個controller。“@RequestMapping”請求路徑對映,如果標註在某個controller的類級別上,則表明訪問此類路徑下的方法都要加上其配置的路徑;最常用是標註在方法上,表明哪個具體的方法來接受處理某次請求。
@RequestMapping 引數說明
value
定義處理方法的請求的 URL 地址。
method
定義處理方法的 http method 型別,如 GET、POST 等。
params
定義請求的 URL 中必須包含的引數。
headers
定義請求中 Request Headers 必須包含的引數
2.1 攔截路徑設定
(1) 方法上的攔截
@Controller
public class FirstController {
@RequestMapping(value ="/index")//相對於根目錄的路徑
public String test(ModelMap model) {
model.addAttribute("message", "呼叫FirstController的test方法");
return "index";//指定頁面要跳轉的view檢視路徑
}
}
表示攔截:http://localhost:8080/SpringMVCLearningChapter2/index1(2)類上的攔截
@Controller
@RequestMapping("/user")
public class SecondController {
@RequestMapping(value ="/index")
public String test(ModelMap model) {
model.addAttribute("message", "呼叫SecondController 的test方法");
return "index";//指定頁面要跳轉的view檢視路徑
}
@RequestMapping("/index1")
public ModelAndView test2() {
ModelAndView modelAndView = new ModelAndView();
///modelAndView.setView(new RedirectView("index1"));
modelAndView.setViewName("index1");//指定頁面要跳轉的view檢視路徑
modelAndView.addObject("message", "呼叫SecondController 的test1方法");//第二個引數:指定了要項前臺傳遞的引數,在前臺可以這樣取值 ${sp_ids }
return modelAndView;
}
}
表示攔截:http://localhost:8080/SpringMVCLearningChapter2/user/index和http://localhost:8080/SpringMVCLearningChapter2/user/index1
即攔截:根目錄/user/index和根目錄/user/index1
請求對映- 普通URL路徑對映
@RequestMapping(value={"/login.do","/user/login.do"}):多個URL路徑可以對映到同一個處理器的功能處理方法。
- URL模板模式對映
@RequestMapping(value="/users/{userId}"):{xxx}佔位符,請求的URL可以是"/users/123456"或"/users/abcd"。
@RequestMapping(value="/users/{userId}/login.do"):這樣也是可以的,請求的URL可以是"/users/123/login.do"。
@RequestMapping(value="/users/{userId}/channel/{channelId}"):這樣也是可以的,請求的URL可以是"/users/123/channel/456"。
- Ant風格的URL路徑對映
@RequestMapping(value="/users/**"):可以匹配"/users/abc/abc"。
@RequestMapping(value="/model?"):可匹配"/model1"或"/modela" ,但不匹配"/model"或"/modelaa";
@RequestMapping(value="/model*"):可匹配"/modelabc"或"/model",但不匹配"/modelabc/abc";
@RequestMapping(value="/model/*"):可匹配"/model/abc",但不匹配"/modelabc";
@RequestMapping(value="/model/**/{modelId}"):可匹配"/model/abc/abc/123”或"/model/123",
也就是Ant風格和URI模板變數風格可混用;
- 正則表示式風格的URL路徑對映
從Spring3.0開始支援正則表示式風格的URL路徑對映,格式為{變數名:正則表示式}
@RequestMapping(value="/login/{userId://d+}.do"):可以匹配
"/login/123.do",但不能匹配"/login/abc.do",這樣可以設計更加嚴格的規則。
- 組合使用是"或"的關係
如@RequestMapping(value={"/login.do","/user/login.do"})組合使用是或的關係,即"/login.do"或
"/user/login.do"請求URL路徑都可以對映到@RequestMapping指定的功能處理方法。
2、param引數
如果想給一個頁面設定傳遞的引數,可以寫成如下:
@Controller
@RequestMapping("/third")
public class ThirdController {
@RequestMapping(value ="/index",params="name")//要求傳遞引數name,瀏覽器中輸入name後,控制器會自動把引數傳遞給test中的name
public String test(ModelMap model,String name) {
model.addAttribute("message", name);
return "index";
}
}
這時輸入http://localhost:8080/SpringMVCLearningChapter2/third/index是無法訪問的:
因為它要求你一定要傳遞一個name引數,所以得這樣寫:
當然,這裡也可以把需要的引數註解到方法的引數上去
@RequestMapping(value ="/index")//要求傳遞引數name,瀏覽器中輸入name後,控制器會自動把引數傳遞給test中的name
public String test(ModelMap model,@RequestParam("name")String name) {
model.addAttribute("message", name);
return "index";
}
效果和上面和一樣的。這時如果還是想訪問http://localhost:8080/SpringMVCLearningChapter2/third/index,把required=false加上
@RequestMapping(value ="/index")
public String test(ModelMap model,@RequestParam(value="name",required=false)String name) {
model.addAttribute("message", name);
return "index";
}
3、method
@RequestMapping(value ="/index1",method = RequestMethod.GET,params="name")
public ModelAndView test2() {
ModelAndView modelAndView = new ModelAndView();
///modelAndView.setView(new RedirectView("index1"));
modelAndView.setViewName("index1");//指定頁面要跳轉的view檢視路徑
modelAndView.addObject("message", "呼叫SecondController 的test1方法");//第二個引數:指定了要項前臺傳遞的引數,在前臺可以這樣取值 ${sp_ids }
return modelAndView;
}
表明只能通過GET來訪問,或要POST,改成method = RequestMethod.POST
4、head
@RequestMapping(headers)
headers 的作用也是用於細化對映。只有當請求的 Request Headers 中包含與 heanders 值相匹配的引數,處理方法才會被呼叫。
@RequestMapping(value = "/specify", headers = "accept=text/*")
public String specify(){
return "example_specify_page";
}
請求的 Request Headers 中 Accept 的值必須匹配 text/* ( 如 text/html ),方法才會被呼叫。
三、其它常用註解
handler method 引數繫結常用的註解,我們根據他們處理的Request的不同內容部分分為四類:(主要講解常用型別)
A、處理requet uri 部分(這裡指uri template中variable,不含queryString部分)的註解: @PathVariable;
B、處理request header部分的註解: @RequestHeader, @CookieValue;
C、處理request body部分的註解:@RequestParam, @RequestBody;
D、處理attribute型別是註解: @SessionAttributes, @ModelAttribute;1、 @PathVariable
當使用@RequestMapping URI template 樣式對映時, 即 /fourth/{num}, 這時的num可通過 @Pathvariable註解繫結它傳過來的值到方法的引數上。
示例程式碼:
@Controller
@RequestMapping("/fourth/{num}")
public class FourthController {
@RequestMapping(value ="/index/{string}")
public String test(ModelMap model,@PathVariable("num") int num,@PathVariable("string") String string) {
model.addAttribute("message", "num="+String.valueOf(num)+" string="+string);
return "index";
}
}
瀏覽器輸入:http://localhost:8080/SpringMVCLearningChapter2/fourth/1234/index/linbingwen上面程式碼把URI template 中變數num的值和string的值,繫結到方法的引數上。若方法引數名稱和需要繫結的uri template中變數名稱不一致,需要在@PathVariable("num")指定uri template中的名稱。
2、 @RequestHeader、@CookieValue
@RequestHeader 註解,可以把Request請求header部分的值繫結到方法的引數上。
@RequestMapping(value = "six/index4")
public String getHello(@RequestHeader ("host") String hostName,
@RequestHeader ("Accept") String acceptType,
@RequestHeader ("Accept-Language") String acceptLang,
@RequestHeader ("Accept-Encoding") String acceptEnc,
@RequestHeader ("Cookie") String cookie,
@RequestHeader ("User-Agent") String userAgent)
{
System.out.println("Host : " + hostName);
System.out.println("Accept : " + acceptType);
System.out.println("Accept Language : " + acceptLang);
System.out.println("Accept Encoding : " + acceptEnc);
System.out.println("Cookie : " + cookie);
System.out.println("User-Agent : " + userAgent);
return "index4";
}
執行後輸入:http://localhost:8080/SpringMVCLearningChapter2/six/index4執行結果:
注:@RequestHeader 與@RequestParam 一樣也有3個引數,其含義與的@RequestParam 引數含義相同。
@CookieValue 可以把Request header中關於cookie的值繫結到方法的引數上。
引數繫結的程式碼:
@RequestMapping("six/index3")
public String test3(Model model,@CookieValue(value="JSESSIONID", defaultValue="") String jsessionId) {
model.addAttribute("jsessionId", jsessionId);
return "index3";
}
JSP中獲取:
<%@ page language="java" contentType="text/html; charset=gb2312"
pageEncoding="gb2312"%>
<!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=gb2312">
<title>Insert title here</title>
</head>
<body>
採用String返回檢視jsessionId=:${jsessionId}
</body>
</html>
結果:即把JSESSIONID的值繫結到引數cookie上
注:@CookieValue 與@RequestParam 一樣也有3個引數,其含義與的@RequestParam 引數含義相同
3、@RequestParam, @RequestBody
@RequestParam
A) 常用來處理簡單型別的繫結,通過Request.getParameter() 獲取的String可直接轉換為簡單型別的情況( String--> 簡單型別的轉換操作由ConversionService配置的轉換器來完成);因為使用request.getParameter()方式獲取引數,所以可以處理get 方式中queryString的值,也可以處理post方式中 body data的值;
B)用來處理Content-Type: 為 application/x-www-form-urlencoded編碼的內容,提交方式GET、POST;
C) 該註解有兩個屬性: value、required; value用來指定要傳入值的id名稱,required用來指示引數是否必須繫結;@RequestBody
該註解常用來處理Content-Type: 不是application/x-www-form-urlencoded
編碼的內容,例如application/json, application/xml等;
它是通過使用HandlerAdapter 配置的HttpMessageConverters
來解析post data body,然後繫結到相應的bean上的。
因為配置有FormHttpMessageConverter,所以也可以用來處理 application/x-www-form-urlencoded
的內容,處理完的結果放在一個MultiValueMap<String, String>裡,這種情況在某些特殊需求下使用,詳情檢視FormHttpMessageConverter api;
示例程式碼:
@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
4、@SessionAttributes, @ModelAttribute
@SessionAttributes:
該註解用來繫結HttpSession中的attribute物件的值,便於在方法中的引數裡使用。
該註解有value、types兩個屬性,可以通過名字和型別指定要使用的attribute 物件;
示例程式碼:
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
@ModelAttribute
該註解有兩個用法,一個是用於方法上,一個是用於引數上;
用於方法上時: 通常用來在處理@RequestMapping之前,為請求繫結需要從後臺查詢的model;
用於引數上時: 用來通過名稱對應,把相應名稱的值繫結到註解的引數bean上;要繫結的值來源於:
A) @SessionAttributes 啟用的attribute 物件上;
B) @ModelAttribute 用於方法上時指定的model物件;
C) 上述兩種情況都沒有時,new一個需要繫結的bean物件,然後把request中按名稱對應的方式把值繫結到bean中。
用到方法上@ModelAttribute的示例程式碼:
@ModelAttribute("string1")
public String preRun() {
System.out.println("Test Pre-Run");
String string="linbingwen";
return string;
}
這種方式實際的效果就是在呼叫@RequestMapping的方法之前,為request物件的model裡put(“string1”, string);
JSP中可以這樣來獲取
<%@ page language="java" contentType="text/html; charset=gb2312"
pageEncoding="gb2312"%>
<!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=gb2312">
<title>Insert title here</title>
</head>
<body>
< 通過@ModelAttribute類方法傳遞引數:<br/>
方法一取出"{string1}":${string1}<br/>
<%String string1=(String)request.getAttribute("string1");%>
方法二取出"request.getAttribute":<%=string1 %><br/>
</body>
</html>
它會在每次訪問網頁前呼叫:
輸入http://localhost:8080/SpringMVCLearningChapter2/six/index2,總共重新整理了4次
用在引數上的@ModelAttribute示例程式碼:
@RequestMapping(value ="six/index2")//相對於根目錄的路徑
public String test(@ModelAttribute("str") String str) {
return "index2";//指定頁面要跳轉的view檢視路徑
}
其實就是相相當於把引數傳遞到request中去,所以JSP中可以這樣獲取引數<%@ page language="java" contentType="text/html; charset=gb2312"
pageEncoding="gb2312"%>
<!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=gb2312">
<title>Insert title here</title>
</head>
<body>
==========================================================<br/>
通過@ModelAttribute方法上引數傳遞引數:
<%String str=(String)request.getParameter("str");%>
"request.getAttribute":<%=str%><br/>
</body>
</html>
執行結果:5、ResponseBody
用來輸出響應,如
@ResponseBody
@RequestMapping(value="/index/{imageld}")//相對於根目錄的路徑
public byte[] test2(@PathVariable("imageld") String str) {
System.out.println(str);
Resource resource= new ClassPathResource("/image.jpg");
byte[] data = null;
try {
data = FileCopyUtils.copyToByteArray(resource.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
return data;//指定頁面要跳轉的view檢視路徑
}
輸入:http://localhost:8080/SpringMVCLearningChapter2_1/index/imgd
四、補充講解
問題: 在不給定註解的情況下,引數是怎樣繫結的?
通過分析AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter的原始碼發現,方法的引數在不給定引數的情況下:
若要繫結的物件時簡單型別: 呼叫@RequestParam來處理的。
若要繫結的物件時複雜型別: 呼叫@ModelAttribute來處理的。
這裡的簡單型別指java的原始型別(boolean, int 等)、原始型別物件(Boolean, Int等)、String、Date等ConversionService裡可以直接String轉換成目標物件的型別;