1. 程式人生 > >springMVC4(2)請求對映全面分析

springMVC4(2)請求對映全面分析

在springMVC的控制器中,我們常使用@RequestMapping來完成我們的請求對映,我們可以在類定義上和方法定義上使用註解,其配置的路徑將為類中定義的所有方法的父路徑,如上篇例項中的/user(類)/hello(方法)。
一般的,我們類定義上的路徑註解起到名稱空間的作用,防止不同方法的路徑對映產生衝突,比如我在UserController和ArticleController下都定義瞭如下的方法:

@RequestMapping("list")
public void list(){
    ....
}

一個list對映路徑,這時候springMVC就不知道該將請求交給到哪個方法處理。當然,我們也能在方法上進行二級路徑配置區分:

/*************UserController***********/
@RequestMapping("user/list")
public void list(){
    ....
}
/*************ArticleController***********/
@RequestMapping("article/list")
public void list(){
    ....
}

這樣就能有效防止衝突了,但如果我有很多個方法存在這樣的衝突,是否都要在每個方法加上字首呢?這時候我們可以選擇在類路徑上註解@RequestMapping來對全體方法進行區分。

通過url進行對映

1. Ant風格字元匹配

除了標準的url外,@RequestMapping還支援Ant風格字元,即”?”、”*”、”**”,其中
1. “?”:匹配一個任意字元,如/user/a?,匹配user/aa,user/ab等路徑
2. “*”:匹配任意字串,如/user/a*,匹配/user下任意以a開頭的路徑如/user/abc,/user/aqw等
3. “**“:匹配多級路徑字串,如/user/**/list,匹配/user/user1/list,/user/1resu/list等

在這裡,需要注意的是當*的位置個數不同時,*可以代表的字元數有區別,看下面示例:

@RequestMapping("u1/*"
)//只能匹配u1/a,u1/b,不能匹配u1/————即此時*表示一個或多個字元 public void test(HttpServletResponse response) throws IOException{ response.getWriter().print("u1/*"); } @RequestMapping("u1/**")//能夠匹配u1/,u1/qq,u1/qq/ww,這裡要特別注意的是,“**“能匹配零個而“*”不能 public void test(HttpServletResponse response) throws IOException{ response.getWriter().print("u1/*"); } @RequestMapping("u2/a*")//能夠匹配u2/a,u2/ab,u2/aqqqq等————即此時*表示零個或零個以上字元 public void test1(HttpServletResponse response) throws IOException{ response.getWriter().print("u2/a*"); }

2. restful佔位符匹配

除了使用上面風格,@RequestMapping還支援restful風格佔位符的形式,假如我們需要針對特定使用者檢視其特定文章,restful風格路徑匹配如下所示:


@Controller//註解為控制器,通過spring容器掃描,會註冊為一個Bean
@RequestMapping("/user/{uid}")//一級訪問路徑,對類中所有方法生效
public class UserController {
    @RequestMapping("article/{aid}")
    public String detail(@PathVariable("uid")Integer uid,@PathVariable("aid")Integer aid){
        System.out.println( "檢視id為" + uid + "的使用者文章,且文章id為"+aid);
        return "someplace";
    }
}

這裡,如果我們想訪問使用者id為1,文章id為2的使用者文章,就可以訪問如下路徑:[專案根路徑]/user/1/article/2來完成。
我們使用@PathVariable(“val”)來完成對應路徑中{val}的資源請求,這裡的兩個val名稱需一致,緊接著的方法入參名字任意,我們剛剛示例了一個多路徑引數繫結,假設只有一個,如下也是合法的:

@RequestMapping("user/{uid}")
    public String detail(@PathVariable("uid")Integer notUid){//notUid名字也能成功繫結
        return "someplace";
    }

此外,如果我們入參名字和url路徑資源名稱一致,則可以省略配置@PathVariable中的value值,如下例項也能正確繫結路徑資源到入參

@RequestMapping("user/{uid}")
    public String detail(@PathVariable Integer uid){//notUid名字也能成功繫結
        return "someplace";
    }

3. 優先匹配規則

url還有如下兩個常見匹配準則:最長最精確優先匹配佔位符優先匹配

1. 最長最精確優先匹配

下面我們來看一個匹配例項:

