神奇好望角 The Magical Cape of Good Hope

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

          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)明起見(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)置了很多格式,詳見(jiàn) 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 是可以的。


          評(píng)論

          # re: JAX-RS 從傻逼到牛叉 2:開發(fā)一個(gè)簡(jiǎn)單的服務(wù)[未登錄](méi)  回復(fù)  更多評(píng)論   

          2013-06-13 18:29 by ddd
          ffffff

          # re: JAX-RS 從傻逼到牛叉 2:開發(fā)一個(gè)簡(jiǎn)單的服務(wù)  回復(fù)  更多評(píng)論   

          2013-11-12 09:41 by 飛過(guò)天涯海角
          lz,我昨天跑了一下你的代碼,在jax-rs中@Singleton是沒(méi)用的,每次訪問(wèn)movie的時(shí)候,都會(huì)新建一個(gè)movieService對(duì)象,movieMap也會(huì)被重新初始化,沒(méi)法作為內(nèi)存數(shù)據(jù)庫(kù)來(lái)使用。

          # re: JAX-RS 從傻逼到牛叉 2:開發(fā)一個(gè)簡(jiǎn)單的服務(wù)  回復(fù)  更多評(píng)論   

          2014-01-07 11:49 by zihang_Wu
          想問(wèn)一下樓主,我的webservice還有一層接口,注解該加在哪里比較好啊
          主站蜘蛛池模板: 长宁区| 东阳市| 海盐县| 澎湖县| 永宁县| 利辛县| 易门县| 东阳市| 临夏县| 皋兰县| 呼图壁县| 上杭县| 襄城县| 达日县| 无极县| 清丰县| 蒲江县| 邵阳县| 郎溪县| 陵川县| 九寨沟县| 宜州市| 兴宁市| 获嘉县| 类乌齐县| 遵化市| 郸城县| 珲春市| 温州市| 会泽县| 临海市| 沅陵县| 阿图什市| 凯里市| 东丰县| 左贡县| 庆元县| 宁化县| 长沙县| 墨脱县| 壤塘县|