22
17

More than 5 years have passed since last update.

Feign(OpenFeign)を使ってアノテーションだけでAPIクライアントを実装する

Last updated at Posted at 2018-03-06

(久しぶりにJavaの話 :relaxed: )

はじめに

みなさん複数のサーバで構成されているサービスってどのように通信してますかね。

今私が担当しているプロジェクトではSpringBootを使って、RESTっぽいAPIを各サーバ用意しています。
SpringBootでAPIを呼ぶときにはよくRestTemplateが使われると思うのですが、
パスとリクエスト・レスポンスモデルの対応を仕様書等から引っ張ってきたり、ソースコードが見られるならControllerから判断してみたりと、何かとクライアント側では考慮することが多いです。(経験上

Controllerと同じようにクライアント側も書けたらものすごい楽なのに。。。」
「さらにControllerと同じインターフェースが使えたらメンテナンスももっともっと楽になるのに。。。」

それFeign(OpenFeign)を使えばできますよ :sunglasses:

Feign(OpenFeign)とは

Feign makes writing java http clients easier

FeignはRetrofitJAXRS-2.0WebSocketからインスパイアされたHTTPクライアントためのフレームワークです。上記のように、より簡潔に書けることをウリにしています。

SpringBootで使用するにはSpringCloudの中のSpring Cloud OpenFeignを使います。

※ 昔はFeignという名前のプロジェクトでしたが、Spring CloudのFinchley.M7からはOpenFeignという別のプロジェクトが使われているので注意(使い方は前の時と一緒です)
Spring Cloud Finchley.M7での変更内容

使い方

Livedoorお天気Webサービスを使って天気情報を取得してみます:partly_sunny:

Java9、Maven(3.5.2)、SpringBoot(2.0.0.RELEASE)で動かします。

Spring Cloud OpenFeignのインストール

pom.xmlはこんな感じ。
spring-cloud.versionに最新(2018/03/02時点)のFinchley.M7を指定します。
lombokは好みで

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.ofc</groupId>
    <artifactId>open-feign-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>open-feign-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>9</java.version>
        <spring-cloud.version>Finchley.M7</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

モデルの実装

簡略化するために、タイトルとディスクリプションのみを扱います。

WeatherInfo.java
package com.example.ofc.model;

import lombok.Data;

@Data
public class WeatherInfo {
    private String title;
    private WeatherDescription description;
}
WeatherDescription.java
package com.example.ofc.model;

import lombok.Data;

@Data
public class WeatherDescription {
    private String text;
    private String publicTime;
}

クライアントの実装(?)

実装といいつつアノテーションでインターフェースを宣言するだけです。

サーバ側の@RestControllerと対になるのが@FeignClientです。
@FeignClientには名前とベースパスとなるurlを渡します。
今回はハードコードしていますが、実際のコードではプロパティファイルに外出ししたほうが良いです。
そうすれば環境毎に違うパスを見に行くこともできます。

メソッドはコントローラの実装と同じように@RequestMapping@RequestParam等をつけて宣言します。

WeatherClient.java
package com.example.ofc.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.ofc.model.WeatherInfo;

@FeignClient(name = "weather", url = "weather.livedoor.com")
public interface WeatherClient {

    @RequestMapping(method = RequestMethod.GET, value = "/forecast/webservice/json/v1") 
    ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city);

}

起動クラス

上記のクライアントを@Autowiredしてrunの中でAPIを呼びます。(今回は東京を指定)
それ以外は普通の起動クラスです。

Application.java
package com.example.ofc;

import com.example.ofc.model.WeatherInfo;
import com.example.ofc.api.WeatherClient;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.http.ResponseEntity;

@SpringBootApplication
@EnableFeignClients
@EnableAutoConfiguration
@Slf4j
public class Application implements CommandLineRunner {

    @Autowired
    private WeatherClient weatherClient;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {
        // 東京都: 130010
        ResponseEntity<WeatherInfo> response = weatherClient.getWeatherInfo(130010L);
        log.info(response.getBody().toString());
    }
}

起動するとログに天気情報が出力されます。

.
.
.
2018-03-02 16:00:22.536  INFO 8604 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshScope': registering with JMX server as MBean [org.springframework.cloud.context.scope.refresh:name=refreshScope,type=RefreshScope]
2018-03-02 16:00:22.550  INFO 8604 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'configurationPropertiesRebinder': registering with JMX server as MBean [org.springframework.cloud.context.properties:name=configurationPropertiesRebinder,context=2098d877,type=ConfigurationPropertiesRebinder]
2018-03-02 16:00:22.582  INFO 8604 --- [           main] com.example.ofc.Application              : Started Application in 2.997 seconds (JVM running for 6.875)
2018-03-02 16:00:22.753  INFO 8604 --- [           main] com.example.ofc.Application              : WeatherInfo(title=東京都 東京 の天気, description=WeatherDescription(text= 日本付近は冬型の気圧配置
となっています。

【関東甲信地方】
 関東甲信地方はおおむね晴れていますが、長野県や関東地方北部の山沿い
では曇りで雪の降っている所があります。

 2日は、冬型の気圧配置は次第に緩み高気圧に覆われてくるため、おおむ
ね晴れますが、関東地方北部や長野県の山沿いでは、はじめ寒気の影響によ
り曇りで雪の降る所があるでしょう。

 3日は、高気圧に覆われるため、おおむね晴れる見込みです。

 関東近海では、2日はうねりを伴ってしける所があり、3日は波が高い所
があるでしょう。船舶は高波に注意してください。

【東京地方】
 2日から3日にかけて、晴れるでしょう。, publicTime=2018-03-02T10:40:00+0900))
 .
 .
 .

ほとんど実装せずにできちゃいました! :yum:

FeignClientとRestControllerで同一のインターフェースを使いたい場合

先ほどの例は外部のAPIでしたが、内部でのAPI通信のほうが多いのではないでしょうか。
はじめににも書きましたが、クライアント側のFeignClientとサーバ側のRestControllerで同一のインターフェースが使えたらいろいろと楽になりますよね。

そんなときには、クライアントではインターフェースを継承したFeignClientを、サーバではインターフェースを実装したRestControllerを作成すれば良いです。
すると、公開するのはインターフェース(とモデル)のみでよくなり、@ApiResponse等のアノテーションを使ってステータスコード等の情報も書けば仕様書もいらなくなります。(仕様書が欲しければSwaggerで自動作成する)

先ほどの天気情報を例にします。(import等は省略)

public interface WeatherApi {
    @RequestMapping(method = RequestMethod.GET, value = "/forecast/webservice/json/v1")
    ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city);
}
// urlの向き先は適当
@FeignClient(name = "weather", url="localhost:8888")
public interface WeatherApiClient extends WeatherApi {
}
@RestController
public class WeatherController implements WeatherApi {

    @Override
    ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city) {
      // 天気情報作成...
      return responseEntity;
    }
}

なんて簡単なんでしょう!:dizzy_face:

まとめ

Spring Cloud Open Feignを使ってApiクライアントを作成する方法を紹介しました。
実装量や可用性を考えたら積極的に使っていきたいですね。

今回のサンプルはこちら
https://github.com/totto357/open-feign-client-example

22
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
17