@RequestMapping("test/**")
public void test2(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/**");
}
@RequestMapping("test/*")
public void test3(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/*");
}
@RequestMapping("test/*/**")
public void test4(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/*/**");
}
@RequestMapping("test/*/*")
public void test5(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/*/*");
}
@RequestMapping("test/1/*")
public void test6(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/1/*");
}
@RequestMapping("test/1/2")
public void test7(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/1/2");
}

直接看上面匹配會覺得很亂,我們直接看下面的測試:

測試 匹配結果
test/a 匹配test/*而不匹配test/**(更精確優先匹配)
test/a/a/aa/a 匹配test/**而不匹配test/*/**,(在多層匹配中,**比*/**更精確)
test/a/a 匹配test/*/*,因為/*/*比**精確
test/1/a 匹配test/1/*,因為/1/*比/*/*精確
test/1/2 匹配test/1/2,這是完全匹配

2. 佔位符優先匹配原則

佔位符是指@PathVariable等路徑資源佔位符,下面我們在看一個例項

@RequestMapping("test/1/2")
public void test7(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/1/2");
}
@RequestMapping("test/1/{id}")
public void test8(HttpServletResponse response,@PathVariable Integer id ) throws IOException{
    response.getWriter().print("test/1/(myId=)" + id );
}
@RequestMapping("test/1/a")
public void test7(HttpServletResponse response) throws IOException{
    response.getWriter().print("test/1/a");
}

從上一個例項的所有路徑對映中,我們測試出test/1/2是最精確的。但我們根據添加了佔位符對映,在遊覽器輸入test/1/2,此時遊覽器返回test/1/(myId=)2,即佔位符的優先順序比普通字串的優先順序更高!但如果我們此時輸入test/1/a。程式不會因為我們的在方法入參中id對映為Integer型別而放棄匹配,佔位符的優先順序依然比字元(串)a的優先順序高,但由於“a”不能轉化為Integer型別,所以伺服器會返回400錯誤

通過HTTP其它請求資源對映

除了使用url外,我們還能通過請求引數、請求方法、或請求頭進行對映
我們先看看@RequestMapping的完整屬性列表:

屬性 說明
value 指定請求的實際地址, 比如 /action/info之類。
method 指定請求的method型別, GET、POST、PUT、DELETE等
consumes 指定處理請求的提交內容型別(Content-Type),例如application/json, text/html;
produces 指定返回的內容型別,僅當request請求頭中的(Accept)型別中包含該指定型別才返回
params 指定request中必須包含某些引數值是,才讓該方法處理
headers 指定request中必須包含某些指定的header值,才能讓該方法處理請求

其中,consumes, produces使用content-type資訊進行過濾資訊;headers中可以使用content-type進行過濾和判斷。
在前面的使用中,我們發現並沒有指定value屬性,直接在括號裡輸入字串也能向value屬性賦值,這是因為在java註解中不加其他屬性,直接賦值必定是針對註解的value成員,如果該註解沒有名為value的成員,則會報錯
下面我們先看幾個示例:

示例1:vmethod,headers

@RequestMapping(value = "testa",method = RequestMethod.POST,headers = "content-type=text/*")

表示對映路徑為testa,請求方法必須為POST方法(如果我們用post發出請求,會返回錯誤資訊HTTP Status 405 - Request method ‘GET’ not supported),headers部分表示請求頭資訊中必須包含等號後相應部分內容,*匹配任意字串

示例2:consumes

@RequestMapping(value = "testb", consumes="application/json") 

表示方法僅匹配request Content-Type為“application/json”型別的請求。

示例3:produces

@RequestMapping(value = "/testc", produces="application/json")

表示方法匹配的請求需要請求頭中Accept部分包含”application/json“,同時在響應時,會將返回內容同時設定為”application/json‘’

示例4:params

@RequestMapping(value = "testd",method = RequestMethod.GET,params = {"id1","id2"})
public void test12(HttpServletResponse response,Integer id1,Integer id2) throws IOException{
    response.getWriter().print(id1 + "——" + id2);
}

示例表示入參需包含引數名為id1,id2的兩個引數,這裡如果我輸入:
1. http://localhost:8080/springMVC/user/testd—-
2. http://localhost:8080/springMVC/user/testd?id1=1報404錯誤
3. ttp://localhost:8080/springMVC/user/testd?id1=1&id2=2—-返回1——2
4. ttp://localhost:8080/springMVC/user/testd?id1=1&id2=2&id3=3—-返回1——2

從以上我們可以看出,只有具有相應引數的才能完成對映,且可以有除了params中要求以外的引數,如id3。

在params的常見對映規則如下:

示例規則 說明
”param1” 請求必須包含名為param1的引數
“!param1” 請求中不能包含名為param1的引數
“param1!=value1 請求中必須包含param1引數,但其值不能為value1
{“param1=value1”,”param2”} 請求中需要包含param1引數和param2引數,且param1的值必須為value1