JAX-RS 1.1 総復習 (1)

この記事は JavaEE Advent Calendar 2012 の12月7日担当分「JAX-RS 2.0 ことはじめ」の前提知識のまとめとして派生したものです。2013年1月以降、より実践的な内容を含めた続編を執筆予定です。


1. JAX-RSの基本

1.1. JAX-RSとは?

JAX-RSはJavaでRESTful Webサービスを容易に実装するためのAPIです。当初JSR 311で策定され、JAX-RS 1.1がJava EE 6のFull Profileに組み込まれました。JAX-RSはJava EEに組み込まれる以前から先行的に利用されていた完成度の高いAPIであり、もしJAX-RSがRuby on Railsより先に登場していたら今日のようなRubyの発展はあり得なかったと言われるほどのものです。

JAX-RS 1.1以降、このAPIに対して機能追加の要求が多数ありました。リファレンス実装のJerseyをはじめ、RESTEasyやApache CXFなどのJAX-RS実装は独自拡張の形でこれらの要求に応えていましたが、標準規格として取り入れようという取り組みがJSR 339で行われ、その成果がJAX-RS 2.0です。JAX-RS 2.0はJava EE 7 Full ProfileおよびWeb Profileに組み込まれます。

1.2. RESTful Webサービス

JAX-RSを知るためにはまずRESTful Webサービスについて知る必要があります。RESTful Webサービスは、Roy Thomas Fieldingが博士論文「Architectural Styles and the Design of Network-based Software Architectures」 [Fielding, 2000] で提唱した "REST" という概念に基づいて構築されたWebサービスのことを言います。ただし、[Fielding, 2000] において "REST" はまだこなれた表現ではなく、論文からすべてを察するのは難しいと思われます。

"REST" とは、大雑把に言うと以下の3つの要素から構成されるWebアプリケーションのことをいいます。

  • リソース (Resource)
  • URI (Unified Resource Identifier)
  • メソッド (Method)

"REST" ではすべてのものを「リソース」と呼び(例:HTMLファイル)、リソースにはURIという一意の識別子が付けられます。URIは現在ではURLと同一のものと定義されています。URIはUnixのファイルシステムを模していることからも推察できるように、ツリー構造がベースになっています。メソッドはリソースに対する操作のことで、基本的にはHTTPプロトコルで定義されている以下の4メソッドが用いられます。

RESTの基本メソッド
メソッド説明
GET リソースの状態を取得する
POST リソースの状態を変更する
PUT リソースを追加する(既にあれば差し替える)
DELETE リソースを削除する(既になければ何もしない)

このうちGET、PUT、DELETEについては、リソースの状態が変化しない限り同じ操作を何度繰り返しても同じ結果が得られます。これを「べき等性」といいます。POSTはリソースの状態を変えるためべき等性は成り立ちません。StrutsやJavaServer Facesでブラウザの戻るボタンを使用すると画面遷移が乱れるのは、これらのフレームワークは基本的にPOSTのみで画面遷移を実現しているため、戻るボタンでPOSTが再発行されると異なる結果になってしまう(POSTはべき等性が成り立たない)ためです。

POSTとPUTは似たような役目を持っているため使い分けが必要です。現実にはPOSTとPUTの使い分けはあまり厳密に行われていませんが(例えばGlassFishのREST管理チャネルではリソースの更新はすべてPOSTを使用します)、だいたい以下の基準に従います。

  • POST:リソースの状態を更新する、または、子リソースを作成しURIを自動的に付与する場合
  • PUT:具体的なURIを指定してリソースを追加する(もしリソースが存在すれば新しいものと差し替える)場合

さて、RESTfulなものとRESTfulでないものはどこが違うのかというと、そこは実例を挙げた方が分かりいいかと思います。

まずは。RESTfulな例:

  • 静的なWebサイト
  • ブログ (Movable Type, WordPress, etc.)
  • Google, Amazon, Twitter, Facebokなどの公開API

これらの特徴としてRESTの3原則、特にメソッドの使い分けを明確に行っていることが挙げられます。RESTの原則から導き出されるアーキテクチャは "Stateless" であるため、プロトコル全体が完結になるというメリットもあります。

反対に、RESTfulでない例:

  • 古いCGIアプリケーション
  • SOAPベースのWebサービス
  • Apache Struts
  • JavaServer Faces

