神奇好望角 The Magical Cape of Good Hope

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

          顧名思義,CountDownLatch 是一個(gè)用來(lái)倒計(jì)數(shù)的咚咚。如果某項(xiàng)任務(wù)可以拆分成若干個(gè)子任務(wù)同時(shí)進(jìn)行,然后等待所有的子任務(wù)完成,可以考慮使用它。

          該類的用法非常簡(jiǎn)單。首先構(gòu)造一個(gè) CountDownLatch,唯一的參數(shù)是任務(wù)數(shù)量,一旦構(gòu)造完畢就不能修改。接著啟動(dòng)所有的子任務(wù)(線程),且每個(gè)子任務(wù)在完成自己的計(jì)算后,調(diào)用 CountDownLatch#countDown 方法將倒計(jì)數(shù)減一。最后在主線程中調(diào)用 CountDownLatch#await 方法等待計(jì)數(shù)器歸零。

          例如賽跑的準(zhǔn)備階段,八名運(yùn)動(dòng)員先后到達(dá)起點(diǎn)做好準(zhǔn)備,然后裁判打響發(fā)令槍,準(zhǔn)備工作就結(jié)束了,比賽開始。如果把從運(yùn)動(dòng)員就位到發(fā)令槍響看做賽跑準(zhǔn)備任務(wù),那么每個(gè)運(yùn)動(dòng)員的準(zhǔn)備過(guò)程就是其子任務(wù),可以用 CountDownLatch 模擬如下:

                  final int count = 8;
                  System.out.println("運(yùn)動(dòng)員開始就位。");
          
                  // 構(gòu)造 CountDownLatch。
                  final CountDownLatch cdl = new CountDownLatch(count);
                  for (int i = 1; i <= count; i++) {
                      final int number = i;
                      new Thread() {
                          @Override
                          public void run() {
                              System.out.println(number + " 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...");
                              try {
                                  // 讓運(yùn)動(dòng)員隨機(jī)準(zhǔn)備 2~5 秒鐘。
                                  TimeUnit.SECONDS.sleep(new Random().nextInt(4) + 2);
                              } catch (InterruptedException ex) {
                              }
                              System.out.println(number + " 號(hào)運(yùn)動(dòng)員就位。");
                              // 倒計(jì)數(shù)減一。
                              cdl.countDown();
                          }
                      }.start();
                  }
          
                  System.out.println("等待所有運(yùn)動(dòng)員就位...");
                  try {
                      // 等待倒計(jì)數(shù)變?yōu)?0。
                      cdl.await();
                      System.out.println("比賽開始。");
                  } catch (InterruptedException ex) {
                  }
              

          運(yùn)行輸出(可能)為:

          運(yùn)動(dòng)員開始就位。
          1 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          2 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          4 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          等待所有運(yùn)動(dòng)員就位...
          8 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          6 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          3 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          7 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          5 號(hào)運(yùn)動(dòng)員到場(chǎng)并開始準(zhǔn)備...
          6 號(hào)運(yùn)動(dòng)員就位。
          1 號(hào)運(yùn)動(dòng)員就位。
          5 號(hào)運(yùn)動(dòng)員就位。
          4 號(hào)運(yùn)動(dòng)員就位。
          7 號(hào)運(yùn)動(dòng)員就位。
          8 號(hào)運(yùn)動(dòng)員就位。
          2 號(hào)運(yùn)動(dòng)員就位。
          3 號(hào)運(yùn)動(dòng)員就位。
          比賽開始。

          從上面的例子還可以看出 CountDownLatch 的局限性和 CompletionService 類似,在于無(wú)法處理子任務(wù)數(shù)量不確定的情況,例如統(tǒng)計(jì)某個(gè)文件夾中的文件數(shù)量。另外,如果某個(gè)子任務(wù)在調(diào)用 countDown 之前就掛掉了,倒計(jì)數(shù)就永遠(yuǎn)不會(huì)歸零。對(duì)于這種情況,要么用 finally 之類的手段保證 countDown 一定會(huì)被調(diào)用,要么用帶參數(shù)的 await 方法指定超時(shí)時(shí)間。

          posted @ 2011-10-14 14:22 蜀山兆孨龘 閱讀(1713) | 評(píng)論 (1)編輯 收藏

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

          模板參數(shù)

          前面已經(jīng)見過(guò)用 @Path("{id}")@PathParam("id") 來(lái)匹配路徑參數(shù) id。這種匹配方式可以被嵌入到 @Path 注解中的任何地方,從而匹配多個(gè)參數(shù),例如下面的代碼用來(lái)查找 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 已被自動(dòng)轉(zhuǎn)換為 int 類型。JAX-RS 支持多種類型的自動(dòng)轉(zhuǎn)換,詳見 @PathParam 的文檔。

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

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

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

          正則表達(dá)式

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

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

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

          查詢參數(shù)

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

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

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

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

          矩陣參數(shù)

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

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

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

          編程式訪問(wèn)

          如果簡(jiǎn)單的注入不能達(dá)到目的,就需要通過(guò)注入 PathSegmentUriInfo 對(duì)象來(lái)直接編程訪問(wèn) URI 的信息。

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

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

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

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

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

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


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

          posted @ 2011-10-09 12:43 蜀山兆孨龘 閱讀(4256) | 評(píng)論 (1)編輯 收藏

          CompletionService 接口的實(shí)例可以充當(dāng)生產(chǎn)者和消費(fèi)者的中間處理引擎,從而達(dá)到將提交任務(wù)和處理結(jié)果的代碼進(jìn)行解耦的目的。生產(chǎn)者調(diào)用 submit 方法提交任務(wù),而消費(fèi)者調(diào)用 poll(非阻塞)或 take(阻塞)方法獲取下一個(gè)結(jié)果:這一特征看起來(lái)和阻塞隊(duì)列(BlockingQueue)類似,兩者的區(qū)別在于 CompletionService 要負(fù)責(zé)任務(wù)的處理,而阻塞隊(duì)列則不會(huì)。

          在 JDK 中,該接口只有一個(gè)實(shí)現(xiàn)類 ExecutorCompletionService,該類使用創(chuàng)建時(shí)提供的 Executor 對(duì)象(通常是線程池)來(lái)執(zhí)行任務(wù),然后將結(jié)果放入一個(gè)阻塞隊(duì)列中:果然本就是一家親啊!ExecutorCompletionService 將線程池和阻塞隊(duì)列糅合在一起,僅僅通過(guò)三個(gè)方法,就實(shí)現(xiàn)了任務(wù)的異步處理,可謂并發(fā)編程初學(xué)者的神兵利器!

          接下來(lái)看一個(gè)例子。樓主有一大堆 *.java 文件,需要計(jì)算它們的代碼總行數(shù)。利用 ExecutorCompletionService 可以寫出很簡(jiǎn)單的多線程處理代碼:

                  public int countLines(List<Path> javaFiles) throws Exception {
                      // 根據(jù)處理器數(shù)量創(chuàng)建線程池。雖然多線程并不保證能夠提升性能,但適量地
                      // 開線程一般可以從系統(tǒng)騙取更多資源。
                      ExecutorService es = Executors.newFixedThreadPool(
                              Runtime.getRuntime().availableProcessors() * 2);
                      // 使用 ExecutorCompletionService 內(nèi)建的阻塞隊(duì)列。
                      CompletionService cs = new ExecutorCompletionService(es);
          
                      // 按文件向 CompletionService 提交任務(wù)。
                      for (final Path javaFile : javaFiles) {
                          cs.submit(new Callable<Integer>() {
                              @Override
                              public Integer call() throws Exception {
                                  // 略去計(jì)算單個(gè)文件行數(shù)的代碼。
                                  return countLines(javaFile);
                              }
                          });
                      }
          
                      try {
                          int loc = 0;
                          int size = javaFiles.size();
                          for (int i = 0; i < size; i++) {
                              // take 方法等待下一個(gè)結(jié)果并返回 Future 對(duì)象。不直接返回計(jì)算結(jié)果是為了
                              // 捕獲計(jì)算時(shí)可能拋出的異常。
                              // poll 不等待,有結(jié)果就返回一個(gè) Future 對(duì)象,否則返回 null。
                              loc += cs.take().get();
                          }
                          return loc;
                      } finally {
                          // 關(guān)閉線程池。也可以將線程池提升為字段以便重用。
                          // 如果任務(wù)線程(Callable#call)能響應(yīng)中斷,用 shutdownNow 更好。
                          es.shutdown();
                      }
                  }
              

          最后,CompletionService 也不是到處都能用,它不適合處理任務(wù)數(shù)量有限但個(gè)數(shù)不可知的場(chǎng)景。例如,要統(tǒng)計(jì)某個(gè)文件夾中的文件個(gè)數(shù),在遍歷子文件夾的時(shí)候也會(huì)“遞歸地”提交新的任務(wù),但最后到底提交了多少,以及在什么時(shí)候提交完了所有任務(wù),都是未知數(shù),無(wú)論 CompletionService 還是線程池都無(wú)法進(jìn)行判斷。這種情況只能直接用線程池來(lái)處理。

          posted @ 2011-09-29 13:37 蜀山兆孨龘 閱讀(2070) | 評(píng)論 (0)編輯 收藏

          實(shí)體間的多對(duì)多的關(guān)聯(lián)需要一張關(guān)聯(lián)表。如果直接使用 ManyToMany 來(lái)映射,JPA 就會(huì)隱式地幫我們自動(dòng)管理關(guān)聯(lián)表,代碼寫出來(lái)和其他類型的關(guān)聯(lián)差別不大。例如,某州炒房團(tuán)需要一個(gè)炒房跟蹤系統(tǒng),那么該系統(tǒng)中的炒房客和房子就是多對(duì)多的關(guān)系:

                  public class Speculator implements Serializable {
                      @Id
                      private Integer id;
                      @ManyToMany
                      @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                              inverseJoinColumns = @JoinColumn(name = "house_id"))
                      private List<House> houses;
                      // 此處省略若干行
                  }
          
                  public class House implements Serializable {
                      @Id
                      private Integer id;
                      @ManyToMany(mappedBy = "houses")
                      private List<Speculator> speculators;
                      // 此處省略若干行
                  }
              

          如果炒房客 s 要賣掉房子 h(嚴(yán)格點(diǎn)說(shuō)是賣掉房子的產(chǎn)權(quán)部分),那么系統(tǒng)執(zhí)行的代碼差不多就是 s.getHouses().remove(h)。看似簡(jiǎn)單,然而底層的操作卻性能低下:JPA 會(huì)先從數(shù)據(jù)庫(kù)中取出炒房客的所有房產(chǎn)(s.getHouses()),然后再刪除指定的那套房子;從數(shù)據(jù)庫(kù)層面上看,這將先從關(guān)聯(lián)表(speculator_house)中找到該炒房客的所有房子的外鍵,然后從 house 表載入這些 House 對(duì)象,最后才從 speculator_house 刪除關(guān)聯(lián)。在 ORM 出現(xiàn)前,這種操作只需要改關(guān)聯(lián)表,根本不用關(guān)心其他房子。這種簡(jiǎn)單的多對(duì)多映射寫法將關(guān)聯(lián)表隱藏起來(lái),雖然簡(jiǎn)化了代碼,卻也可能帶來(lái)性能隱患。

          很自然地可以想到,如果把關(guān)聯(lián)表也映射成實(shí)體類,就能解決這個(gè)問(wèn)題。speculator_house 包含兩個(gè)外鍵,可用作聯(lián)合主鍵。如果把它映射為 SpeculatorHouse 類,則該類與 SpeculatorHouse 都是多對(duì)一的關(guān)系。關(guān)聯(lián)表實(shí)體類的代碼如下(EmbeddedId 的映射技巧見《JPA 應(yīng)用技巧 2:主鍵外鍵合體映射》):

                  @Embeddable
                  public class SpeculatorHouseId implements Serializable {
                      private Integer speculatorId;
                      private Integer houseId;
                      // 此處省略若干行
                  }
          
                  @Entity
                  @Table(name = "speculator_house")
                  public class SpeculatorHouse implements Serializable {
                      @EmbeddedId
                      private SpeculatorHouseId id;
                      @MapsId("speculatorId")
                      @ManyToOne
                      private Speculator speculator;
                      @MapsId("houseId")
                      @ManyToOne
                      private House house;
                      // 此處省略若干行
                  }
              

          SpeculatorHouse 也要增加相應(yīng)的關(guān)聯(lián)信息:

                  public class Speculator implements Serializable {
                      @Id
                      private Integer id;
                      @ManyToMany
                      @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                              inverseJoinColumns = @JoinColumn(name = "house_id"))
                      private List<House> houses;
                      @OneToMany(mappedBy = "speculator")
                      private List<SpeculatorHouse> speculatorHouses;
                      // 此處省略若干行
                  }
          
                  public class House implements Serializable {
                      @Id
                      private Integer id;
                      @ManyToMany(mappedBy = "houses")
                      private List<Speculator> speculators;
                      @OneToMany(mappedBy = "house")
                      private List<SpeculatorHouse> speculatorHouses;
                      // 此處省略若干行
                  }
              

          這樣既保留了多對(duì)多關(guān)系,又映射了關(guān)聯(lián)表,然后就可以根據(jù)實(shí)際情況選擇隱式或顯示的關(guān)聯(lián)表管理。例如,要得到一個(gè)炒房客的全部房子,就使用隱式管理:s.getHouses();而要?jiǎng)h除炒房客和某套房子的關(guān)聯(lián),則用顯示管理:delete from SpeculatorHouse sh where sh.speculator = :s and sh.house = :h

          posted @ 2011-09-27 11:04 蜀山兆孨龘 閱讀(3258) | 評(píng)論 (2)編輯 收藏

          JAX-RS 使用注解進(jìn)行配置,所以用它開發(fā) REST 風(fēng)格的服務(wù)非常簡(jiǎn)單。樓主在本文用一個(gè)小例子來(lái)說(shuō)明 JAX-RS 的基本用法。


          假設(shè)樓主要開發(fā)一個(gè)小電影服務(wù),客戶端可以通過(guò)請(qǐng)求 URI 對(duì)電影進(jìn)行 CRUD 操作。為簡(jiǎn)明起見,這兒不使用數(shù)據(jù)庫(kù),只在內(nèi)存中模擬。先用一個(gè)非常簡(jiǎn)單的 Movie 類,在后續(xù)的文章中根據(jù)情況逐步擴(kuò)充:

                  public class Movie {
                      private int id;
                      private String title;
                      // 此處省略若干行
                  }
              

          嗯,就是一個(gè)很普通的 JavaBean,實(shí)際項(xiàng)目中可以根據(jù)需要加上 @Entity 等注解。接下來(lái)看看如何編寫 JAX-RS 服務(wù)。


          一個(gè) JAX-RS 服務(wù)就是一個(gè)使用了 JAX-RS 注解來(lái)將 HTTP 請(qǐng)求綁定到方法的 Java 類,一共支持兩種類型:?jiǎn)握?qǐng)求對(duì)象或單例對(duì)象。單請(qǐng)求對(duì)象意味著每來(lái)一個(gè)請(qǐng)求,就創(chuàng)建一個(gè)服務(wù)對(duì)象,在請(qǐng)求結(jié)束時(shí)銷毀。單例對(duì)象則意味著只有一個(gè)服務(wù)對(duì)象處理所有的請(qǐng)求,從而可以在多個(gè)請(qǐng)求間維持服務(wù)狀態(tài)。JAX-RS 服務(wù)可通過(guò)繼承 javax.ws.rs.core.Application 來(lái)定義,其中的 getClasses 方法返回單請(qǐng)求對(duì)象的類型,getSingletons 方法返回單例對(duì)象的類型。這兩個(gè)方法是可選的。在 Java EE 6 環(huán)境中,如果這兩個(gè)方法都返回 null 或者空集合,那么應(yīng)用程序中的所有 JAX-RS 都將被部署。這時(shí)可以用 CDI 的 @javax.inject.Singleton 或者 EJB 的 @javax.ejb.Singleton 注解來(lái)指定單例對(duì)象。

          如果電影服務(wù)的上下文根路徑為 http://localhost/ms,而樓主希望將服務(wù)部署到 http://localhost/ms/rest 下面,只需要寫一個(gè)類:

                  @ApplicationPath("rest")
                  public class RestApplication extends Application {
                  }
              

          @ApplicationPath 注解指定所有服務(wù)的相對(duì)基址,如果為空字符串,則直接使用上下文根路徑。另一種配置方式是在 web.xml 文件中進(jìn)行聲明,那是為了使 JAX-RS 能在 Servlet 容器(例如 Tomcat)中運(yùn)行,此處略過(guò)。這項(xiàng)配置必不可少,否則無(wú)法部署服務(wù)。


          很好很強(qiáng)大,現(xiàn)在開始編寫電影服務(wù)類 MovieService,先看看聲明和初始化:

                  @Singleton
                  @Path("movie")
                  public class MovieService {
                      private AtomicInteger ai;
                      private ConcurrentMap<Integer, Movie> movieMap;
          
                      @PostConstruct
                      private void init() {
                          ai = new AtomicInteger();
                          movieMap = new ConcurrentHashMap<>();
                          int id = ai.getAndIncrement();
                          movieMap.put(id, new Movie().setId(id).setTitle("Avatar"));
                      }
              

          因?yàn)闃侵髦恍枰粋€(gè)“內(nèi)存數(shù)據(jù)庫(kù)”,所以用單例對(duì)象即可,此處使用 CDI 的 @javax.inject.Singleton 來(lái)聲明單例。@Path 聲明了一個(gè)服務(wù),它指示 MovieService 負(fù)責(zé)處理發(fā)送到 http://localhost/ms/rest/movie 的請(qǐng)求。路徑的拼接方式非常直觀。init 方法帶有 @PostConstruct 注解,因此將在 MovieService 構(gòu)造完成后立即調(diào)用,它向 movieMap 中存入了一個(gè) ID 為 0 的 Movie 對(duì)象。為簡(jiǎn)化代碼,Movie 的設(shè)置方法都返回 this,有點(diǎn)偽造構(gòu)建者模式的味道。


          接下來(lái)看看如何處理 HTTP 請(qǐng)求。

          GET

          GET 請(qǐng)求用于獲取一個(gè)或多個(gè)資源。在本例中用來(lái)獲取一部電影的信息:

                  @GET
                  @Path("{id}")
                  @Produces(MediaType.APPLICATION_JSON)
                  public Movie find(@PathParam("id") int id) {
                      Movie movie = movieMap.get(id);
                      if (movie != null) {
                          return movie;
                      } else {
                          throw new WebApplicationException(Response.Status.NOT_FOUND);
                      }
                  }
              

          該方法標(biāo)注了 @GET,表示用來(lái)處理向 http://localhost/ms/rest/movie/{id} 發(fā)送的 GET 請(qǐng)求。@Path 再次用來(lái)綁定路徑,注意其參數(shù) {id},它帶有花括號(hào),對(duì)應(yīng) URI 的最后一段,也正好和方法參數(shù) id@PathParam 的值相對(duì)應(yīng)。這種參數(shù)還有很多高級(jí)用法,以后再介紹。@Produces 注解指定輸出格式為 JSON。JAX-RS 內(nèi)置了很多格式,詳見 MediaType 的文檔。如果找到了相應(yīng) ID 的對(duì)象,則將其返回,JAX-RS 會(huì)自動(dòng)加上響應(yīng)碼 200 OK;否則拋出異常,錯(cuò)誤碼為 404 Not Found。

          例如,通過(guò)瀏覽器訪問(wèn) http://localhost/ms/rest/movie/0,得到的結(jié)果為 {"@id":"0","@title":"Avatar"}。

          POST

          POST 請(qǐng)求用于創(chuàng)建一個(gè)資源。在本例中用來(lái)創(chuàng)建一部電影:

                  @POST
                  @Consumes(MediaType.APPLICATION_JSON)
                  public Response create(Movie movie) {
                      int id = ai.getAndIncrement();
                      movieMap.put(id, movie.setId(id));
                      return Response.created(URI.create(String.valueOf(id))).build();
                  }
              

          由于沒(méi)有 @Path 注解,所以 POST 請(qǐng)求的目標(biāo)就直接是 http://localhost/ms/rest/movie。Consumes@Produces 相反,表示接受的數(shù)據(jù)類型,此處 JAX-RS 會(huì)自動(dòng)把 JSON 數(shù)據(jù)轉(zhuǎn)換為 Movie 對(duì)象。返回的響應(yīng)碼為 201 Created,并且?guī)в兴鶆?chuàng)建資源的 URI。

          例如,向 http://localhost/ms/rest/movie 發(fā)送 POST 請(qǐng)求,正文為 {"@title": "007"},則可以從 FireBug 的網(wǎng)絡(luò)監(jiān)控中看到返回的響應(yīng)碼,以及頭部中 Location 的值為 http://localhost:8080/rest/service/movie/1。多次發(fā)送該 POST 請(qǐng)求,將會(huì)創(chuàng)建多個(gè)資源,以保證 POST 不是冪等的。

          PUT

          PUT 請(qǐng)求用于創(chuàng)建或更新一個(gè)資源。與 POST 不同,PUT 請(qǐng)求要指定某個(gè)特定資源的地址。在本例中用來(lái)更新一部電影的信息:

                  @PUT
                  @Path("{id}")
                  @Consumes(MediaType.APPLICATION_JSON)
                  public Response update(@PathParam("id") int id, Movie movie) {
                      movie.setId(id);
                      if (movieMap.replace(id, movie) != null) {
                          return Response.ok().build();
                      } else {
                          throw new WebApplicationException(Response.Status.NOT_FOUND);
                      }
                  }
              

          更新成功就返回 200 OK,否則返回 404 Not Found。這兒先把 movie 對(duì)象的 ID 強(qiáng)制改為 URI 所指定的,以免出現(xiàn)不一致。也可以根據(jù)需求,將不一致作為異常處理,給客戶端返回一個(gè)錯(cuò)誤碼。

          順便啰嗦一句,反正代碼在自己手中,樓主也可以把 PUT 搞成非冪等的,例如將 PUT 當(dāng)成 POST 來(lái)處理,就像以前把 GET 和 POST 一視同仁那樣。不過(guò)咱既然在搞 JAX-RS,就還是要沾染一點(diǎn) REST 風(fēng)格,嚴(yán)格遵守 HTTP 才是。

          DELETE

          DELETE 請(qǐng)求用于刪除一個(gè)資源。在本例中用來(lái)刪除一部電影:

                  @DELETE
                  @Path("{id}")
                  public Response delete(@PathParam("id") int id) {
                      if (movieMap.remove(id) != null) {
                          return Response.ok().build();
                      } else {
                          throw new WebApplicationException(Response.Status.NOT_FOUND);
                      }
                  }
              

          沒(méi)什么特別的,該說(shuō)的前面都說(shuō)了。

          HEAD 和 OPTIONS 請(qǐng)求就忽略吧,用得不太多,也同樣挺簡(jiǎn)單的。


          JAX-RS 服務(wù)的部署和部署常規(guī) Web 程序一樣,打包成 war 文件就可以了。最后贊一下 NetBeans 可以為 REST 風(fēng)格的服務(wù)自動(dòng)生成測(cè)試頁(yè)面,很好用,雖然在 Firefox 下頁(yè)面顯示不正常(對(duì)此我已經(jīng)提了一個(gè) bug),但 IE 是可以的。

          posted @ 2011-09-20 17:22 蜀山兆孨龘 閱讀(9743) | 評(píng)論 (3)編輯 收藏

          僅列出標(biāo)題
          共8頁(yè): 上一頁(yè) 1 2 3 4 5 6 7 8 下一頁(yè) 
          主站蜘蛛池模板: 井陉县| 顺平县| 宜昌市| 交口县| 上蔡县| 靖远县| 齐齐哈尔市| 涟水县| 榆社县| 朔州市| 兴隆县| 普格县| 罗江县| 绍兴县| 宁海县| 通渭县| 周口市| 仪征市| 天峻县| 临湘市| 邳州市| 宿松县| 澄江县| 竹溪县| 广平县| 临湘市| 浪卡子县| 怀远县| 汕尾市| 刚察县| 盖州市| 江山市| 吉安市| 平武县| 商南县| 龙山县| 潜山县| 武川县| 乌鲁木齐县| 延边| 哈巴河县|