20
第 章
RESTfulウェブサービス
RESTfulウェブサービス(Jakarta Restful Web Services)は、RESTという設計原則 に基づくウェブサービスです。RESTはクラウド時代のソフトエア設計法として登場 しましたが、近年、特に注目を浴びています。大きな原因はインターネットの普及と コンテナ技術や大規模な運用管理技術の発達があげられます。それらにより、独立し た小さなサービスを組み合わせて、大規模システムを構成しようとするマイクロサー ビスに現実味が増してきました。RESTfulウェブサービスの仕組みを学ぶことは、マ イクロサービスを学ぶ初めの一歩です。この章を学習してクラウド時代の新しいJava を発見してください。
1 ...
20
一般に、RESTfulウェブサービスとはRESTという構成方式を使って実装された、ネッ トワークアプリケーションを指します。では"REST"とは何でしょうか。
1.1 RESTとは
簡単に言うと、RESTは、サーバー/クライアント間のコミュニケーション方式のひとつ です。同じようなものは他にもありますすが、JSFのように大がかりではなく、軽量で実 装も簡単ということから、クラウドネイティブな時代にマッチした方式として大きな注目 を集めています。
RESTでは、サーバーが何かのリソース(=オブジェクトと考えてよい)を持っている時、
クライアントコンピュータからそれを操作できます。具体的には、HTTP通信のリクエス トタイプを表すGET、POST、PUT、DELETEなどを動詞(命令語)として使います。
そのため、特定のサーバーに対するリクエストはURI※として作成し、処理に必要なパラ メータ・データなども一緒に送受信します。命令にはHTTP通信の用語しか使わないので、
プログラム言語はJava言語に限定されません。特に、クライアント側ではJavascriptがよ く使われています。
リクエストを受信したサーバーはレスポンスを返します。レスポンスはあらゆるものが 可能です。テキストやバイナリのデータ、XML、HTML、オブジェクトやそのコレクショ ンなど、なんでも可能です。RESTの定義にはありませんが、Javascriptに対応しやすいよ うに、データをJSON形式に変換してやり取りする機会が多くなっています。
なお、通信では、セッションを記憶することはありません。1回単位のステートレスな 通信という特徴があります。
1.2 RESTfulウェブサービスの構成方法
RESTfulウェブサービスのサーバーへのリクエストは、次のようなURI(Uniform Resource Identifier)として作成します。
Section Section Section Section Section S Sectition Se Sectctioionn Section S i
1
S tiRESTfulウェブサービスの概要
20
これは、「
id
が 1 番の会員のデータを送れ」というGETリクエストです。詳細は後で説 明しますが、/sample27-01/は、作成したプロジェクト名です。そして、次の"bookshop"の 部分が作成したウェブサービスの名前です。"bookshop"の部分を、アプリケーション・パスといい、ウェブサービスの名前として、
設定しておく必要があります。
RESTアプリケーションパスの設定 sample27-01/bookshop/ServiceConfig.java 例題1
1 import javax.ws.rs.ApplicationPath;
2 import javax.ws.rs.core.Application;
3
4 @ApplicationPath("bookshop")
5 public class ServiceConfig extends Application {
6 }
Application
クラスを継承したクラスを作成して、@ApplicationPath
アノテーショ ンで指定します。引数にサービス名("bookshop
")を指定します。このクラスはどのパッケー ジにあってもよく、クラスの中身は空で構いません。クラス名も自由です。アプリケーションパスを指定したら、別のクラスを作って、具体的な処理を作成します。
次は、GETの処理を書いたサーバーサイドのプログラムです。
例題1(続き)ウェブサービスの作成 sample27-01/bookshop/MemberResource.java 1 @Path("member") // リソース名
2 public class MemberResource {
3 private Member member
4 = new Member(1L, "田中", "[email protected]","140-0014", "東京都", "品川区");
5 @GET
6 public String getMember(){
7 return member.toString();
8 }
9 }
ウェブサービスでは、サービスをリソースごとに 作成します。そこで、最初にリソース名を宣言し ます。それを行うのがクラスに付けた
@Path
アノ テーションです。引数にリソース名を指定します(右 図を参照)。このクラスは、bookshopサービスのう ち、会員リソースに関するサービスを提供するの で、"member
"というリソース名です。"member
"をを
次のように
改行
20
さて、例題の
getMember()
メソッドは、@GET
が付けられています。これにより、GET
アクセスに応答するメソッドになります。
@GET
以外にも、HTTPリクエストの種類に応じて、次のようなタイプがありますが、ど れを使うかは、HTTPでの意味に準じて決めます。アノテーション HTTP 意 味
@GET GET リソースを取得する
@POST POST リソースを新規登録する
@PUT PUT リソースを更新する
@DELETE DELETE リソースを削除する
※これ以外に@HEAD、@OPTIONS、@PATCHがあります
getMember()
メソッドは、会員オブジェクトのインスタンスを文字列表現にして返す だけです。本来ならば、パラメータとして会員ID
を受け取り、データベースを検索した結 果を返すところです。ここでは構成方法を示すだけなので簡単にしています。
Member
クラスはプロジェクトのentity
パッケージに入っています。次のようなクラス でtoString
メソッドが定義されています。例題1(続き)会員エンティティ sample27-01/entity/Member.java 1 @Entity
2 public class Member implements Serializable {
3 private static final long serialVersionUID = 1L;
4 @Id
5 private Long id;
6 private String name; // 氏名 7 private String mail; // メール 8 private String zip; // 郵便番号 9 private String pref; // 県 10 private String city; // 市区町村 11 // 以下省略
12 }
1.3 クライアントプログラムで実行する
ウェブサービスができたので、次はクライアント側から実行してみましょう。クライア も
20
本章では、これらのクライアントを順番に使ってみますが、最初は、ブラウザを使ってサー バーにGETアクセスしてみましょう。。NetBeansでsample27-01プロジェクトを起動した後、
次のURIを開きます。
http://localhost:8080/sample27-01/bookshop/member
※payara serverはHTTP通信にポート8080を使うので、ポート指定が必要です
実行すると、次のように、
member.toString()
が出力した文字列が表示されます。トルツメ
20
2.1 サブURI
リソースについての処理を、細かく分けたい場合は、サブURIを指定できます。サブ URIはメソッドに
@Path
アノテーションを付けて指定します。サブURIの指定 sample27-02/bookshop/MemberResource.java 例題2
1 @Path("member")
2 public class MemberResource {
3 private Member member
4 = new Member(1L, "田中", "[email protected]","140-0014", "東京都", "品川区");
5 @GET
6 @Path("/address")
7 public String getMemberAddress() {
8 return member.getZip()+member.getPref()+member.getCity();
9 }
10 @GET
11 public String getMember() {
12 return member.toString();
13 }
14 }
例題は、
getMemberAddress()
メソッドに@Path("/address")
を指定しています。このメソッドをGETアクセスするURIは、リソース名に
/address
を連結したものになり ます。http://localhost:8080/sample27-02/bookshop/member/address
ブラウザからGETアクセスすると次のような応答が返されます。
Section Section Section Section Section S Sectition Se Sectctioionn Section S i
2
S tiサブURIの指定とPathパラメータ
住所情報を処理するサブURIの指定
20
2.2 Pathパラメータ
URIの中に、何かの値をパラメータとして埋め込むことができます。例えば、会員番号 をPathパラメータとして埋め込むには、URIを次のように指定します。
http://localhost:8080/sample27-02/bookshop/member/1
末尾の "1" がPathパラメータで、会員番号に1を指定しています。
パラメータを受け取るには、サーバーサイドは、次のように書く必要があります。
@Path("member")
public class MemberResource @GET
@Path("{id}")
public String getMember(@PathParam("id") Long id) { ・・・
getMember()
メソッドに@Path("{id}")
を付けたので、URIは次のようになりますが、{"id"}
は、idという名前のパラメータを意味します。http://localhost:8080/smaple27-01/bookshop/member/{"id"}
したがって、
{"id"}
の部分には、1とか2のようなパラメータの値を指定できます。指定されたパラメータは、
getMember()
メソッドの引数に代入されます。そこで、値 を受け取る引数には、@PahtParam("id")
というアノテーションを付けておきます。public String getMember(@PathPram("id") Long id){
このようにすると引数
id
に、URIで指定された値を受け取ることができます。次は、このPathパラメータ指定を適用した例題です。
トルツメ
トルツメ
20
【注意】これ以降の例題ではデータベースを使います。
例題を実行する前に、sample27-dbプロジェクトを実行して、データ ベースを作成しておいてください。
実行すると図のような表示がでるので、[実行]ボタンを押すだけです。
これにより、5件のメンバーを登録したmemberテーブルができます。
Bb.javaを見てデータ内容を確認してください。
Pathパラメータの受取り sample27-02B/bookshop/MemberResource.java 例題3
1 @Stateless
2 @Path("member")
3 public class MemberResource {
4 @PersistenceContext
5 EntityManager em;
6 @GET
7 @Path("{id}")
8 public String getMember(@PathParam("id") Long id) {
9 Member member = em.find(Member.class, id);
10 return member.toString();
11 }
12 }
例題は、データベースを使うので、
@Stateless
を付けてステートレス・セッションビー ンにしています※。会員番号を引数id
に受け取っているので、それを使ってデータベース を検索します。最後に、取得した
Member
エンティティの文字列表現を返すことは、これまでの例題と 同じです。sample27-02Bを実行し、ブラウザでURIにアクセスすると次のようになります。idとして2を指定
同一サイズで、
8430.tif に差替え↑8430なし
ステートレスセッションビーンにする
データベースを検索
送ります
20
もう1つのパラメータの指定方法として、Queryパラメータがあります。普通のウェブで もGETアクセスのパラメータ指定に使う形式です。例えば、次のようなURIです。
http://localhost:8080/sample27-03/bookshop/member?id="2"
これは
id
という名前で値が2であるパラメータを指定しています。Queryパラメータで は、これ以外にも、複数のパラメータを指定したり、デフォルト値を決めておいたりでき るので便利です。3.1 Queryパラメータ
次は、例題2を、Queryパラメータに変更したものです。idをパラメータとして受け取り、
Member
エンティティを検索して返します。Queryパラメータの受け取り sample27-03/bookshop/MemberResource.java 例題4
1 @Stateless
2 @Path("member")
3 public class MemberResource {
4 @PersistenceContext
5 EntityManager em;
6 @GET
7 public String getMember(@QueryParam("id") Long id) {
8 Member member = em.find(Member.class, id);
9 return member.toString();
10 }
11 }
Queryパラメータでは、メソッドの引数に
@QueryParam("id")
を指定して、パラメータ を受け取ります。単にパラメータ名を指定するだけので、Pathパラメータよりもシンプルです。検索処理は例題2と同じなので解説は省略します。クラスに
@Stateless
を指定してい ることに注意してください。例題を実行してブラウザでアクセスした結果を示します。Section Section Section Section Section S Sectition Se Sectctioionn Section
S i
3
S tiQueryパラメータ
3
3
20
http://localhost:8080/sample27-03/bookshop/member?id="5"
3.2 Querayパラメータのデフォルト値
Queryパラメータでは、何も値を指定しなかった場合のデフォルト値を決めておくこと ができます。
デフォルト値の指定 sample27-03B/bookshop/MemberResource.java 例題5
1 @Stateless
2 @Path("member")
3 public class MemberResource {
4 @PersistenceContext
5 EntityManager em;
6 @GET
7 public String getMember(@DefaultValue("1") @QueryParam("id") Long id) {
8 String jpql = "select e from Member e where e.id=?1";
9 Member member = em.find(Member.class, id);
10 return member.toString();
11 }
12 }
@DefaultValue()
アノテーションをメソッドの引数に付けて、()内にデフォルト値を 指定します。Long
などの数値でも、文字列の形式で指定することに注意してください。例題を実行し、パラメータを指定しないURIでアクセスすると次のように、デフォルト 値(=1)が有効になっていることがわかります。
http://localhost:8080/sample27-03/bookshop/member
20
3.3 複数のQueryパラメータ
Queryパラメータはいくつでも指定できます。次は2つのパラメータを指定する例です。
複数のQueryパラメータ sample27-03C/bookshop/MemberResource.java 例題6
1 @Stateless
2 @Path("member")
3 public class MemberResource {
4 @PersistenceContext
5 EntityManager em;
6 @GET
7 @Path("/address")
8 public List<Member> memberByAddress(@QueryParam("pref") String pref,
9 @QueryParam("city") String city) {
10 String jpql = "select e from Member e where e.pref=?1 and e.city=?2";
11 List<Member> members = em.createQuery(jpql, Member.class)
12 .setParameter(1,pref)
13 .setParameter(2,city)
14 .getResultList();
15 return members;
16 }
17 }
パラメータがたくさんある時は、その数だけ
@QueryParam
アノテーションを書きます。例題はJPQLを使って、
pref
(都府県)とcity
(市区)という2つのパラメータを受け取っ て、その条件で会員を検索します。なお、会員の住所情報を扱うので、サブURIとして"
/address
"を指定しています。例えば、東京都で品川区に住んでいる会員を求めるURIは次のようになります。
http://localhost:8080/sample27-03C/bookshop/member/address?pref=東京都&city=品川区
文字列を引用符で囲わないことに注意してください。また、2つ目以降のパラメータは&
を使って連結します。
例題を実行し、ブラウザで上記のURIをアクセスした場合の結果を示します。
Listをそのまま返す
20
検索結果は
Member
のList
で、例題はList
をそのまま返したため、このような表示に なっています。これはJSON形式ですが、ブラウザの表示では見にくくなります。そこで、もう少し便利なRESTクライアントを使うことにしましょう。
3.4 通信テストプログアラムの利用
通信テストプログアラムをインストールすると、GETの結果を見やすくできるだけでな く、POSTやPUT、DELETEなどのURIも実行できます。また、実行時に、HTTPヘッダー を編集してサーバーに適切な指示を出せるようになります。
インストール
ここではPostmanという通信テストプログアラムをインス トールします。次のURLからダウンロード・インストールし てください。
https://www.postman.com/downloads/
ウェブバージョンではなく、「Download the app」をクリック します。ダウンロードファイルをダブルクリックしてインス トールし、IDやパスワードを登録しますが、画面の指示に従っ て、インストールを終了してください。
MacもWindowsと基本的に同じ手順です。
起動
インストールして起動すると"Launchpad"タブが選択された状態の画面が開きます。
この画面で、「create a request」を選んでください。
これをクリックする
トルツメ
トルツメ
トルツメ
20
URIの作成と送信
URIの入力画面になるので、例題6で入力したURIを次の手順で入力して送信します。
①入力欄にQueryパラメータ以外の部分を入力する
http://localhost:8080/sample27-03C/bookshop/member/address
②Query Params欄にパラメータを入力する ⇒ URI欄には自動的に追加される
③[send]ボタンを押す
④実行結果がResponse欄に表示される
Postmanを使って実行すると、取得した2件のJSON形式のデータが、わかりやすく表示 されます。これは要素が2つのJSONデータの配列表現です。
20
4.1 MIMEタイプ
前節の例では、レスポンスとして
List
をそのまま返したので、データがJSON形式になっ ていました。一般に、送受信するデータ形式を指定しない場合は、MIMEタイプとして"
*/*
"が設定されているものとみなされ、自動的に適切なタイプが使われます。データ形式は、IANA(Internet Assigned Numbers Authority)によりMIMEタイプとし て定義されています。例えば、次のようなものがあります。Jakarta Restful Web Services では
jakarta.ws.rs.core.MediaType
クラスでクラス変数として定義しています。MIMEタイプ 意味 MediaTypeクラスのクラス変数
text/plain テキスト形式 TEXT_PLAIN
text/xml, Application/xml XML形式 TEXT_XML, APPLICATION_XML
Application/json JSON形式 APPLICATION_JSON
text/html HTML形式 TEXT_HTML
プログラムでは、
@Produces
アノテーションを使ってクライアントに返すデータ形式を 指定し、@Consumes
アノテーションを使ってクライアントから受け取るデータ形式を指定 します。次に、その具体的な方法を解説します。4.2 @ProducesとMIMEタイプの選択メカニズム
2つのアノテーションは、原則としてクラスに付けますが、必要な場合はメソッドにも指 定して、クラスでの定義を上書きできます。
また、サーバーサイドでは、MIMEタイプを複数指定しておくことができます。実際に どのMIMEタイプを適用するかは、クライアント側の指定によります。クライアントは、
送信するデータのMIMEタイプをcontentsヘッダで、また、受け取るデータのMIMEタイ プをAcceptヘッダで指定できます。なお、クライアント側で指定しない場合は、サーバー 側で自動的に適切なMIMEタイプが選択されます。
Section Section Section Section Section S Sectition Se Sectctioionn Section S i
4
S tiHTTPとメディアタイプ
RESTful
20
MIMEタイプの選択 sample27-04/bookshop/MemberResource.java 例題7
1 @Stateless
2 @Path("/member")
3 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
4 public class MemberResource {
5 @PersistenceContext
6 EntityManager em;
7 @GET
8 public Member getMember(@QueryParam("id") Long id) {
9 Member member = em.find(Member.class, id);
10 return member;
11 }
12 }
例題の、
getMember
メソッドは、会員番号で会員を検索して返すサービスで、会員番 号をQueryパラメータで受け取ります。3行目の
@Produces
は、出力するMIMEタイプを指定します。複数のMIMEタイプを指 定する時は、{ }
の中に、コンマで区切って並べます。@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
この指 定により、MemberエンティティをXML形 式とJSON形 式で 返 すことが で きます。ただ、XML形 式 で 返 せるようにするためには、
Member
エンティティに@ XmlRootElement
※アノテーションを付けておく必要があります。例題7(続き)XMLに変換できるエンティティ sample27-04/entity/MemberResource.java 1 @XmlRootElement
2 @Entity
3 public class Member implements Serializable {
4 private static final long serialVersionUID = 1L;
5 @Id
6 private Long id;
7 private String name; // 氏名 8 private String mail; // メール // 以下省略
※XMLをオブジェクトにマップするためのJAXB(Jakarta XML Binding)で使われるアノテーションです。これによ
1行 アケル
次の行に送る
20
では、sample27-04を実行し、Postmanから呼び出してみましょう。
http://localhost:8080/sample27-04/bookshop/member?id=1
XML 形式
XML形式で返ってきたのは、受け取りのMIMEタイプを指定していなかったので、サー バー側で適切なタイプを選択したからです。送信時のヘッダーがどうなっていたか調べて みましょう。それには、Postmanの左下端にあるConsoleボタン( )を押します。す ると、次のようにヘッダ情報が表示されます。
特に指定していない
サーバーはxmlで返してきた
20
では、Acceptヘッダに"
application/json
"を指定して、JSON形式で受け取ってみま しょう。Postmanではヘッダ情報を設定するには、Pre-requestScriptをクリックします。クリックする
入力欄が開くので、次のように入力します。
pm.request.headers.add({key: 'Accept', value: 'application/json'});
Postmanはコマンドでヘッダ情報を設定します。
'Accept
'と'application/json
'の部 分を変更すると、他のヘッダも設定できます。設定したら[Send]ボタンを押して実行します。応答ではJSON形式のデータが返されます。
まだヘッダ情報もJSONとなっていることがわかります。
20
4.3 @Consumes
@Consumes
は、サーバーサイドがPOSTやPUTなどで受け入れるデータのMIMEタイプ を指定します。そこで、@POST
に対応するcreateMember()
メソッドを作成して、動作 を確認しましょう。リソースの追加 sample27-04B/bookshop/MemberResource.java 例題8
1 @Stateless
2 @Path("/member")
3 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
4 @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
5 public class MemberResource {
6 @PersistenceContext
7 EntityManager em;
8 @GET
9 public Member getMember(@QueryParam("id") Long id) {
10 Member member = em.find(Member.class, id);
11 return member;
12 }
13 @POST
14 public String createMember(Member member) {
15 if(member==null) throw new BadRequestException();
16 em.persist(member);
17 return "created";
18 }
19 }
4行目に、
@Consumes
アノテーションを付けてXMLとJSON形式のデータを受け入れる ように定義しています。青枠内がMember
インスタンスを受け取ってデータベースに登録 するcreateMember()
メソッドで、@POST
が付けてあります。@POST
は、新しくリソース(=オブジェクト)を作成・追加するメソッドに付けるアノテーションです。
createMember()
メソッドの引数がMember member
となっていることからわかるよう に、ウェブサービスクライアントが、Member
インスタンスをPOST※で送信してくること を予定しています。データ形式はクライアントのContent-Typeヘッダを見てXMLかJSON に自動的に設定されます。createMember()
メソッドは、受け入れたインスタンスをその ままデータベースに登録します。追加した メソッド
20
す。この例外はJakarta Restful Web Servicesで定義されている実行時例外で、HTTPの 応答に変換されます。
jakarta.ws.rsパッケージには、WebApplicationException以下、次のような実行時例外 が定義されているので、必要に応じて使うことができます。
ではsample27-04Bを実行し、PostmanからPOSTで
Member
インスタンスを送ってみま しょう。POST送信の手順
①○+ボタンを押して新しいタブを開く
②送信種別をPOSTに切り替え、URIを入力する。
http://localhost:8080/sample27-04B/bookshop/member
③[Body]⇒[raw]と選択し、[Text]をクリックして選択肢からJSONを選ぶ
RESTful
20
④前回実行したGETのタブをクリックして表示する(JSONデータをコピーするため)
⑤コピーボタンを押してJSONデータをコピーする
⑥POSTのタブをクリックして戻る
⑦JSONデータの入力欄で右ボタンを押して、JSONデータを貼り付ける
JSONになっているか確認
⑧JSONデータを次のように修正する(画面では表示順がABC順になっているが構わない)
id 6
name 中村
mail [email protected] zip 221-0014
pref 神奈川県
city 横浜市
20
⑨[Send]ボタンを押す ⇒ createdと200 OKというリターンコードが表示される
以上で、サーバーへの送信が終わり、データベースに書き込まれたことがわかります。
念のため、NetBeansでMemberテーブルの内容を参照すると、次のようになっています。
同様にしてXMLデータを送信してもエンティティを作成できます。例えば、次のデータ を使って、各自でやってみてください。送信データ種別をJSONからXMLに変え、データ 欄を消して代わりに次のデータを書き込みます。後は[Send]ボタンを押すだけです。
<member>
<city>横浜市</city>
<id>7</id>
<mail>[email protected]</mail>
<name>佐々木</name>
<pref>神奈川県</pref>
<zip>221-0014</zip>
</member>
20
5.1戻り値に使うResponseクラス
すでに見たように、RESTサービスのメソッドの戻り値には、どんな値でも使うことが できますが、実際にはResponseクラスのインスタンスを返すのが普通です。Responseク ラスは、レスポンスヘッダーや返す値(リソース)をひとつにまとめて保持するクラスです。
主に次の表に示すメソッドを使って、レスポンスを作成します。
Responseクラスのクラスメソッド
主なメソッド ステータス(番号) 意味・説明
ok() OK(200) OKステータスのみのレスポンス
ok(entity) OK(200) エンティティをセットして返す
ただし、entityはObject型(以下も同様)
ok(entity, mediaType) OK(200) エンティティとメディアタイプをセットし
て返す
ok(entity, type) OK(200) エンティティとメディアタイプ文字列を
セットして返す
ok(entity, variant) OK(200) エンティティとVariantオブジェクトを
セットして返す。Variant型はローケル、
エンコーディング、言語などの情報を含む オブジェクト
created(uri) CREATED(201) 作成したリソースへのURIをセットして返
す
accepted(entity) ACCEPTED(202) リクエストを承認したというステータスと
エンティティをセットして返す
noContent() NO_CONTENT(204) 要求を実行したがエンティティは返さない
seeOther(uri) SEE_OTHER(303) POSTした後にリダイレクトするパターン
で、リダイレクトURIをセットして返す
notModified() NOT_MIDIFIED(304) 変更なしというステータスをセットして返
す
notModified(tag) NOT_MIDIFIED(304) 同上。ただし、エンティティを指すタグを 含めて返す
temporaryRedirect(uri) TEMPORARY_REDIRECT (307)
要求されたリソースが一時的に別のURIに あるので、リダイレクトURLをセットして返 す
Section Section Section Section Section S Sectition Se Sectctioionn Section S i
5
S ti戻り値の作成
20
主なメソッド ステータス(番号) 意味・説明
serverError() サーバーエラー
(500番台の値) 何らかのサーバエラーを示す
status(status) セットしたステータス enum型のStatus、または、int型のステー タス番号をセットして返す
stats(status, reason) セットしたステータス int型のステータス番号と理由を示す文字 列をセットして返す
これらは、
Response
クラスのクラスメソッドで、ステータス情報と共に、オブジェク トやURIなどを返すことができます。これらのメソッドの戻り値は(メソッドを連結できるように)どれも
ResponseBuilder
型になっています。メソッドチェーンを終了するには、
Response
型の値を返すbuild()
メソッドを、末尾に連結する必要があります。
では、
Respose
を作成する具体的な方法をいくつか示します。// OKステータスを返す
return Response.ok().build();
// Memberクラスのmemberエンティティを返す return Response.ok(member).build();
// MemberクラスのmemberエンティティをJSON形式で返す
return Response.ok(member, MediaType.APPLICATION.JSON).build();
// 作成されたことを示すリターンコード(201)を返す
return Response.status(Status.CREATED).build();
// 作成・登録したMemberエンティティにアクセスできるURIを付けて返す return Response.created(memberUri).build();
// 削除処理の結果を返す。指定されたエンティティを削除したので、「エンティティは存在しなくなった」の意味
return Response.noContent().build();
5.2 CRUDのウェブサービス
戻り値に
Response
を使うCRUD形式のプログラムを作成します。前節ではGET(検 索)、POST(新規登録)の処理を作ったので、ここではUPDATE(更新)、DELETE(削除)、GET(全件検索、同じGETでもURIが違う)を追加して作成します。また、すべての戻り値 を
Response
オブジェクトで書き換えます。青太字にする
20
なお、
@get
処理に変更があります。パラメータ受け渡しのバリエーションを示すため、この例題ではクエリパラメータの代わりにパスパラメータを使っています。例題でアンダー ラインで示している9、10行目に注意してください。
やや長いソースコードですが、基本的に、データベースの章で解説したものと同じ内容 です。各メソッドの働きを確認しながら目を通してください。
CRUDのウェブサービス sample27-05/bookshop/MemberResource.java 例題9
1 @Stateless
2 @Path("/member")
3 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
4 @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
5 public class MemberResource {
6 @PersistenceContext
7 EntityManager em;
8 @GET // 検索 9 @Path("{id}")
10 public Response getMember(@PathParam("id") Long id) {
11 Member member = em.find(Member.class, id);
12 if(member==null) return Response.status(Status.NOT_FOUND).build();
13 return Response.ok(member).build(); // 200 14 }
15 @POST // 登録
16 public Response createMember(Member member) {
17 if(member==null) return Response.status(Status.BAD_REQUEST).build();
18 em.persist(member);
19 return Response.status(Status.CREATED).build(); // 201 20 }
21 @PUT // 更新
22 public Response updateMember(Member member) {
23 if(member==null) return Response.status(Status.BAD_REQUEST).build();
24 em.merge(member);
25 return Response.ok().build(); // 200 26 }
27 @DELETE // 削除
28 public Response deleteMember(@QueryParam("id") Long id) {
29 Member member = em.find(Member.class, id);
30 if(member==null) return Response.status(Status.NOT_FOUND).build();
31 em.remove(member);
32 return Response.noContent().build(); // 204
20
36 String jpql = "select e from Member e";
37 List<Member> list = em.createQuery(jpql, Member.class).getResultList();
38 return Response.ok(list).build();
39 }
40 }
@PUT
は更新処理、@DELETE
は削除処理です。また、クエリパラメータがないGET
は全 件検索の呼び出しになります。どれもエンティティマネージャーでの処理ですから、解説 は省略します。処理が失敗する場合は、例外を投げるのではなく適切なステータスコードを返すように します(下線部分)。
主なステータスには、次のようなものがあります。
ステータス 意味
OK 200 OK
CREATED 201 作成
ACCEPTED 202 承認済み
NO_CONTENT 204コ ンテンツなし
FOUND 302 見つかりました
BAD_REQUEST 400 BadRequest
FORBIDDEN 403 禁止されています
NOT_FOUND 404 見つかりません
REQUEST_TIMEOUT 408 RequestTimeout
CONFLICT 409 競合
また、例題のソースコードに示しているように、正常終了の場合に返すステータスは、
次のように決まっています。
動作 正常終了で返すステータスコード POST 201(CREATED)
GET 200(OK) PUT 200(OK)
DELETE 204(No Content)
では、PostmanからPOST、GET、PUT、DELETEを実行してみましょう。
以下の図をみながら、同じように実行してみてください。
20
POST
id=7のMemberを新規登録する送信データ(JSON 形式)
GET
id=6のレコードを検索する検索結果の受信データ 5
9340.tif に差替え サイズ、
枠線は 同じ 180 を挿入 図との行間は 0にする
182 を挿入 図との行間は 0にする
20
PUT
id=7のレコードのcity項目を川崎市に変更する送信データ(JSON 形式)
DELETE
id=7のレコードを削除する 184 を挿入図との行間は 0にする
186 を挿入 図との行間は 0にする
20
GET(全件検索)
188 を挿入 図との行間は 0にする
20
Jakarta Restful Web Servicesにはクライアントを作成するクライアントAPIがあります。
RESTサービスのクライアントをJava言語で作成できるので、いろいろな場面で活用でき ます。クライアントAPIは、StatelessセッションビーンやCDIビーンからも利用できます。
ここでは、Javaクライアントからサービスにアクセスする方法を具体的に解説します。
6.1 CRUD処理の概要
前節のsample27-05で作成したRESTfulウェブサービスを、クライアントAPIを使って、
Javaプログラムからアクセスします。
内容は、15章の例題2(sample22-01プロジェクト)と同様のCRUD処理です。ただ、15章 ではデータベースに直接アクセスしていましたが、ここでは、ウェブサービスにアクセス します。
ウェブ画面の構成は次のようです。sample27-05を実行した上で、この例題を実行して 動作を確認してください。
Section Section Section Section Section S Sectition Se Sectctioionn Section
S i
6
S tiクライアントAPI
RESTful20
6.3 クライアントAPIの使い方
解説の前に、例題のソースコードを示しておきます。青字の部分に注意してください。
Client APIの使い方 sample27-06/beans/Bb.java 例題10
1 @Named
2 @ViewScoped
3 public class Bb implements Serializable {
4 @NotNull
5 private Long id; // ID 6 private String name; // 氏名 7 private String mail; // メール 8 private String zip; // 郵便番号 9 private String pref; // 県 10 private String city; // 市区町村 11
12 Client client; // Client API 13 WebTarget target; // Client API 14 @PostConstruct
15 public void initClient() { // 最初にアクセスするリソースを設定しておく 16 client = ClientBuilder.newClient();
17 target
18 = client.target("http://localhost:8080/sample27-05/bookshop/member");
19 }
20 public void find() { // 検索 21 Response res = target
22 .path(String.valueOf(id))
23 .request()
24 .get();
25 if (res.getStatus() == Status.OK.getStatusCode()) {
26 Member member = res.readEntity(Member.class);
27 this.id = member.getId();
28 this.name = member.getName();
29 this.mail = member.getMail();
30 this.zip = member.getZip();
31 this.pref = member.getPref();
32 this.city = member.getCity();
33 } else {
34 clear();
35 this.name = "該当なし";
36 } 190
を挿入
削除
20
38 public void create() { // 新規作成
39 Member member = new Member(id, name, mail, zip, pref, city);
40 Response res
41 = target
42 .request()
43 .post(Entity.entity(member, MediaType.APPLICATION_JSON));
44 if (res.getStatus() == Status.CREATED.getStatusCode()) clear();
45 }
46 public void update() { // 更新
47 Member member = new Member(id, name, mail, zip, pref, city);
48 Response res
49 = target
50 .request()
51 .put(Entity.entity(member, MediaType.APPLICATION_JSON));
52 clear();
53 }
54 public void delete(Member member) { // 削除 55 Response res = target
56 .queryParam("id", member.getId())
57 .request()
58 .delete();
59 }
60 public List<Member> getAll() { // 全データをListに入れて返す 61 Response res = target
62 .request()
63 .get();
64 return res.readEntity(new GenericType<List<Member>>(){});
65 }
66 public void edit(Member member) { // 編集のため変数にコピーする 67 this.id = member.getId();
68 this.name = member.getName();
69 this.mail = member.getMail();
70 this.zip = member.getZip();
71 this.pref = member.getPref();
72 this.city = member.getCity();
73 }
74 public void clear() { // 変数をクリア 75 id = null;
76 name = mail = zip = pref = city = null;
77 }
78 @PreDestroy
79 public void close(){
80 client.close();
81 }
82 // セッター・ゲッターの掲載を省略 83 }
20
ウェブサービスをポイントするWebTargetインスタンスの作成
クライアントAPIを利用するには、最初に
Client
オブジェクトを作ります。Client
オ ブジェクトは重量級のオブジェクトなので、プログラムの最初で1度だけ作成します。そこ で、普通は、@PostConstract
を付けたメソッドの中で作成します。Client client;
WebTarget target;
@PostConstruct
public void initClient(){
client = ClientBuilder.newClient();
target
= client.target("http://localhost:8080/sample27-05/bookshop/member");
}
また、
Target
はリソースのURIをセットするオブジェクトで、Clientのインスタンスか ら作成し、それを使ってサービスにアクセスします。アクセス先に応じて必要な時に作成 して構いませんが、例題では同じURIしか使わないので、Client
オブジェクトと一緒に 作成しています。WebTargetの使い方の基本
WebTarget
インスタンスは、図のようにメソッドチェーンで使います。図は、代表的な 使い方を示しています。図で、[ ]の部分は必要なければ省略できます。
path()
やqueryParam()
はパラメータをセットします。また、accept()
はMIMEタ イプを指定し、header()
は任意のHTTPヘッダを付加します。例えば、次のようにすると
get
リクエストを実行して、id
が3のレコードをResponse
オ ブジェクトで受け取ることができます。8440.tif に差替え↑8440なし RESTfulウェブサービスのURIを指定する
この位置に、この大きさで この文字列を挿入
送付します 8840.tif に差替え
20
Response res = target .path("3") .request()
.get(); // getリクエストを実行して結果を受け取る
このほかにも使えるメソッドがありますが、Jakarta EE のAPIを参照してください。
jakarta.ws.rs.client
パッケージに、Client
クラス、WebTarget
クラスがあります。また、
jakarta.ws.rs.core
パッケージには、Response
クラスの詳細が掲載されてい ます。では、例題のコードを見ていきましょう。
findメソッド(20行目)
id
をパスパラメータで指定して、GETアクセスでレコードを取得します。path()
の引 数は文字列なので、String.valueOf(id)
のように文字列にして指定します。Response res = target
.path(String.valueOf(id)) .request()
.get();
if (res.getStatus() == Status.OK.getStatusCode()) { Member member = res.readEntity(Member.class); ・・・
Response
オブジェクトから検索したMember
オブジェクトを取り出すには、Response
クラスの
readEntity()
メソッドを使います。オブジェクトの型情報(Member.class
) を引数に指定する必要があります。なお、
readEntity()
メソッドだけでなく、Response
オブジェクトのメソッドを使って、様々な情報を得ることができます。次の表によく使うメソッドを示します。
メソッド 機能
String getHeaderString(String name) nameで指定したヘッダー文字列を得る
Locale getLanguage() ローケルを得る
URI getLocation() エンティティにアクセスするURIを得る
MediaType getMediaType() メディアタイプを得る
int getStatus() ステータスを得る
T readEntity(Class<T> entityType) 型を指定してエンティティを得る
※これは主なメソッドの抜粋です。詳細はjakarta EE apiドキュメントを参照してください https://jakarta.ee/specifications/platform/9/apidocs/
本文中の参照元
※不明 // Pahtパラメータとして3をセットする
続けて
表のメソッドに 表
、
20
create()メソッド(38行目)
Member
クラスのインスタンスを作成して、POSTアクセスによりデータベースに登録し ます。Member member = new Member(id, name, mail, zip, pref, city);
Response res = target .request()
.post(Entity.entity(member, MediaType.APPLICATION_JSON));
送信する
Member
オブジェクトは、Entity
クラスでラップしますので、entity
メソッ ドの引数に、送信するオブジェクトとそのメディアタイプを指定します。update()メソッド(46行目)
PUTアクセスによりデータベースのレコードを更新します。
Member member = new Member(id, name, mail, zip, pref, city);
Response res = target .request()
.put(Entity.entity(member, MediaType.APPLICATION_JSON));
クライアントAPIの使い方は、
post
と同じで、メソッドをput
に変えるだけです。delete()メソッド(54行目)
id
をクエリパラメータで指定して、DELETEアクセスでレコードを削除します。クエリ パラメータを指定するqueryParam()
では、引数に主キーのフィールド名と値を指定しま す。Response res = target
.queryParam("id", member.getId()) .request()
.delete();
青下線
青下線
20
Response
オブジェクトから取り出すには、readEntity()
メソッドを使いますが、その 引数の書き方に注意してください。Response res = target .request() .get();
return res.readEntity(new GenericType<List<Member>>(){});
List
のような複数のオブジェクトの集まりを受け取るには、何かのクラスにラップして 受け取る必要があります。従来は、ラップするクラスを別に作成して使っていました。例 えばこのケースでは、ArraysList
を継承したクラスを作成する必要があります。
GenericType
クラスはそれを簡単にするために設けられた総称型のクラスで、任意の 型を表すオブジェクトです。<List<Member>>
のように具体的な型を指定してサブクラ スを生成することで、いろいろな型を表すことができます。new GenericType<List<Member>>(){} // 匿名サブクラスのインスタンスを生成
なお、右端にある
{ }
に疑問を持つかもしれませが、これは指定したクラス(ここではGenericType
)のサブクラスのインスタンスを生成する書き方です。サブクラスの名前が 指定しないので匿名サブクラスといいます。これにより、GenericType
クラスの無名の サブクラスを定義すると共に、そのインスタンスが作成されます。Jakart EE のAPIドキュメント(
jakarta.ws.rs.core.GenericType<T>
を参照)に もこの使い方が示されています。(注)匿名クラスについては「わかりやすいJavaオブジェクト指向徹底解説」をご覧ください。
定型的な書き方ですから、いつもこのように書いてください。これにより、
Member
の リストを受け取ることができます。Clientオブジェクトをcloseする(78行目)
client
オブジェクトは、コンテンツを読み取った後で自動的にclose
されますが、通 信エラーなどが発生し、そのまま残ってしまうことがあります。そこで、@PreDestroy
を付けたメソッドの中でクローズしておきます。これによりバッキングビーンが消去され る直前に
client
オブジェクトも自動的にクローズされます。lv4(0.0 の数字 のつく形式)の 指示があったが 他に合わせた
1行アケル
?です
20
要点
※理解した項目にはチェックを入れましょう
RESTfulウェブサービスの構成
□ Applicationクラスを継承したクラスに@ApplicationPathを書いてアプリケーションパス を設定する
・public なクラスで、パッケージとクラス名は自由、メソッドなどは書かない
□ HTTPの動詞(GET、POST、PUT、DELETEなど)を使ってサービスを構成する
□ サービスはリソースごとに、1つのクラスとして作成する
・クラスには@Pathを付けてリソースを設定する -- @Path("member")など
□ サービスを提供するメソッドには、@GET、@POST、@PUT、@DELETEなどを付ける
□ クライアントは、URIを指定してリソースにアクセスする
・http://localhost:8080/sample27-01/bookshop/member
□ PathパラメータやQueryパラメータを使ってURIと共に送信できる ・http://localhost:8080/sample27-02/bookshop/member/1 ・http://localhost:8080/sample27-02/bookshop/member?id="1"
□ Queryパラメータには@DefaultValue()によって、指定しなかった場合の既定値を設定できる
HTTPとメディアタイプ
□ @Producesは送信するデータのMEDIAタイプを指定する。複数のタイプを指定できる
□ @Consumesは受信するデータのMEDIAタイプを指定する。複数のタイプを指定できる
□ @Producesと@Consumesはクラスに指定するが、メソッドに付けるとクラスの指定を上書きで きる
□ クライアントは、Acceptヘッダを指定して受信するタイプを指定できる
□ クライアントは、Content-Typeヘッダで送信するタイプを指定できる
□ XML形式で送受信するエンティティには@XmlRootElementアノテーションを付ける
Responseクラスは戻り値として使う
Section Section Section Section Section S Sectition Se Sectctioionn Section S i
7
S tiまとめ
20
□ サービスでエラーが発生した時は、例外を投げる方法もあるが、Responseにステータスをセッ トするとクライアントで対策が取りやすくなる
クライアントAPI
□Clientオブジェクトは@PostConstructを付けたメソッドの中で作成する
□Clientオブジェクトは、@PreDestroyを付けたメソッドの中でclose()する
□Clientインスタンスを使ってURIをセットしたTargetオブジェクトを作成する
□リソースにアクセスしてResponseを得るには次のようなメソッドチェーンを使う
<∇≡挿入>☆8440.tif 84%
□Responseからオブジェクトを得るにはreadEntityメソッドを使う Member member = res.readEntity(Member.class);
□Responseからオブジェクトのリストを得るにはGenericTypeクラスでListの型を指定する。
List<Member> list = res.reanEntity(new GenericType<List<Member>>(){});
8440ない
今回、送付します