これらは何でもかんでもPOSTで処理しており、Statelessなアーキテクチャ、すなわちWeb本来の姿をねじ曲げています。CGIは登場時期のHTTPサーバの技術的な制限により、多くのデータを処理できるPOSTに依存せざるを得なかったことは認めましょう。しかし、Strutsがすべての処理をPOSTで行うアーキテクチャを採用したのは、Web本来の姿を無視した暴挙であると感じています(※この問題においてはStrutsはA級戦犯と言っても過言ではないでしょう)。そしてStrutsの事実上の後継規格であるJavaServer Facesも基本的にはPOST中心のアーキテクチャを堅持しており、JSF 2.xになって一部GETに対応するという抜け道を見つけたに過ぎません。

1.3. リクエストとレスポンス

RESTful Webサービスでは、HTTPプロトコルに備わったリクエストとレスポンスの仕組みをフル活用します。送信データ内に様々なヘッダを内包しPOSTでのみやりとりするSOAP Webサービスとは対照的です。

リクエストは対象となるURIとメソッドを指定し、内容はヘッダー、パラメーター、ボディーからなります。ただしパラメーターとボディーはメソッドによりどちらか一方しか使用できないことがあります。ヘッダーは定義済みのものと、"X-" で始まるユーザー定義のものがあります。

リクエストヘッダーの例
ヘッダー説明
Host リクエスト送信先のホスト名(唯一の必須ヘッダー) Host: www.coppermine.jp
Accept 受け付けるレスポンスボディのMIMEタイプ、リソースが複数のMIMEタイプを持つ場合にAcceptヘッダーで指定したタイプのレスポンスが返される Accept: application/xml
Content-Type リクエストボディのMIMEタイプ Content-Type: application/x-www-form-urlencoded
Date 日時 Date: Fri, 7 Dec 2012 09:30:45 GMT

パラメーターは、GET、PUT、DELETEメソッドではリソースURIの後ろに "?" から始まるクエリー文字列として設定されます。パラメーターはキーと値のペアの集合からなり、キーと値を "=" でつなぎ、パラメーター同士は "&" で区切ります。パラメーターには順序は関係ありませんが、パラメーターを含むURIはサーバーやクライアントの仕様により制限される場合があります(最近は事実上無視できるくらいに緩和されています)。なお、URIはASCII文字以外をエスケープエンコードする必要があります(URIをUTF-8で指定できる場合は多くの文字がエスケープ不要となります)。POSTメソッドの場合はパラメーターをボディーに設定しFORMエンコードを行います。そのため、POSTのパラメーターはURIに現れないのです(パラメーターが多いことやセキュリティ上の考慮を理由にGETメソッドの代替としてPOSTメソッドを使用することは "REST" の観点からは忌避すべき事です)。

リクエストボディーはMIMEエンコードされたあらゆるデータを含めることができます。ただしGET、DELETEメソッドはボディーを含めることができません。

レスポンスは、サーバーがリクエストに応じて返すデータのことで、結果の正常or異常を表すステータスと、ヘッダー、ボディーからなります。このうちヘッダーとボディーはリクエストとの共通点が多くあります。

レスポンスのステータスは3桁のステータス番号と説明文で表現されますが、説明文はあくまで補足程度で、基本的には3桁のステータス番号で判定します。

ステータス番号体系
ステータス番号意味
1xx 情報(最近はあまり見かけない)
2xx 成功
3xx リダイレクトなどの指示
4xx エラー、原因がクライアントにある場合
5xx エラー、原因がサーバにある場合

1.4. JAX-RSの仕組み

JAX-RSはリクエストに対するリソースの振る舞いをPOJOのクラスで定義するためのフレームワークです。URIの解析、メソッドごとの処理の振り分け、パラメーターの取得とデコード、リクエストボディーのデコード(Message Body Reader)とレスポンスボディーのエンコード(Message Body Writer)をすべてJAX-RSが行ってくれます。同様の処理をサーブレットで実装する場合、これらのほとんどをコーディングしなければならないことを考えると、いかにJAX-RSが優れているかよく分かると思います。

jax-rs.png

数あるJAX-RSの機能の中でも圧巻なのはやはりボディーの取り扱いでしょう。これはMessage Body Reader/Writerと呼ばれているもので、HTTPのリクエスト/レスポンスボディーとJavaのデータ型を直接マッピングする機能を有しています。

