1. Feignとは
Feignとは非常にシンプルなJavaのHTTPクライアント用ライブラリです。
githubで2745の評価(2018/4/2時点)ので、かなり使われているライブラリのようです。
とても短いコードでHTTPリクエストを発行することができます。Feign
を利用する際の流れは以下のような感じです。
デメリットとしてはテキストベースのHTTPリクエストしか発行できない、HTTPレスポンスはBODYにしかアクセスできない等の制限があります。
(2018/4/8 追記)
Feign
のデフォルトでは対応していませんが、別モジュールや独自の実装でバイナリのHTTPリクエストを扱えます。
2. ライブラリの用意
Fegin
は利用するコンポーネントを入れ替えることが可能な構成になっています。
今回はOkHttp
、Jackson
、Logback
を利用する構成とします。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>9.5.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>9.5.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>9.5.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
3. APIのインターフェースを定義
呼び出したいAPIをインターフェースとして定義します。
-
@RequestLine
で発行したいHTTPリクエストのHTTPメソッドとパスを設定 - パスの一部を
{変数名}
でパラメータを参照できる - パラメータはメソッド引数に
@Param
を付与して定義 - HTTPヘッダに設定したい内容があれば
@Headers
で設定
今回は以下に示すAPIを呼び出す場合を考えてみたいと思います。
項番 | パス | HTTPメソッド | 説明 |
---|---|---|---|
1 | /todos | GET | 全てのTODOを取得する |
2 | /todos/{todoId} | GET | todoIdで指定されたTODOを取得する |
3 | /todos | POST | 送信されたデータでTODOを作成する コンテントタイプは application/json とする |
4 | /todos/{todoId} | PUT | todoIdで指定されたTODOを更新する コンテントタイプは application/json とする |
5 | /todos/{todoId} | DELETE | todoIdで指定されたTODOを削除する |
APIのエンドポイント(URL)はhttp://localhost:8090/todo-rest/api/v1
とします。
package com.example.feign.demo;
import java.util.List;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
public interface TodoApi {
@RequestLine("GET /todos")
List<Todo> findAll();
@RequestLine("GET /todos/{todoId}")
Todo getTodo(@Param("todoId") String todoId);
@RequestLine("POST /todos")
@Headers("Content-Type: application/json")
Todo createTodo(Todo todo);
@RequestLine("PUT /todos/{todoId}")
@Headers("Content-Type: application/json")
Todo updateTodo(@Param("todoId") String todoId, Todo todo);
@RequestLine("DELETE /todos/{todoId}")
void deleteTodo(@Param("todoId") String todoId);
}
4. FeignでAPIのインスタンスを生成して実行
APIのインターフェースは定義しましたが、その実装クラスは定義していません。APIのインスタンスはFeign
で生成します。
APIの呼び出しはインターフェースのメソッドを実行するだけです。
package com.example.feign.demo;
import java.util.List;
import feign.Feign;
import feign.Logger;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;
public class TodoApiDemo {
public static void main(String[] args) {
// 1. create instance of api interface with feign
TodoApi todoApi = Feign.builder() // builder call at first
.client(new OkHttpClient()) // use OkHttpClient of feign
.encoder(new JacksonEncoder()) // use Jackson of feign
.decoder(new JacksonDecoder()) // use Jackson of feign
.logger(new Slf4jLogger()) // use Slf4j of feign
.logLevel(Logger.Level.FULL) // setting log level to most detail
.target(TodoApi.class,
"http://localhost:8090/todo-rest/api/v1");
// 2. call api [GET /todos]
List<Todo> todos = todoApi.findAll();
System.out.println(todos);
}
}
Builderパターンでインスタンスを生成するため、設定のための専用メソッドが用意されています。
-
client
メソッド - HTTPを発行する際に利用するクライアントを指定する
-
ApacheHttpClient
、OkHttpClient
等がある -
encoder
メソッド - HTTPリクエストのBODYにデータを設定する際のコンバート処理(シリアライズ)で利用するオブジェクトを指定する
-
JacksonEncoder
、GsonEncoder
等がある -
decoder
メソッド - HTTPレスポンスのBODYのデータをJavaのオブジェクトにコンバート処理(デシリアライズ)で利用するオブジェクトを指定する。
-
JacksonDecoder
、GsonDecoder
等がある -
logger
メソッド - ロガーを指定する
-
Feign
はログ出力にSLF4J
を利用しているため、ログ出力の実装はSLF4J
に対応しているものであれば差し替え可能である。 -
logLevel
メソッド - ログレベルを指定する
- 最も詳細なログを出力したい場合、
Logger.Level.FULL
を指定する -
target
メソッド - APIのインスタンス化に必要となる情報を指定する
- 第1引数 : 3. APIのインターフェースを定義で定義したAPIのインターフェースの
class
- 第2引数 : 実際にアクセスするAPIのエンドポイント(URL)
5. APIテストのサンプル(おまけ)
APIのインスタンスのメソッドを呼び出すだけなので、呼び出し方法に難しいことがありませんが、参考までにサンプルのAPIのテストを記載します。
Feign
の使い方を目的としているため、テストの内容自体は適当です。
package com.example.feign.demo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import feign.Feign;
import feign.Logger;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;
public class TodoApiTest {
// test api
TodoApi todoApi;
private <T> T factory(Class<T> apiType, String url) {
return Feign.builder() // builder call at first
.client(new OkHttpClient()) // use OkHttpClient of feign
.encoder(new JacksonEncoder()) // use Jackson of feign
.decoder(new JacksonDecoder()) // use Jackson of feign
.logger(new Slf4jLogger()) // use Slf4j
.logLevel(Logger.Level.FULL) // setting log level to most detail
.target(apiType, url);
}
@Before
public void setUp() throws Exception {
todoApi = factory(TodoApi.class,
"http://localhost:8090/todo-rest/api/v1");
}
@Test
public void testFindAll() {
List<Todo> todos = todoApi.findAll();
System.out.println(todos);
assertThat(todos.size(), is(2));
}
@Test
public void testCreateTodo() {
Todo todo = new Todo();
todo.setTodoTitle("hello");
Todo registeredTodo = todoApi.createTodo(todo);
System.out.println(registeredTodo);
}
@Test
public void testGetTodo() {
String todoId = "36e06987-ef33-436a-a2c6-d215096ee902";
Todo todo = todoApi.getTodo(todoId);
System.out.println(todo);
assertThat(todo.getTodoId(), is(todoId));
}
@Test
public void testUpdateTodo() {
String todoId = "36e06987-ef33-436a-a2c6-d215096ee902";
String title = "update......";
Todo todo = new Todo();
todo.setTodoId(todoId);
todo.setTodoTitle(title);
todo.setCreatedAt(new Date());
Todo updatedTodo = todoApi.updateTodo(todoId, todo);
System.out.println(updatedTodo);
assertThat(updatedTodo.getTodoId(), is(todoId));
assertThat(updatedTodo.getTodoTitle(), is(title));
assertThat(updatedTodo.isFinished(), is(true));
}
@Test
public void testDeleteTodo() {
String todoId = "36e06987-ef33-436a-a2c6-d215096ee902";
todoApi.deleteTodo(todoId);
System.out.println("ok");
}
}
6. さいごに
今回はJavaのHTTPクライアント用ライブラリであるFeign
について説明しました。
ほぼAPIのインターフェースを定義するだけで実装が済んでしまうので、とても簡単に使えるライブラリではないでしょうか。
なお、今回は説明していませんがFeign
はJAXB
やJAX-RS
にも対応しています。
テキストベースのHTTPリクエストしか扱えないという制約はありますが、その影響がないAPIでは積極的に利用していきたいと思いました。