私たちはシステムをサブシステム・マイクロサービスに分割する方針を採用しているので、サブシステム間の通信、Web API を呼び出す処理を開発することが多くあります。このような処理をJavaで開発する場合、何をつかってどう書いたらいいでしょうか。この記事では、弊社シナジーマーケティングのプロダクト開発のスタンダードな方法をご紹介します。
目次
Jerseyとは
ライブラリは Jersey を利用しています。Jersey ( https://jersey.java.net/) は、RESTful Web サービスとそのクライアントのJava API 標準規格であるJAX-RS API (http://jax-rs-spec.java.net/) のリファレンス実装です。執筆時点での最新バージョンは2.23.1です。以降、このバージョンで説明します。
クライアントの作成
最初は、RESTful Web API のクライアントを作成します。
1 2 3 4 |
import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; private Client client = ClientBuilder.newClient(); |
Client はスレッドセーフです。また、インスタンスを作成する際にコストがかかりますので、システム内でむやみにインスタンスを生成せずに共有することをお勧めします。
GET メソッドの実行
GET は以下のように実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import javax.ws.rs.client.WebTarget; ... WebTarget target = client.target("https://api.example.com") .path("/oauth/access_token") .queryParam("token", "shortAccessToken"); try { String result = target.request().get(String.class); } catch (BadRequestException e) { logger.error("response=" + e.getResponse().readEntity(String.class), e); throw e; } |
まず、Clientのtarget メソッドでWebTarget オブジェクトを作成します。引数には、アクセス先URLの基本部分を指定します。
1 |
WebTarget target = client.target("https://api.example.com") |
続いて、pathメソッドでパスを指定します。これにより、targetは"https://api.example.com/oauth/access_token" にアクセスします。
1 |
.path("/oauth/access_token") |
クエリパラメータを指定する場合はqueryParam メソッドで指定します。
1 |
.queryParam("token", "shortAccessToken"); |
pathメソッドはパスをURLエンコードすることに注意してください。例えば、以下のように指定した場合、実際には"https://www.techscore.com/blog/2016/08/08/%E9%96.."のようなURLエンコードしたURLにアクセスします。
1 2 |
WebTarget target = client.target("https://www.techscore.com/blog") .path("/2016/08/08/開発新卒に捧ぐ、基本のアルゴリズムと計算量"); |
そのため、クエリパラメータはpathメソッドではなく、必ずqueryParamメソッドを利用して指定しましょう。pathに指定した場合はURLエンコードされてしまうので、意図した場所にアクセスしません。
1 2 3 |
//https://api.example.com/oauth/access_token?token=shortAccessToken にリクエストを投げない WebTarget target = client.target("https://api.example.com") .path("/oauth/access_token?token=shortAccessToken") ; |
また、queryParamメソッドもパラメータの値をURLエンコードするので、メソッドに渡す前にエンコードをする必要はありません。
リクエストを送るには、まずWebTargetのrequestメソッドを実行し、HTTPリクエストを起動するオブジェクト(Invocation.Builder)を作成します。
Invocation.Builderのget メソッドで、HTTPリクエストGET を送信します。引数に String.class を指定することにより、HTTP リクエストが成功した(HTTP コード 200のレスポンスが返った)場合にレスポンスのボディ部の値を文字列で取得することができます。
1 |
String result = target.request().get(String.class); |
HTTPリクエストが失敗した(400番台/500番台のステータスコードが返った)場合、BadRequestException 例外を投げます。
1 2 3 4 5 6 7 |
try{ ... } catch (BadRequestException e) { // 例外よりレスポンスの内容やステータスコードを確認可能 logger.error("response=" + e.getResponse().readEntity(String.class), e); throw e; } |
POST メソッドの実行
POSTメソッドにより、ボディ部にデータを指定して送信する場合は、以下のようにEntityオブジェクトを利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; ... Entity<Form> entity = Entity.entity(new Form().param("name", name).param("subtype", "CUSTOM"), MediaType.APPLICATION_FORM_URLENCODED_TYPE); try { String result = client.target("https://api.example.com") .path("/path_to_post_method") .request() .post(entity, String.class); } catch (BadRequestException e) { logger.error("response=" + e.getResponse().readEntity(String.class), e); throw e; } |
まず、ボディ部を表すEntityオブジェクトを作成します。
1 2 |
Entity<Form> entity = Entity.entity(new Form().param("name", name).param("subtype", "CUSTOM"), MediaType.APPLICATION_FORM_URLENCODED_TYPE); |
EntityでさまざまなMediaType のデータを送信できますが、パラメータを送信する場合は、Formを利用します。
その後、GETの場合と同じように Invocation.Builderを作成したあと、postメソッドを実行します。postメソッドの引数にEntityを指定します。
1 2 3 4 |
String result = client.target("https://api.example.com") .path("/path_to_post_method") .request() .post(entity, String.class); |
HTTPヘッダの指定
認証などのために、HTTP ヘッダーを指定したい場合は、以下のようにします。
1 2 3 4 5 6 7 8 |
import javax.ws.rs.core.MultivaluedMap; ... MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>(); headers.putSingle("Authorization", "Bearer " + accessToken); WebTarget target = client.target("https://api.example.com").path("/accounts"); String jsonString = target.request().headers(headers).get(String.class); |
まず、HTTPヘッダーで送信したい情報を格納したMultivaluedHashMapを作成します。WebTargetのrequest メソッドで作成したInvocation.Builder のheaders メソッドで作成したMultivaluedHashMapを指定すると、ヘッダーを送信することができます。
JSON のパース
最後にうけとったJSONのレスポンスをパースし、オブジェクトを作成する方法を簡単に紹介します。シナジーマーケティングでは、Jackson ( https://github.com/FasterXML/jackson )を利用しています。
例えば、レスポンスのJSON文字列をパースし、Map
1 2 3 4 5 6 7 8 9 10 11 |
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.type.TypeReference; ... String result = client.target("https://api.example.com") .path("/path_to_post_method") .request() .post(entity, String.class); ObjectMapper objectMapper = new ObjectMapper(); Map<String,Object> map = objectMapper.readValue(result, new TypeReference<Map<String, Object>>() {}); |
JSON をパースするにはObjectMappaerを利用します。ObjectMapper のreadValue メソッドで、第2引数に指定したクラスに変換することができます。Map
1 2 |
//これは、うまくいきません。。 Map<String,Object> map = objectMapper.readValue(result, Map.class); |
最後に
Java はRuby などと比較して標準ライブラリの機能が少ないため、「これがしたい」となったときに何を利用するのがよいか悩むことが多いです。歴史があるだけに検索するとライブラリも複数みつかったり、とても古い情報が上位にランキングされたり。ただ、現時点ではJavaでWeb API クライアントを開発するなら、わかりやすい流れるようなJAX-RS インタフェースを実装したJerseyが最適だ、と私は信じています。ぜひお試しください。