1.5. 簡単な例

簡単な例として、URIが /hello のリソースを挙げます。このリソースはGETメソッドに応答し、MIMEタイプがtext/plainのメッセージを返します。JAX-RSにおける /hello リソースに対応するクラスは、以下のように記述できます。

package jp.coppermine.samples.jaxrs;

import javax.ws.rs.*;

@Path("/hello")
public class HelloResource {
  @GET
  @Consumes("application/x-www-form-urlencoded")
  @Produces("text/plain")
  public String sayHelloTo(@DefaultValue("world") @QueryParam("q") String name) {
    return "Hello, " + name;
  }
}

JAX-RSではこのようにリソースの振る舞いを記述するクラスをリソースクラスと呼びます。リソースクラスは、クラスレベルアノテーション @Path によって識別されます。@Path は必須のデフォルト引数を持ち、リソースクラスに対応するURIを指定します。この例ではリソースクラス HelloResource がリソース /hello に対応するため、@Path("/hello") を付加しています。

リソースクラスはリクエストに応じるメソッドを持ちます。リクエストの振り分けはメソッドに付加されたアノテーションによって決定します。

もっとも基本的な振り分けルールはHTTPのメソッド GET, POST, PUT, DELETE, etc. に対応したアノテーション @GET, @POST, @PUT, @DELETE, etc. です。この例では sayHello メソッドがGETメソッドに応答するよう @GET を付加しています。

同一のHTTPメソッドでも、リクエストによってContent-Typeが異なる場合があります。またリソースによっては複数の形式のレスポンスに対応するものもあります(例:HTML, XML or JSON)。受け付けるリクエストのContent-Typeを特定するのが @Consumes アノテーション、レスポンス形式を指定するのが @Produces アノテーションです。この例ではそれぞれ @Consumes("application/x-www-urlencoded"), @Produces("text/plain") となっており、受け付けるリクエストのContent-TypeがWebフォームの標準である application/x-www-urlencoded、レスポンスの形式が text/plain であることを意味しています。なお、@Consumes と @Produces の引数は配列になっており、複数の値を指定することができます。

JAX-RSでは、リソースクラスのメソッド引数がリクエストのパラメーター(ヘッダーを含む)またはボディー、戻り値がレスポンス(ボディーおよびヘッダー)となります。主要なデータ型については文字列表現との相互変換が自動的に行われます。またJAXBを用いたXML/JSONとPOJOクラスの相互変換も同様にサポートされています(JSONに対応するためJAXB拡張が行われています)。

マッピングについては、以下のサンプルコードをご覧下さい。

beginning-jaxrs_03.png

beginning-jaxrs_04.png

1.6. リクエスト/レスポンスのマッピング

リクエストのパラメーターは、種類とパラメーター名によりメソッド引数とダイレクトに対応します。マッピングは以下のアノテーションで行われます。

リクエストパラメーターの取得規則
アノテーション説明
@QueryParam クエリー・パラメーターの値
@FormParam フォーム・パラメーターの値
@PathParam URIの一部
@MatrixParam Matrix URIの属性部分
@HeaderParam リクエストヘッダーの値
@CookieParam クッキーの値

上記のアノテーションが付加されていない引数はリクエストのボディーを受け取ります。この場合、後述のJavaデータ型とMIMEタイプのマッピングを考慮しなければなりません。

パラメータには@DefaultValueアノテーションを付加して、省略時のデフォルト値を設定することができます。例えば、上記の例では、

@DefaultValue("world") @QueryParam("q") String name

となっており、クエリーパラメーター q が省略された場合には "world" が設定されたものとして扱います。なお、引数の型がString以外(例えばintやlong)の場合でも、@DefaultValueに設定するデフォルト値はStringになります。これはパラメーターを型変換する前にまずパラメーター自体の存在をチェックし、存在しない場合には可能であれば@DefaultValueから値を取得する仕組みとなっているためで、そのため型変換前の形式=文字列を指定しなければならないのです。最初は感覚的ではないかも知れませんが、リクエストの解析過程が分かってくるうちに違和感はなくなります。

ここで@PathParamと@MatrixParamについて補足します。

