新規のエンタープライズJava開発において現在有力視される3つのフレームワーク、Java EE、Spring Framework、Play Framework。本連載「3つのフレームワークで学ぶエンタープライズJava開発入門」では、3つの違いについて、アーキテクチャ、UI開発/画面の作り方、画面遷移、セッション管理、トランザクション、DBアクセス、開発生産性(ツール/デプロイ/CI/テスト)、外部システムとの連携などの観点から見ていきます。
連載第1回の「Strutsを使い続けることの問題点&現在有力なJava EE、Spring、Play Frameworkの基礎知識とアーキテクチャ」では、本連載で取り上げる3つのフレームワークの概要について解説し、全体的なアーキテクチャを俯瞰しました。
前回の「Strutsと比較して理解するJSFとCDI、アクションベースとコンポーネントベースの違い」から複数回に分けて、Java EE、Spring Framework、Play Framework各フレームワークのControllerやViewについての解説を行っています。前回はJava EEのWebアプリケーション仕様であるJSFを紹介しました。今回は同じくJava EEのWebサービス仕様であるJAX-RSを取り上げます。
JAX-RSは、RESTに準拠したWebサービスを作るためのJava EEの仕様です。本記事ではまず、JAX-RSの基本的な設計方針であるRESTについて解説し、Struts 1やJSFとの違いを明らかにします。その後、サンプルコードを通して基本的な機能を紹介し、JAX-RSの使いどころを紹介します。
REST(Representational State Transfer)とは、端的にいえば、HTTPの仕組みや考え方を最大限に生かしてWebサービスを構築する考え方を指します。特定のアーキテクチャやルールはありません。幾つかの原則や考え方があるだけです。
RESTによる設計原則は以下の4つに集約できます。
RESTでは、情報やデータを全て「リソース」という考え方で表現します。また、リソースはURIによって一意に識別します。
リソースに対して操作をするためのメソッドはHTTPで定義されているGET、POST、PUT、DELETE、HEAD、OPTIONS、TRACEのみを使用します。よく使うのは、GET(取得)、POST(登録)、PUT(更新)、DELETE(削除)の4種類です。
リソースをさまざまな形式で表現できるようにします。例えば、HTMLやテキスト、XML、JSON、バイナリなどです。また、関連するデータはハイパーメディア(リンク)としてデータに含めることができます。
RESTでは基本的に、状態はクライアント側で保持し、サーバとのやりとりで必要な情報は全てリクエストに載せるようにします。これはすなわち、サーバ側ではクライアントの状態を保持するためにセッションスコープを用いないことを意味します。
RESTの利点の1つは、【原則1】と【原則2】により、シンプルなWeb API(以下、API)を作れることです。これは、URIとHTTPメソッドによって処理が決定されるためです。
例えば、従業員番号が「1」の従業員の情報を取得するためのAPIは次のようになります。
GET /employees/1 HTTP/1.1
また、従業員番号が1の従業員の情報を更新するためのAPIは次のようになります。
PUT /employees/1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=hoge&age=35
Webサービスの仕様にはRESTの他にもSOAPがあります。SOAPでは開発者が任意の名称のメソッドを定義できますが、WSDLのようなサービスを定義するためのドキュメントが必要になり、大掛かりな仕組みが必要になってしまいます。
一方、RESTでは、URIとあらかじめ決められた数種類のHTTPメソッドのみでAPIを構成するため、WSDLを必要とせずシンプルなAPIが作成できます。
2つ目の利点は、【原則3】により多様なデータの表現に対応できることです。
SOAPではデータの形式はXMLに限定されますが、RESTでは、XMLやJSON、バイナリといったさまざまなデータ表現に対応できます。そのため、ブラウザ以外からのアクセスにも適用が容易です。例えば、JavaScriptのAjaxからのアクセスや、スマートフォンのネイティブアプリからの通信への適用も容易です。
最後は、【原則4】によるステートレス性に関する利点です。ステートレスな通信によりサーバ側で状態を保持しないことから、サーバ側でクライアントごとの状態を保持する必要がなくなります。そのため、サーバ増設による負荷分散が単純でスケールアウトが容易になります。
ただし、ステーレス性については、作成するアプリケーションの内容やフレームワークの機能に応じて、トレードオフが発生することがあります。
プログラムから使用することを前提とした、APIのようなシステムの場合は1度の通信で処理を完結させるために、ステートレスな通信とするべきです。ですが、ブラウザで使用することを前提とした画面ありきのECサイトや業務システムでは、画面の情報をクライアントで持ち回るよりもセッションスコープを用いた方が効率よく開発できる場合があります。
また、セッションスコープで保持することが前提となっているフレームワークもありますので、自前でRESTに対応した認証の仕組みを作るよりも既存の仕組みを利用した方が良い場合があります。
従来はURIが示すのは対応するプログラムでしたが、RESTではURIが示すのはリソースです。リソースとは単純に言えばそのシステムで扱う主要なデータを指します。そのデータをどう操作するのかをHTTPメソッドによって決めます。例えば、GETでデータを取得する、PUTでデータを更新するといった具合です。つまり、RESTではリソースが主役といえます。
システムのデータ構造をリソースとして適切にURIで表現できると、システムの利用者に対して、推測しやすく直感的なAPIを提供できます。
RESTはJAX-RSだけではなく、今後紹介するSpring FrameworkやPlay Frameworkの他、Ruby on Railsといった他言語のフレームワークにも影響を与えています。GoogleやAWSが提供するサービスなどもREST APIの形式で公開しています。RESTはどこにでも登場してきますので覚えておいて損はありません。
ただし、RESTはシンプルですが奥が深いため、Struts 1から移行するとなるとアプリケーション設計に対する考え方をシフトする必要があります。
JAX-RS(Java API for RESTful Web Services)は、前述したRESTの原則に沿ったWebサービスをJavaで構築するための仕様です。JAX-RSのバージョン1.0が策定されたのは2008年で、Java EE 6から採用された比較的新しい仕様です。Java EE 7ではJAX-RSのバージョンは2.0です。
JAX-RSは学習コストが少なく、シンプルなWebサービスを作るのに適しています。拡張機能を導入することでMVCフレームワークとしても使うこともできます。応用範囲が広くどこでも使えるフレームワークといえます。
Struts 1と違って、設定ファイルや継承を用いず、多くの設定をアノテーションで行うのが特徴です。
JAX-RSによるプログラムは、基本的にRESTのリソースと対応するリソースクラスを作成し、リソースクラスのメソッドにHTTPメソッドや表現形式をアノテーションで設定していきます。リソースクラスは、JAX-RSの主要な要素であり、Struts 1のアクションやJSFのコントローラーに相当します。
JSFとの違いは連載第1回で解説してます。概要についての図を再掲しておきます。
JSFやStruts 1もHTTPを使用しています。ですが、これらのフレームワークではHTTPを重視せず、クライアントとサーバ間でデータを送るための経路くらいに考えています。
例えば、JSFではHTTPのやりとりは隠蔽されていて開発者が意識することはほとんどありません。また、Struts 1では、Actionクラスと対応付けるのはURIのみです。URIに対して、GET、POSTどちらの呼び出しでも同じActionクラスのメソッドが実行されるので、HTTPメソッドによる処理の違いをさほど意識する必要がありません。
RESTによるアプリケーションを作成する場合は、今までは深く考慮する必要がなかった、URIの設計やHTTPメソッドの使い分けなど、HTTP仕様に基づいた設計について考慮する必要があります。
それでは、従業員データを対象としたCRUD操作を提供するリソースクラスを作ってみましょう。このリソースクラスは、以下のように、IDと名前、年齢を持ちます。
public class Employee {
private long id;
private String name;
private int age;
// コンストラクタ、アクセッサは省略
}
従業員データを操作するためのURIとHTTPメソッドは以下のように定義します。
| 従業員の操作 | メソッド | URI | レスポンスの表現形式 |
|---|---|---|---|
| 検索 | GET | /crudsample/employees?name={name} | JSON、XML |
| 取得 | GET | /crudsample/employees/{ID} | JSON、XML |
| 登録 | POST | /crudsample/employees | JSON、XML |
| 更新 | PUT | /crudsample/employees/{ID} | なし |
| 削除 | DELETE | /crudsample/employees/{ID} | なし |
このようにRESTでは、操作内容とHTTPメソッドを一致させます。上記の取得・更新・削除のように、同一のURIでもHTTPメソッドを変えることによって異なる操作を設定できます。
JAX-RSで上記のリソースに対応するEmployeeResourceクラスを作成します。少々長くなりますので、ソースコードを分割しながら紹介します(リスト2-1~3)。
まずはクラス宣言とフィールド宣言です。
@Path("crudsample/employees") //【1】このリソースの起点のURIを指定
@RequestScoped //【2】CDIと統合
public class EmployeeResource {
//【3】ビジネスロジックを注入
@Inject
private EmployeeService service;
ポイントを解説します。
【1】では、@Pathアノテーションによりリソース全体で共通のURIを定義しています。前述の表では、「/crudsample/employees」というURIがどの操作でも共通になっています。そのようなURIはリソースクラスの@Pathアノテーションに設定することで、一元化して定義できます。
【2】では、リソースクラスをCDIのBeanとして宣言しています。Java EE環境では、JAX-RSとCDIを統合できるため、インジェクションが可能となります。
【3】ではビジネスロジックを実行するEmployeeServiceというBeanをインジェクションしています。【2】でリソースクラスをCDIのBeanとしたことにより、他のCDI Beanをインジェクションできます。
続いて、HTTPメソッドのGETに対応するリソースクラスのメソッドを説明します。まずは、名前を指定して従業員を検索するsearchメソッドです。このメソッドは次のURIに対応付けています。
GET /curdsample/employees?name=hoge HTTP/1.1
@GET // 【4】GET crudsample/employees?name=<name> に対応
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) //【5】XMLかJSONを生成
public List<Employee> search(@QueryParam("name") String name) {
return service.findByName(name);
}
【4】では、@GETアノテーションを指定してsearchメソッドを上記のGETリクエストに対応付けています。URIに含まれるクエリストリングnameの値(上のURIの例ではhoge)は、searchメソッドの引数nameに割り当てられます。これは引数に、@QueryStringアノテーションを指定しているためです。JAX-RSではHTTPリクエストのさまざまな値を取得するためのアノテーションを多数用意しています。
【5】では、@Producesアノテーションにより、searchメソッドの戻り値のList
実行時にどちらのデータ形式を選択するかは、クライアントからの指定に従います。クライアントのHTTPリクエストに、受け入れ可能な表現形式を示すAcceptヘッダがある場合、その形式で返却します。
例えば、「Accept : application/json」ヘッダを付与して実行すると、以下のようなJSON形式のデータを返却します。
一方、「Accept: Application/xml」ヘッダを付与して、リクエストを発行すると、以下のようなXML形式のデータを返却します。
上記以外のAcceptヘッダを指定すると、エラーとなります。
このように、アノテーションの設定のみでさまざまな表現形式に対応できるのがJAX-RSの特徴の1つです。
次に、従業員をIDで検索するメソッドを見てみましょう。
@GET
@Path("{id}") // 【6】crudsample/employees/{id} に対応
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Employee get(@PathParam("id") long id) {
return service.findById(id);
}
【6】では 従業員1人分の情報を取得するためのGETリクエストのURIを定義しています。getメソッドに設定した「@Path(“{id}”)」というアノテーションは、リソースクラスに付与した@Pathアノテーションからの相対パスとして解釈されるため、「/crudsample/employees /{id}」というURIに対応します。
{id}のような{ }で囲まれた文字列は、URIの中の可変文字列を意味しています。IDを指定するようなリソースに対しては、URI中にIDを埋め込む方式がRESTでは一般的です。
URIの可変文字列をメソッドの中で使用するには、メソッドの引数に@PathParamアノテーションを指定します。
最後は、HTTPメソッドのPOST、PUT、DELETEに対応するメソッドです。
@POST // 【7】POST crudsample/employees に対応
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) //【8】HTTPフォームを受け取る
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Employee newEmployee(@FormParam("name")String name, @FormParam("age")int age) {
return service.save(name, age);
}
@PUT // 【9】PUT curdsample/employees/{id} に対応
@Path("{id}")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void updateEmployee(@PathParam("id") long id,
@FormParam("name")String name, @FormParam("age")int age) {
service.update(new Employee(id, name, age));
}
@DELETE // 【10】DELETE crudsample/employees/{id} に対応
@Path("{id}")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void deleteEmployee(@PathParam("id") long id) {
service.delete(id);
}
}
【7】では、@POSTアノテーションを指定して、POSTに対応するメソッドであることを宣言しています。このnewEmployeeメソッドでは、新しい従業員IDを発行するため、従業員データを戻り値として返すことで、発行したIDをクライアントに通知しています。
【8】では、@Consumesアノテーションによって、newEmployeeメソッドが受け取ることのできるデータ形式を指定しています。ここでは、通常のHTMLのFORMを受け取ると定義しています。FORMの各属性の値は、メソッドの引数の@FormParamアノテーションによって取得できます。
【9】では、@PUTアノテーションによって、PUTメソッドによる更新操作を定義しています。PUTでは、従業員IDを指定することを前提にするため、idをURIに含めるように指定します。このメソッドでは返却するデータがないため、戻り値をvoidにしています。その場合、クライアントにはHTTPレスポンスコード「204」(No Content)のみが返却されます。
【10】では、@DELETEアノテーションによって、DELETEメソッドによる従業員削除メソッドを定義しています。
以上が、JAX-RSのリソースクラスの基本的な作成方法となります。アノテーションによって、リクエストパラメータの解析や、レスポンスデータの作成といった定型的なコードが削除され、とてもシンプルなコードになります。
また、今回は割愛しましたが、リクエストパラメータからCookie やHTTPヘッダなどを取得するためのアノテーションも用意しています。
続いて次ページでは、JAX-RSの応用的な使い方を幾つか紹介します。
先ほどのPOSTメソッドの例では、リクエストからのデータをHTMLのFORM形式としていました。しかし、AjaxリクエストではJSONのようなJavaScriptと親和性のあるデータ形式を使うのが一般的です。JAX-RSでは、出力データの場合と同様に、入力データにもJSONやXMLなどが使えます。
サンプルとして、前回の記事でJSFを使って作成した認証処理をJAX-RSで実装してみましょう。まず、リクエストのJSONデータを示すJavaクラスを定義します。このLoginRequestクラスは、JSONデータに対応し、ユーザーIDとパスワードを持ちます。
public class LoginRequest {
@NotNull
@Size(min = 1)
public String id;
@NotNull
@Size(min = 1)
public String password;
}
続いて、ログイン処理を行うloginメソッドを定義します。
@Path("login")
@RequestScoped
public class LoginResource {
@POST
@Consumes(MediaType.APPLICATION_JSON) // JSONでデータを受け取る
@Produces(MediaType.APPLICATION_JSON)
public LoginResponse login(@Valid LoginRequest loginReq) {
// 省略
}
}
このメソッドには、@Consumesアノテーションで「Application/json」形式のデータを受け取ることを指定し、引数にはJSONデータを示すLoingRequestクラスを指定します。
loginメソッドを実行するには、「{“id”:”xxx”, “password”:”xxxx”}」のようなJSONデータをリクエストに指定します。
このように、JSONなどのデータからJavaオブジェクトの変換をJAX-RSが行いますので、さまざまなデータの扱いが簡単になります。
JSFと同様に、JAX-RSでもBeanValidationを用いた入力値のチェックが可能です。BeanValidationのアノテーションをリソースメソッドの引数に指定すると、リソースメソッドの実行前にチェックが行われます。
まず、String型やプリミティブ型の引数に対するアノテーションの指定方法を説明します。
リスト2-3でString型のIDを指定して検索を行うリソースメソッドを紹介しました。ここで、IDの入力を必須としたい場合、以下のように@NotNullアノテーションを引数に追加します。
@GET
@Path("{id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Employee get(@PathParam("id") @NotNull long id) {
次にJavaオブジェクトに対するアノテーションの指定方法を説明します。
前述のJSONデータの受け取る方法では、リソースメソッドの引数にLoginRequestクラスのようなJavaオブジェクトを指定していました。Javaオブジェクトに対するチェックは、引数に @Valid アノテーションを追加し、Javaオブジェクトの各フィールドにBeanValidationのチェック用のアノテーションを追加します。
リスト3とリスト4のソースコードは、この内容に沿ってアノテーションを設定していますので、確認してみてください。
バリデーションエラーは、何もしなければシステムエラーとして扱われます。バリデーションエラーで発生するConstraintViolationExceptionをハンドリングするために、ExceptionMapperという例外に対する拡張ポイントを作成します。
コードは以下のようになります。
@Provider //【1】
public class BeanValidationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
//【2】例外からResponseへの変換
@Override
@Produces(MediaType.APPLICATION_JSON)
public Response toResponse(ConstraintViolationException exception) {
List<Pair> list =
exception.getConstraintViolations().stream()
.map(v -> new Pair(last(v.getPropertyPath().toString()), v.getMessage()))
.collect(Collectors.toList());
//【3】Responseの構築
return Response.status(400).type(MediaType.APPLICATION_JSON)
.entity(new GenericEntity<List<Pair>>(list){})
.build();
}
private String last(String str) {
System.out.println(str);
String[] arr = str.split("[.]");
return arr[arr.length-1];
}
}
【1】では、クラス宣言の前に、JAX-RSに処理を追加することを示す@Provider アノテーションを指定して、JAX-RSの拡張であることを示しています。
【2】では、toResponseメソッドを宣言し、例外からResponseへの変換コードを書きます。
Responseとは、JAX-RSのレスポンスを表す汎用的なクラスです。実際には、リソースクラスのメソッドの戻り値は最終的にResponseに変換されます。
【3】では、Responseを構築しています。例外から入力チェックエラーのメッセージを取得して、JSON形式のデータをレスポンスに書き込んでいます。また、ステータスコードにHTTPレスポンスコードの「400」(BadRequest)も設定しています。
RESTではクライアントプログラムが、サーバからの処理結果を正しく判定するために、処理結果に応じたステータスコードを設定することが重要です。
ここまで、JAX-RSの基本的なプログラムの作成方法を解説しました。最後に、JAX-RSを用いてWebアプリケーションを作成する方法について解説します。
ここまでの解説では、Viewに関する説明をしてきませんでした。実は、JAX-RSはWebサービスを実現するための仕様のため、Viewに関する特定の機能を提供していません。そこで、JAX-RSでWebアプリケーションを作成する2つの方法を紹介します。
1つ目の方法は、JAX-RSではViewの構築に必要なデータだけを提供し、Viewの構築はクライアントに任せる方式です。近年、クライアント側にはさまざまな技術が使われるようになっています。例えば、JavaScriptによるクライアントサイドのフレームワークや、モバイル向けのHTMLサイト、iOSやAndroidなどのネイティブアプリなどです。
さまざまなクライアントから利用するシステムでは、サーバ側はREST APIを公開するだけのシンプルな構成にしておくことで、クライアントの種類に応じた技術を採用できるようになります。
REST APIを使うと、クライアントとサーバはREST APIを通してデータを送受信するようになるので、両者で使用する技術を変更しても影響を受けにくくなります。
クライアントがブラウザのみの場合、サーバ側でViewを構築することもできます。
具体的な方法として、JAX-RSの参照実装フレームワークである「Jersey」の提供するMVC拡張機能を使う方法があります。このMVC拡張機能を使うことで、JAX-RSのリソースのメソッドの戻り値に、JSPなどのテンプレートを指定できるようになります。
GETリクエストに対する例を示します。
@GET
@Template(name = "/welcome")
public Model notifications() {
Model model = new Model();
model.put("notifications", Arrays.asList("This isJAX-RS app", "note.."));
return model;
}
MVC拡張機能を利用した場合、リソースクラスのメソッドはレスポンスとしてViewの内容を返却する必要があります。具体的には、Viewで使うテンプレートとテンプレートに埋め込むデータの2つが必要です。上記のサンプルコードでは、@Templateアノテーションで、遷移先のViewテンプレートのパスを設定しています。戻り値のModelがテンプレートに埋め込むデータです。
こうすることで、レスポンスにはwelcome.jspとModelから生成されたHTMLが設定されるようになります。
このように、MVC拡張機能を用いると、JAX-RSをシンプルなWebアプリケーションフレームワークとして使えます。
まだ正式にリリースはされていませんが、次期のJava EE 8には、公式のアクションベースのフレームワーク「MVC 1.0」が含まれる予定です。
「MVC 1.0」はJAX-RSに、JSPやFaceletsなどのテンプレート機能とCDI、BeanValidationを盛り込んだ、前述のMVC拡張と似たフレームワークとなることがアナウンスされています。
MVC 1.0が登場すると、JAX-RSだけで、RESTベース、ブラウザベース両方のシステム構築が可能となるため、筆者はJava EE 8の目玉になると予想しています。
今回は、RESTの概要、JAX-RSの基本的な使い方、JAX-RSを用いたViewの構築について解説しました。
前回の記事で紹介したJSFとJAX-RSはどちらもJava EEの標準仕様です。最後にこれらの使い分けについて述べます。
JSFは、Viewが主役のコンポーネントベースのフレームワークであり、ブラウザベースのアプリケーションを作成することに特化しています。また、セッションを多用するステートフルなフレームワークです。このため、View間で保持する情報が多数ある場合や、Viewの共通部分が多い場合には、ステートフルな性質やコンポーネントの再利用性を生かして効率的な開発を行えます。
一方、JAX-RSは、URIとHTTPメソッドによって処理を決定し、レスポンスデータを返却するシンプルなアクションベースのフレームワークです。サーバサイドはREST APIを提供するシンプルな構成になるため、ブラウザに限らず多種多様なクライアントに対応できます。
また、セッションを用いない、ステートレスなフレームワークのため、単体ではViewに関する機能を持たず、Viewを構築する技術やクライアントの状態管理は別途考慮しなければいけません。そのため、さまざまなViewを提供する必要があるシステムやViewの変更が多いシステムに適しています。
また、ステートレスな性質からサーバ負荷が軽くなり負荷分散も容易になるため、アクセスが多いシステムにも適しています。JSFと比較するとJAX-RSはシンプルで使いやすく、データ変換やバリデーションのような定型的なコードをほとんど書く必要がないため、効率的に開発できるフレームワークです。
Java EE標準の中で、アクションベースでWebアプリケーションを構築する場合には有力な選択肢になります。
次回は、Javaの代表的なオープンソースソフトウェアフレームワークであるSpring Frameworkについて、Spring BootとSpring MVCを中心に解説します。
なお、今回作成したアプリケーションのソースコードは筆者のGitHubで公開しています。
本記事は重要なトピックのみを抜粋して紹介しましたので、ソースコード全体については以下のGitHubリポジトリを参照してください。