神奇好望角 The Magical Cape of Good Hope

          庸人不必自擾,智者何需千慮?
          posts - 26, comments - 50, trackbacks - 0, articles - 11
            BlogJava :: 首頁 ::  :: 聯(lián)系 :: 聚合  :: 管理

          JAX-RS 從傻逼到牛叉 3:路徑匹配

          Posted on 2011-10-09 12:43 蜀山兆孨龘 閱讀(4263) 評論(1)  編輯  收藏 所屬分類: Java EESOA

          JAX-RS 的核心功能是處理向 URI 發(fā)送的請求,所以它提供了一些匹配模式以便簡化對 URI 的解析。樓主在本系列的上一篇文章中已經(jīng)使用了最簡單的路徑參數(shù),本文將介紹一些稍微高級點的咚咚。

          模板參數(shù)

          前面已經(jīng)見過用 @Path("{id}")@PathParam("id") 來匹配路徑參數(shù) id。這種匹配方式可以被嵌入到 @Path 注解中的任何地方,從而匹配多個參數(shù),例如下面的代碼用來查找 ID 在某一范圍內(nèi)的電影:

                  @GET
                  @Path("{min}~{max}")
                  @Produces(MediaType.APPLICATION_JSON)
                  public List<Movie> findMovies(@PathParam("min") int min, @PathParam("max") int max) {
              

          于是,GET /ms/rest/movie/5~16 就將返回 ID 為 5 到 16 的電影。此處的 minmax 已被自動轉(zhuǎn)換為 int 類型。JAX-RS 支持多種類型的自動轉(zhuǎn)換,詳見 @PathParam 的文檔。

          根據(jù) HTTP 規(guī)范,參數(shù)可能會編碼。默認情況下,JAX-RS 會自動解碼。如果希望得到未解碼的參數(shù),只需在參數(shù)上再加個 @Encoded 注解。該注解適用于大多數(shù) JAX-RS 注入類型,但并不常用。

          模板參數(shù)雖然靈活,也可能會帶來歧義。例如想用 {firstName}-{lastName} 匹配一個人的姓名,但恰好某人的名(lastName)含有“-”字符,像 O-live K 這種,匹配后就會變成姓 live-K,名 O。這種場景很難避免,一種簡單的解決方法就是對參數(shù)值進行兩次編碼,然后在服務(wù)端代碼解碼一次,因為 JAX-RS 默認會進行一次解碼,或者加上 @Encoded 注解,自己進行兩次解碼。

          另外,在一個復(fù)雜系統(tǒng)中,多個 @Path 可能會造成路徑混淆,例如 {a}-{a}-z 都能匹配路徑 a-z。雖然 JAX-RS 定義了一些規(guī)則來指定匹配的優(yōu)先級,但這些規(guī)則本身就比較復(fù)雜,并且也不能完全消除混淆。樓主認為,設(shè)計一個 REST 系統(tǒng)的核心就是對 URI 的設(shè)計,應(yīng)當小心處理 URI 的結(jié)構(gòu),合理分類,盡量保證匹配的唯一性,而不要過度使用晦澀的優(yōu)先級規(guī)則。樓主將在下一篇文章介紹優(yōu)先級規(guī)則。

          正則表達式

          模板參數(shù)可以用一個正則表達式進行驗證,寫法是在模板參數(shù)的標識符后面加一個冒號,然后跟上正則表達式字符串。例如在根據(jù) ID 查詢電影信息的代碼中,模板參數(shù) {id} 只能是整數(shù),于是代碼可以改進為:

                  @GET
                  @Path("{id : \\d+}")
                  @Produces(MediaType.APPLICATION_JSON)
                  public List<Movie> findMovies(@PathParam("min") int min, @PathParam("max") int max) {
              

          冒號左右的空格將被忽略。用正則表達式驗證數(shù)據(jù)很有局限性,可惜 JAX-RS 目前并不能直接集成 Bean 驗證框架,因此復(fù)雜的驗證只能靠自己寫代碼。

          查詢參數(shù)

          查詢參數(shù)很常見,就是在 URI 的末尾跟上一個問號和一系列由“&”分隔的鍵值對,例如查詢 ID 為 5 到 16 的電影也可以設(shè)計為 /ms/rest/movie?min=5&max=16。JAX-RS 提供了 QueryParam 來注入查詢參數(shù):

                  @GET
                  @Produces(MediaType.APPLICATION_JSON)
                  public List<Movie> findMovies(@DefaultValue("0") @QueryParam("min") int min,
                          @DefaultValue("0") @QueryParam("max") int max) {
              

          查詢參數(shù)是可選的。如果 URI 沒有設(shè)定某個查詢參數(shù),JAX-RS 就會根據(jù)情況為其生成 0、空字符串之類的默認值。如果要手動設(shè)定默認值,需要像上面的代碼一樣用 @DefaultValue 注解來指定。另外還可以加上 Encoded 注解來得到編碼的原始參數(shù)。

          有的查詢參數(shù)是一對多的鍵值對,例如 /xyz?a=def&a=pqr,這種情況只需將注入的參數(shù)類型改為 List 即可。

          矩陣參數(shù)

          矩陣參數(shù)應(yīng)該屬于 URI 規(guī)范中的非主流類型,但它實際上比查詢參數(shù)更靈活,因為它可以嵌入到 URI 路徑中的任何一段末尾(用分號隔開),用來標識該段的某些屬性。例如 GET /ms/rest/movie;year=2011/title;initial=A 表示在 2011 年出品的電影中查找首字母為 A 的標題。year 是電影的屬性,而 initial 是標題的屬性,這比把它們都作為查詢參數(shù)放在末尾更直觀可讀。匹配 URI 的時候,矩陣參數(shù)將被忽略,因此前面的 URI 匹配為 /ms/rest/movie/title。矩陣參數(shù)可以用 @MatrixParam 來注入:

                  @GET
                  @Path("title")
                  @Produces(MediaType.APPLICATION_JSON)
                  public List<String> findTitles(@MatrixParam("year") int year,
                          @MatrixParam("initial") String initial) {
              

          如果 URI 的多個段中含有相同名稱的矩陣參數(shù),例如 /abc;name=XXX/xyz;name=OOO,這種直接注入就失效了,只能用下面要講的編程式訪問來取得。

          編程式訪問

          如果簡單的注入不能達到目的,就需要通過注入 PathSegmentUriInfo 對象來直接編程訪問 URI 的信息。

          一個 PathSegment 對象代表 URI 中的一個路徑段,可以從它得到矩陣參數(shù)。它可以通過 @PathParam 來注入,這要求該路徑段必須整個被定義為一個模板參數(shù)。例如下面的代碼也可以用來處理 GET /ms/rest/movie/{id}

                  @GET
                  @Path("{id}")
                  @Produces(MediaType.APPLICATION_JSON)
                  public Movie findMovie(@PathParam("id") PathSegment ps) {
              

          @PathParam 也可以注入多個段,如果想把 /a/b/c/d 匹配到 /a/{segments}/d,直接注入一個字符串顯然不行,因為 b/c 是兩個路徑段。唯一的選擇是把注入的類型改為 List<PathSegment>。樓主嚴重不推薦用一個模板參數(shù)匹配多個路徑段,因為這很容易干擾其他匹配的設(shè)計,最后搞成一團亂麻。URI 路徑段應(yīng)當盡量設(shè)計得簡單明晰,再輔以矩陣參數(shù)或查詢參數(shù)就能應(yīng)付大多數(shù)場景。不論對服務(wù)端還是客戶端開發(fā)人員來說,簡潔的 URI 既便于管理,又便于使用。網(wǎng)上有不少關(guān)于 URI 設(shè)計指南的文章,此處不再贅述。

          如果想完全手動解析路徑,則可以用 @Context 注入一個 UriInfo 對象,通過此對象可以得到 URI 的全部信息,詳見 API 文檔。例如:

                  @GET
                  @Path("{id}/{segments}")
                  @Produces(MediaType.PLAIN_TEXT)
                  public String getInfo(@PathParam("id") int id, @Context UriInfo uriInfo) {
              

          UriInfo 主要用在某些特殊場合下起輔助作用,設(shè)計良好的 URI 用普通的注入就能完成大部分匹配。


          工欲善其事必先利其器,為此 JAX-RS 提供了這些利器來解析 URI。至于如何用這些器來做出一個好系統(tǒng),則還是依賴于 URI 本身的設(shè)計。


          評論

          # re: JAX-RS 從傻逼到牛叉 3:路徑匹配  回復(fù)  更多評論   

          2011-10-14 04:32 by 與你同飛
          謝謝樓主的文章,很長見識。
          主站蜘蛛池模板: 永川市| 内黄县| 米脂县| 濮阳县| 崇州市| 崇义县| 九寨沟县| 四会市| 马鞍山市| 南澳县| 博客| 平顶山市| 马关县| 宕昌县| 定襄县| 隆化县| 北票市| 阜康市| 清徐县| 台中市| 忻州市| 达州市| 北海市| 水城县| 南昌县| 绍兴县| 额济纳旗| 阿拉善盟| 洪湖市| 仪征市| 大厂| 云浮市| 芦溪县| 平利县| 朝阳县| 南陵县| 海淀区| 四子王旗| 吉安县| 安陆市| 都匀市|