@Pathで指定できるURIには { } で囲んだプレースホルダーを指定できます。例えば @Path("/hello/{name}") のように指定することができ、この時の {name} の値を取得するために @PathParam("name") というアノテーションを付加します。JAX-RSではURIのプレースホルダーの他、正規表現を用いてURIを指定することも可能です。

beginning-jaxrs_06.png

URIにはMatrix URIという拡張形式があり、クエリー文字列ではなくURI本体にパラメーターに相当するものを埋め込んでしまうというものです。具体的には以下のような形式となります。

/position;latitude=35.75;longitude=139.72

キーと値のペアをセミコロンで区切っているのが特徴です。パラメーターとしての使用を意図したものではありますが、あくまでURIの一部であることに注意して下さい。@MatrixParamはこのMatrix URIから値を取得しようというものです。一時期Matrix URIを採用しているWebサービスを見かけたのですが、最近はまたクエリー文字列に回帰しているケースも多いようです。

beginning-jaxrs_07.png

リクエストおよびレスポンスは、MIMEタイプレベルでJavaのデータ型とのマッピングが決まっています。以下にJAX-RSの仕様書をもとに作成したマッピング表を示します。この表の範囲外でも、MessageBodyReader/Writerを独自に実装することでマッピング規則をカスタマイズすることもできます。

Javaのデータ型とMIMEタイプのマッピング表
Javaのデータ型MIMEタイプ
byte[] 任意のMIMEタイプ (*/*)
String
InputStream
Reader
File
javax.activation.DataSource
javax.xml.transform.Source text/xml
application/xml
application/*+xml
JavaBean
/(JAXBクラス)
text/xml
application/xml
application/*+xml
application/json (Optional)
MultivaluedMap<String, String> application/x-www-form-urlencoded
OutputStream 任意のMIMEタイプ (*/*)
(MessageBodyWriterのみ)
Boolean text/plain
Character
Number

標準ではサポートされないJavaデータ型(例えばJSR 310のOffsetDateTimeなど)をマッピングしたい場合は、XML/JSONの使用が前提であれば、JAXBのカスタムマーシャラーを実装するだけで済みます。MessageBodyReader/Writerの独自実装は結構骨の折れる作業なのです。

もっとも、筆者はリソースクラスを設計する際に前掲の表を参照することはなく、より簡易なマッピング表を「暗記」して使っています。

リクエストとJavaデータ型のマッピング
リクエストのMIMEタイプJavaのデータ型
text/plain String, boolean, int, long, double
text/html String
application/octet-stream InputStream, byte[]
application/xml, application/json JAXBクラス

レスポンスとJavaデータ型のマッピング
レスポンスのMIMEタイプJavaのデータ型
text/plain, text/html String
application/octet-stream OutputStream, byte[]
application/xml, application/json JAXBクラス

Webサービスの場合、大半がXMLまたはJSONの入出力になるので、重点を置くべきはJAXBによるXML/JSONのマッピング方法でしょう。また、簡単なリクエスト/レスポンスのためtext/plainのマッピング方法を知っておく必要があります。あとはapplication/octet-streamがbyte配列やStreamとマッピングできることを知っておけば、大抵のケースで通用します。ボディー以外はtext/plainと同等のマッピング規則が適用されると思って間違いはありません。

2. JAX-RSのデプロイ

2.1. JAX-RSのデプロイについて

JAX-RSで実装したRESTful WebサービスはWebアプリケーション(WAR)としてサーバーにデプロイします。これはJAX-RSがサーブレット技術の上に成り立っているためです。事実、Jersey、RESTEasy、Apache CXF、Apache Winkといった主要なJAX-RS実装はすべてサーブレットを拡張して実装しています。

WAR内でJAX-RSを有効にする方法としては、大きく以下の2種類があります(これらの派生型を含めると相当数になります)。

  1. Applicationのサブクラスを作成する。
  2. 実装固有のサーブレットをweb.xmlに登録する。

Java EE 6 Full Profile準拠のサーバーは上記いずれかの方法でデプロイ可能です。そうでないサーバーでJAX-RSを使用する場合は追加の手順が必要になるかも知れません。ただしJava EE 6認定サーバーであってもuCosminexus Application Serverのみなぜか例外で、ベンダー独自の方法に従います。

