1. 程式人生 > >SpringMVC註解驅動的控制器詳解

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轉換成目標物件的型別;