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;
}
}