uCosminexusはデプロイ以外についても、他のサーバーのJAX-RS実装が持っている機能、例えばハンドリングするメソッドの拡張(これによりJAX-RSでWebDAVサーバーを実現することも可能)が使用できないなど、基礎レベルでの実装がなされていない点が多く見受けられます。ベンダーの言い分としてはTCKで必須とされていなかったから実装しなかったとのことですが、余り感心できる実装とは言えません(これらのことから、uCosminexusのJAX-RS実装はベンダーが全くの独自に実装したものであると考えられます)。

2.2. Applicationのサブクラスを作成する方法

Applicationのサブクラスを作成し、@ApplicationPathでWebサービスの起点となるURIを指定することで、web.xmlなしでJAX-RSを有効にすることができます。この方法は、uCosminexusを除くすべてのJava EE 6 Full ProfileサーバーとGlassFish 3.x Web Profileに共通のものです。

@ApplicationPath("/app")
public MyApplication extends Application {
  // 通常は空の実装でよい
}

このようなクラスを作成することによりJAX-RSが自動的に登録され、例えば前出の hello リソースには以下のURLでアクセスできるようになります。

http://hostname/context-root/app/hello

2.3. 実装固有のサーブレットをweb.xmlに登録する方法

JAX-RSはサーブレットとして実装されているため、本体のサーブレットをweb.xmlに登録することでJAX-RSを有効にすることもできます。JerseyやRESTEasyのように多数の独自機能をオプションとして持っている実装では、独自機能がサーブレットフィルターやサーブレットリスナーとして実装されているため、それらを使用する場合には必然的にこの方法を採用することになります。当然のことながら、JAX-RS実装が異なるサーバー間での互換性は損なわれます(サーバーごとのJAX-RS実装については2.5節を参照して下さい)。

以下はJerseyにおける設定例で、2.2節の例と同じ結果が得られます。他の実装もスーパークラスが異なることを除けば概ね同様です。

