神奇好望角 The Magical Cape of Good Hope

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

          目前我們的電影服務只提供了對電影信息的訪問服務,現在我們要再增加兩項級服務,分別用來訪問導演和演員信息。加上原先的電信信息服務,我們把 URI 統一放到 /ms/rest/service/ 的子路徑下。最先想到的方法就是為這三個 URI 分別寫 JAX-RS 服務:

          @Singleton
          @Path("service/movie")
          public class MovieService {
              // 此處省略若干行
          }
          
          @Singleton
          @Path("service/director")
          public class DirectorService {
              // 此處省略若干行
          }
          
          @Singleton
          @Path("service/director")
          public class ActorService {
              // 此處省略若干行
          }
              

          這種寫法的缺點就是讓三個本來有點關系(父級 URI 相同)的服務被放到了毫不相干的三個類里面,不一個個類地查看注解難以看出這點關系。為此,JAX-RS 提供了動態資源綁定的功能,讓我們能夠對這種情況做一些整理。

          首先,我們引入一個服務定位器來處理集中管理這三個子級服務:

          @Singleton
          @Path("service")
          public class ServiceLocator {
              @Inject
              private MovieService movieService;
              @Inject
              private DirectorService directorService;
              @Inject
              private ActorService actorService;
              private Map<String, Object> serviceMap;
          
              @PostConstruct
              private initServiceMap() {
                  serviceMap = new HashMap<>();
                  serviceMap.put("movie", movieService);
                  serviceMap.put("director", directorService);
                  serviceMap.put("actor", actorService);
              }
          
              @Path("{name}")
              public Object locateService(@PathParam("name") String name) {
                  Object service = serviceMap.get(name);
                  if (service == null) {
                      throw new WebApplicationException(Status.SERVICE_UNAVAILABLE);
                  }
                  return service;
              }
          }
              

          該類中的 locateService 方法根據服務的名稱返回相應的服務實例,注意該方法只有一個 @Path 注解,因為它并不清楚請求的具體內容;返回對象的類型為 Object,表明動態資源定位不要求服務類實現相同的接口,只需要它們的方法帶有相應的 JAX-RS 注解,就能夠被 JAX-RS 自動發現和處理(專業術語稱為 introspect,內省),以 MovieService 為例:

          @Singleton
          public class MovieService {
              @GET
              @Path("{id}")
              @Produces(MediaType.APPLICATION_JSON)
              public Movie find(@PathParam("id") int id) {
                  Movie movie = movieDao.get(id);
                  if (movie != null) {
                      return movie;
                  } else {
                      throw new WebApplicationException(Status.NOT_FOUND);
                  }
              }
          
              // 此處省略若干行
          }
          

          這樣,每個請求實際上都由兩個類先后處理。例如,處理請求 GET /ms/rest/service/movie/1 的時候,先由 ServiceLocator 返回相配的服務實例 movieService,然后再由該實例的 find 方法返回結果。比起最開始那三個簡單的類,雖然多了一層調用,但換來了更加清晰的結構。

          動態資源定位是一個非常靈活強大的功能,用好的話,完全可以把 URI 層次整理成一個類似于文件目錄結構的抽象文件系統。

          posted @ 2011-12-21 16:00 蜀山兆孨龘 閱讀(2929) | 評論 (0)編輯 收藏

          籠子大了什么鳥都有。同樣的道理,不論多么細心地設計 URI 結構,在系統復雜到一定程度后,仍然難以避免路徑沖突。為此,JAX-RS 使用一些規則來定義路徑匹配的優先級。

          如果某個請求路徑可以對上多個 URI 匹配模式,那么 JAX-RS 就把可能匹配上的 URI 模式先拼接完整,按照下列規則依次進行比較,直到找出最適合的匹配模式:

          1. 首先,字面字符數量更多的 URI 模式優先。“字面字符”就是寫死的路徑段,不包含路徑分隔符 / 和模板參數。例如 /ms/rest/movie/{id : \\d+} 包含 11 個字面字符。
          2. 其次,模板參數個數最多的 URI 模式優先。例如 /ms/rest/movie/{id : \\d+} 帶一個模板參數。
          3. 最后,含正則表達式的模板參數個數最多的 URI 模式優先。例如 /ms/rest/movie/{id : \\d+} 帶一個含正則表達式的模板參數。

          現在看一個例子。回顧一下,/ms/rest/movie/{id : \\d+} 已經用來根據 ID 獲取電影信息。為了制造麻煩,現在引入 /ms/rest/movie/{title} 來根據電影標題獲取電影信息。先請你猜一猜 /ms/rest/movie/300 代表啥?ID 為 300 的神秘電影,還是我們可愛的勇士?只能跟著規則一條一條地看:

          1. 首先,兩個 URI 匹配模式的字面字符都是 11,下一步。
          2. 其次,兩個 URI 匹配模式都帶一個模板參數,下一步。
          3. 最后,只有 /ms/rest/movie/{id : \\d+} 帶了一個含正則表達式的模板參數,勝利!所以返回 ID 為 300 的片片。

          傳說這三條規則能夠覆蓋 90% 以上的情景。不過我們馬上就能造出一個打破規則的東西:/ms/rest/movie/{title : [ \\w]+}。經過測試,/ms/rest/movie/300 會匹配上 /ms/rest/movie/{id : \\d+}。如何解釋?JAX-RS 規范文檔 3.7.2 定義了完整的匹配規則,對于這兩個簡單的 URI 匹配模式,似乎一直進行到底都無法比較出優先級。莫非有另外的潛規則?或者是 JAX-RS 的實現(參考實現為 Jersey)自行規定?但無論如何,搞出這種怪物本身就是一個設計錯誤,所以也不必去深究原因。

          posted @ 2011-12-07 15:10 蜀山兆孨龘 閱讀(2317) | 評論 (0)編輯 收藏

          以前我曾用兩個類(ZipItemZipSystem)實現了一個簡單的 ZIP 文件系統(以下簡稱 ZFS)。其實這兩個小類挺好用的,而且支持嵌套的 ZIP 文件,但是,但是……JDK 7 丟下來一枚叫做 NIO2 的笑氣炸彈,引入了一套標準的文件系統 API,我承認我中彈了,手癢了,又根據這套 API 重新實現了 ZIP 文件系統,終于在今天初步完工,哈。

          話說,JDK 7 其實捆綁銷售了一個 ZFS,demo 目錄下還有源代碼。可……它達不到我的奢求,而且 BUG 不少。隨便逮兩個:

                  // com.sun.nio.zipfs.ZipFileSystemProvider 類中的方法
                  @Override
                  public Path getPath(URI uri) {
          
                      String spec = uri.getSchemeSpecificPart();
                      int sep = spec.indexOf("!/");
                      if (sep == -1)
                          throw new IllegalArgumentException("URI: "
                              + uri
                              + " does not contain path info ex. jar:file:/c:/foo.zip!/BAR");
                      // 難怪該方法始終拋 IllegalArgumentException 異常,原來你小子把文件的 URI
                      // 當成 ZFS 的 URI 在用……
                      return getFileSystem(uri).getPath(spec.substring(sep + 1));
                  }
          
                  // com.sun.nio.zipfs.ZipFileSystem 類中的方法
                  @Override
                  public PathMatcher getPathMatcher(String syntaxAndInput) {
                      int pos = syntaxAndInput.indexOf(':');
                      // 丫的,pos == syntaxAndInput.length()?!誰寫的?抓出來鞭尸。
                      if (pos <= 0 || pos == syntaxAndInput.length()) {
                          throw new IllegalArgumentException();
              

          很明顯,官方 ZFS 沒有經過代碼審閱、沒有經過測試、沒有經過……然后,@author Xueming Shen,真是丟咱華夏民族的臉……

          下面列個表格詳細比較官方 ZFS 和山寨 ZFS:

          比較內容 官方 ZFS 山寨 ZFS
          實現方式 另起爐灶,用純 Java 重新實現了對 ZIP 文件格式的處理代碼。 基于 ZipFileZipInputStream 這兩個已經穩定多年的類,但涉及了大量本地代碼調用,也許會影響性能。
          讀操作 支持,且通過解壓到臨時文件支持隨機訪問。 支持,但不支持隨機訪問。
          寫操作 通過解壓到臨時文件進行支持,但無法檢測到其他進程對同一個 ZIP 文件的寫操作,不適用于并發環境。 不支持。ZIP 文件事實上是一個整體,對內部條目的任何修改都可能導致重構整個文件,因此所謂的寫操作必須通過臨時文件來處理,效率低下,意義不大,而且難以處理嵌套 ZIP 文件。這也符合我的原則:不解壓。
          嵌套 ZIP 文件 不支持。 支持,當然讀取嵌套 ZIP 文件會慢一些。
          反斜線分隔符 不支持,直接瓜掉。 支持,且和標準的斜線分隔符區別對待。例如,/abc//abc\ 算不同的文件,實際上這兩個能夠并存于 ZIP 文件中。
          空目錄名 不支持,直接瓜掉。 支持。例如 /a/b/a//b 是兩個可以并存且不同的文件。

          山寨 ZFS 的用法示例:

                  Map<String, Object> env = new HashMap<>();
                  // 用于解碼 ZIP 條目名。默認為 Charset.defaultCharset()。
                  env.put("charset", StandardCharsets.UTF_8);
                  // 指示是否自動探測嵌套的 ZIP 文件。默認為 false。
                  env.put("autoDetect", true);
                  // 默認目錄,用于創建和解析相對路徑。默認為“/”。
                  env.put("defaultDirectory", "/dir/");
          
                  // 從文件創建一個 ZFS。
                  try (FileSystem zfs = FileSystems.newFileSystem(
                          URI.create("zip:" + Paths.get("docs.zip").toUri()), env)) {
                      Path path = zfs.getPath("app.jar");
                      if ((Boolean) Files.getAttribute(path, "isZip")) {
                          // 創建一個嵌套的 ZFS。
                          try (FileSystem nestedZfs = zfs.provider().newFileSystem(path, env)) {
                              // 此處省略若干行。
                          }
                      }
                  }
              

          最后雙手奉上源代碼:請猛擊此處!

          posted @ 2011-12-01 13:12 蜀山兆孨龘 閱讀(2428) | 評論 (1)編輯 收藏

          先看出錯的代碼:

                  public class Holder<T> {
                      private T value;
          
                      public Holder() {
                      }
          
                      public Holder(T value) {
                          this.value = value;
                      }
          
                      public void setValue(T value) {
                          this.value = value;
                      }
          
                      // 此處省略若干行。
                  }
          
                  Holder<Object> holder = new Holder<>("xxx");
              

          看起來還好,但編譯的時候卻報錯:

          Uncompilable source code - incompatible types
            required: zhyi.test.Holder<java.lang.Object>
            found:    zhyi.test.Holder<java.lang.String>

          老老實實把類型寫出來就沒問題:

                  Holder<Object> holder = new Holder<Object>("xxx");
              

          如果非要用鉆石運算符的話,可以采取下列兩種方式之一:

                  // 使用默認構造器,再調用 setValue 方法。
                  Holder<Object> holder = new Holder<>();
                  holder.setValue("xxx");
          
                  // 使用泛型通配符,但之后就不能調用 setValue 了,否則編譯出錯。
                  Holder<? extends Object> holder = new Holder<>("xxx");
              

          posted @ 2011-11-11 11:06 蜀山兆孨龘 閱讀(1564) | 評論 (0)編輯 收藏

          CyclicBarrier 的功能類似于前面說到的 CountDownLatch,用于讓多個線程(子任務)互相等待,直到共同到達公共屏障點(common barrier point),在這個點上,所有的子任務都已完成,從而主任務完成。

          該類構造的時候除了必須要指定線程數量,還可以傳入一個 Runnable 對象,它的 run 方法將在到達公共屏障點后執行一次。子線程完成計算后,分別調用 CyclicBarrier#await 方法進入阻塞狀態,直到其他所有子線程都調用了 await

          下面仍然以運動員準備賽跑為例來說明 CyclicBarrier 的用法:

                      final int count = 8;
                      System.out.println("運動員開始就位。");
          
                      final CyclicBarrier cb = new CyclicBarrier(count, new Runnable() {
                          @Override
                          public void run() {
                              System.out.println("比賽開始...");
                          }
                      });
          
                      for (int i = 1; i <= count; i++) {
                          final int number = i;
                          new Thread() {
                              @Override
                              public void run() {
                                  System.out.println(number + " 號運動員到場并開始準備...");
                                  try {
                                      // 準備 2~5 秒鐘。
                                      TimeUnit.SECONDS.sleep(new Random().nextInt(4) + 2);
                                  } catch (InterruptedException ex) {
                                  }
                                  System.out.println(number + " 號運動員就位。");
                                  try {
                                      cb.await();
                                  } catch (InterruptedException | BrokenBarrierException ex) {
                                  }
                              }
                          }.start();
                      }
                      System.out.println("等待所有運動員就位...");
              

          運行輸出(可能)為:

          運動員開始就位。
          1 號運動員到場并開始準備...
          2 號運動員到場并開始準備...
          等待所有運動員就位...
          3 號運動員到場并開始準備...
          4 號運動員到場并開始準備...
          6 號運動員到場并開始準備...
          8 號運動員到場并開始準備...
          5 號運動員到場并開始準備...
          7 號運動員到場并開始準備...
          1 號運動員就位。
          3 號運動員就位。
          8 號運動員就位。
          6 號運動員就位。
          2 號運動員就位。
          7 號運動員就位。
          5 號運動員就位。
          4 號運動員就位。
          比賽開始...

          最后看看 CyclicBarrierCountDownLatch 的主要異同:

          1. 兩者在構造的時候都必須指定線程數量,而且該數量在構造后不可修改。
          2. 前者可以傳入一個 Runnable 對象,在任務完成后自動調用,執行者為某個子線程;后者可在 await 方法后手動執行一段代碼實現相同的功能,但執行者為主線程。
          3. 前者在每個子線程上都進行阻塞,然后一起放行;后者僅在主線程上阻塞一次。
          4. 前者可以重復使用;后者的倒計數器歸零后就作廢了。
          5. 兩者的內部實現完全不同。

          posted @ 2011-10-17 11:21 蜀山兆孨龘 閱讀(1792) | 評論 (0)編輯 收藏

          僅列出標題
          共8頁: 上一頁 1 2 3 4 5 6 7 8 下一頁 
          主站蜘蛛池模板: 原阳县| 邓州市| 罗定市| 运城市| 交口县| 张家口市| 乐至县| 普兰店市| 十堰市| 巩义市| 清远市| 丹寨县| 炉霍县| 内黄县| 衡南县| 韶关市| 沾化县| 桂阳县| 庆安县| 赞皇县| 英山县| 彭阳县| 长汀县| 哈密市| 长治市| 云浮市| 麻栗坡县| 普洱| 四子王旗| 西贡区| 诸城市| 平乡县| 新民市| 乡城县| 清镇市| 武隆县| 玉门市| 高雄县| 金沙县| 冕宁县| 和平县|