4 CDIとJAX-RSを使ったWebサービスの作成
今回は簡単なWebサービスとして、パラメータに7桁の郵便番号を渡すと、それに対応した住所を返すようなアプリケーションを実装してみる。実は『軽量・高速・安価で、安定性と信頼性も兼ね備えたWebSphere Application ServerのLiberty Coreを使ってみた』でも同様のサンプルを実装しているが、当時はJAX-RS 1.1とCDI 1.0の組み合わせだった。一方Java EE 7のWeb Profileでは、特に以下の点が従来と大きく異なっている。
- CDIを使う際にbeans.xmlの定義が省略できるようになった
- JAX-RSが正式にCDI連携に対応した
- JSON形式のデータをJSON-Pで簡単に扱えるようになった
- JAX-RSにクライアントAPIが追加された
JAX-RSとCDIの連携については、JAX-RS 1.1は直接CDIには対応していなかったため、JAX-RSのリソースクラスをEJB連携機能を使ってEJB化することで、間接的にCDIを利用していた。それに対してJAX-RS 2.0は正式にCDIをサポートするようになったため、EJBを経由することなく直接CDIを使えるようになっている。
では、早速実装していこう。プロジェクト名は「ZipcodeService」とした。
まず、住所を表すAddressクラスを次のように定義する。
public class Address { private String id; // ID private String zip; // 郵便番号 private String address; // 住所 public Address(String id, String zip, String address) { this.id = id; this.zip = zip; this.address = address; } public String toString() { return this.id + ":" + this.zip+ ":" + this.address; } // Setter/Getterは省略 }
住所データの一覧を扱うロジックは、AddressListクラスに次のように実装した。スコープは@ApplicationScopedを指定した。
@ApplicationScoped public class AddressList { private List<Address> addressList = new ArrayList<>(); // 住所のリストを格納 public AddressList() { try { // csvファイルから郵便番号と住所のリストを読み込む URI uri = this.getClass().getResource("postalcode.csv").toURI(); try (Stream<String> source = Files.lines(Paths.get(uri))) { this.addressList = source.map(s -> new Address(s.split(",")[0], s.split(",")[1], s.split(",")[2])) .collect(Collectors.toList()); } catch(IOException ex) { ex.printStackTrace(); } } catch (URISyntaxException e) { e.printStackTrace(); } } /** * 指定された郵便番号の住所データを検索して返す * @param zipcode 7桁の郵便番号 * @return マッチしたAddressを格納したList */ public List<Address> search(String zipcode) { List<Address> result = this.addressList.stream() .filter(s -> s.getZip().equals(zipcode)) .collect(Collectors.toList()); return result; } /** * すべての住所データを返す * @return すべてのAddressを格納したList */ public List<Address> getAll() { return this.addressList; } }
郵便番号データは日本郵便が公開している郵便番号データから、郵便番号と住所の情報のみ抽出し、一意のIDを付加したものをCSVファイルとして使用した。これをAddressListのコンストラクタで読み込んでListとして保持する。AddressListには、読み込んだ住所データから、指定された郵便番号の住所を検索して返すsearch()メソッドと、すべての住所データを返すgetAll()メソッドを用意している。
JAX-RSのリソースクラスは、SearchAddressクラスとして次のような実装になった。
@ApplicationScoped @Path("/address") public class SearchAddress { @Inject private AddressList addressList; // データソース /** * すべての住所データを取得し、JSON形式で返す */ @Path("all") @GET @Produces({MediaType.APPLICATION_JSON}) public List<Address> all() { List<Address> address = addressList.getAll(); return address; } /** * 指定された郵便番号の住所データを検索し、結果をJSON形式で返す */ @Path("search") @GET @Produces({MediaType.APPLICATION_JSON}) public List<Address> search( @QueryParam("zip") String zipcode) { List<Address> address = addressList.search(zipcode); return address; } /** * JSON形式で指定された郵便番号の住所データを検索し、結果をJSON形式で返す */ @Path("jsonsearch") @POST @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) public List<Address> jsonsearch(ZipRequestParam param) { List<Address> address = addressList.search(param.getZip()); return address; } }
ポイントは、データソースとなるAddressListオブジェクトをCDIを利用して@Injectアノテーションでインジェクションしていることだ。こうすることで、SearchAddressクラスはAddressListの実装への依存を取り除くことができる。
このクラスでは、Webサービスとして機能する3つのメソッドを用意している。1つはすべての住所データを取得するためのGETリクエストを提供するall()メソッドで、AddressListのgetAll()の実行結果をJSON形式で返す。リクエストパスは「/address/all」となるように設定している。レスポンスをJSON形式にするには、@Producesアノテーションを使って「@Produces({MediaType.APPLICATION_JSON})」を指定すればよい。
2つ目は、「zip」パラメータで指定した郵便番号をGETリクエストとして受け取るsearch()メソッドである。このメソッドへのリクエストパスは「/address/search」となっており、AddressListのsearch()の結果をJSON形式で返す。
3つ目は、JSON形式で指定されたパラメータをPOSTリクエストとして受け取るjsonsearch()メソッドで、リクエストパスは「/address/jsonsearch」とした。
JSON形式でパラメータを受け取る場合は、@Consumesアノテーションを使って「@Consumes({MediaType.APPLICATION_JSON})」を指定すればよい。この例では、次の形式のJSONデータを受け取ることができる。
{ "zip" : "整数7桁の郵便番号" }
このJSON形式のパラメータを扱うためのクラスをZipRequestParamとして次のように定義した。
public class ZipRequestParam { private String zip; // 郵便番号 public ZipRequestParam() {} public ZipRequestParam(String zip) { this.zip = zip; } // Setter/Getterは省略 }
なお、all()、search()、jsonsearch()が返す結果のJSONデータは、いずれも複数の住所データに対応した次の形式になるようにしてある。メソッドの処理としてはListオブジェクトをreturnしているだけだが、これをJAX-RSがJSONの配列として取り扱ってくれる。
[ { "address" : "住所1", "id" : "ID1", "zip" : "郵便番号1"}, { "address" : "住所2", "id" : "ID2", "zip" : "郵便番号2"}, ...... ]
JAR-RSを使用するために必要となるApplidationConfigクラスについては次のようにした。ルートパスを「ws」とし、リクエストを受け取るSearchAddressクラスを登録している。
@javax.ws.rs.ApplicationPath("ws") public class ApplicationConfig extends Application{ public Set<Class<?>> getClasses(){ HashSet<Class<?>> set = new HashSet<Class<?>>(); set.add(SearchAddress.class); return set; } }