<servlet>
  <servlet-name>Jersey</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>Jersey</serlet-name>
  <url-pattern>/app/*</url-pattern>
</servlet-mapping>

2.2節の@ApplicationPathと異なり、サーブレットのURIマッピングのため、 /app でなくワイルドカードを含めた /app/* を指定することに注意して下さい。

2.4. どうしてもweb.xmlを書きたくない場合には?

2.3節の方法ではweb.xmlが必須です。しかしServlet 3.0以降、web.xmlが省略できるようになったのにどこか退行しているような気がしませんか?この問題は、JAX-RS実装クラスのサブクラスを作成し@WebServletアノテーション(必要に応じて@ServletFilter、@WebServletContextListener、@InitParamなども使用可能)を付加します。以下の例は、2.3節と同等です。

@WebServlet("/app/*")
public class MyServletContainer extends ServletContainer {
  // 必ず空の実装
}

これは過去記事「web.xmlを書き換えずにサーブレットを登録する」および「web.xmlなしでJSF・JAX-WS・JAX-RSを登録する方法」で紹介した方法と同じものです。

2.5. サーバーごとのJAX-RS実装のまとめ

サーバー実装備考
GlassFish Server 3.x Jersey Web Profileにも含まれる(*1)
Oracle WebLogic Server 12c Jersey  
IBM WebSphere AS V8.x Apache Wink  
IBM WAS CE 3.0 Apache Wink  
JBoss AS 7.1.x RESTEasy  
JBoss EAP 6 RESTEasy JBoss ASの商用版 (*2) 
Apache Geronimo 3.0 Apache Wink  
Apache TomEE+ 1.x Apache CXF Java EE 6未認定
Fujitsu Interstage AS V10 Jersey GlassFishベース (*3)
Hitachi uCosminexus AS V9 不明 独自実装と推察
Tmax JEUS 7 不明

(*1) GlassFishは管理ツールのコア部分をJAX-RSで実装しているため、Web ProfileであってもJAX-RSを除外することができないことによる。

(*2) JBoss EAPはJBoss ASとは別にJava EE 6の認定を受けているため。JBoss EAPは商用版GlassFishの場合と異なり、 フリー版のJBoss ASへの不フィードバックを元に改修を行っているためコードベースがいくらか異なる。

(*3) GlassFish標準機能の一部をベンダー独自のものに差し替えている。

3. JAX-RSとInjection

3.1. サーブレットリソースのInjection

JAX-RSでは、@Contextアノテーションを使用したサーブレットリソースのInjectionが可能となっています。具体的には以下の4種類をリソースクラスにInjectできます(追加の設定は必要ありません)。

  • ServletConfig
  • ServletContext
  • HttpServletRequest
  • HttpServletResponse

第1章の範囲だけでもほとんどのRESTful Webサービスは実装可能です。ただし、例えばWebサービスの完全なURLを取得したい場合など、ServletContextなどの情報にアクセスする必要があります。@ContextによるInjectionは、そうした低水準の情報を扱わざるを得ない状況に対処できるように準備されているものです。

例えば、リソースクラスで次のように宣言することで、各サーブレットリソースがInjectされます。

@Path("/sample")
public class SampleResource {
  @Context
  private ServletConfig config;
  
  @Context
  private ServletContext context;
  
  @Context
  private HttpServletRequest request;
  
  @Context
  private HttpServletResponse response;
  // 以下省略
}

JAX-RSはRESTful Webサービスのためのフレームワークであり、すなわちステートレスなものです。しかし、サーブレットリソースをInjectすることでHttpSessionへのアクセスが可能となり、ステートフルな実装を行うこともできるのです(HttpSession以外でもCookieを上手く使うことで似たようなことができます)。JAX-RSでHttpSessionを使用することは、本来ステートレスなプロトコルであるHTTPとそれをベースにしたRESTの概念から見れば忌避されるべきものです。しかし、裏技としてこのような手段も存在することは頭の片隅にあってもよいかも知れません。

JerseyにはさらにJAX-RSでMVCモデルをサポートするという拡張機能があります。これは次期仕様JAX-RS 2.0の追加機能候補で唯一採用されなかったものです。加藤田さん(@den2sn)のブログ記事「Javaを知らない世代が今からはじめるJava EE開発」で紹介されている、Viewableを使ってJSPをレスポンスとして返すやり方は、まさにJerseyのMVCサポートを使用しています。実装に依存しますが、JAX-RSでサーバーサイドページのWebアプリケーションを作成することも不可能ではありません。

このあたりの実例は(あまり勧められるものではありませんが)続編で紹介していきたいと考えています。

3.2. EJBのInjection

サーブレットやJSPは@EJBアノテーションによってEJB(Session Bean)をInjectすることができます。しかし、JAX-RSは@EJBによるInjectionをサポートしていません。

ただし全く方法がないかというと、そういうわけでもありません。JAX-RS 1.1以降(Java EE 6に含まれるもの)ではリソースクラスをEJBとして実装することが可能になっています。いわゆるJAX-RSとEJBの連携機能です。リソースクラスに@Statelessなどのアノテーションを付加することで、@EJBによるInjectionが可能になります。

なお、JAX-RSとEJBの連携はJAX-RS 1.0以前ではサポートされませんが、InjectableProviderというクラスをクラスパス上に含めることで可能になります。現状では陳腐化した内容ですが、もし興味のある方は詳細は過去のブログ記事「JAX-RSにEJBをinjectする際の注意点」を参照して下さい。

3.3. CDIとJAX-RS

CDIが有効(WEB-INF/beans.xmlが存在)であれば、JAX-RSのリソースクラスをEJBやManaged Bean(@javax.annotation.ManagedBeanアノテーションを付加したもの、@javax.faces.bean.ManagedBeanではないので注意)で実装することで、@InjectアノテーションによるInjectionが可能になります。

基本的な使い方こそ3.2節の@EJBと同様ですが、CDIがもたらす様々なメリットを享受できるようになります。

ただし、Jersey 1.1.5から1.1.11までのバージョンにはCDIとの連携に不具合があり、正しくInjectされない場合が多々あります。この件については過去のブログ記事「JAX-RSにEJBをinjectする際の注意点 その2」にまとめていますので、GlassFishおよびWebLogicのユーザーは一度は目を通して下さい。参考まで、現段階におけるGlassFishの最新版v3.1.2.2ではJersey 1.1.11を同梱しているため、当該事象が発生する可能性があります。


今回はJAX-RSの仕組みについて重点を置いたため、具体的なコードを用いて説明することはしませんでした。次回以降、サンプルコードを使ってJAX-RSを理論と実践の両面から見直してゆきたいと思います。