- Feignの概要
1.1 基本概念 FeignはNetflixが開発した、宣言型のREST APIクライアントです。Spring Cloudでは、リバウンディング(Ribbon)とフォールバック(Hystrix)を統合し、微サービス間通信を簡素化しています。従来の手動設定や重複コードの削減により、アプリケーション開発の負担を大幅に軽減します。この仕組みは、Spring Bootが従来のSpringフレームワークを簡略化したのと同様に、開発者の生産性向上を目的としています。
1.2 FeignとRibbonの関係
Ribbonは、クライアント側でロードバランシングを行うためのツールであり、HTTP/TCPベースのリクエスト処理をサポートします。一方、Feignはその上位レイヤーとして、インターフェース定義とアノテーションによってリモートサービスの呼び出しを宣言的に実現します。これにより、RestTemplateなどでの手動リクエスト構築が不要となり、まるでローカルメソッド呼び出しのように自然なコード記述が可能になります。
1.3 ロードバランシングの自動適用
Feignは内部的にRibbonを統合しており、追加の依存関係やRestTemplateの登録は不要です。サービスディスカバリ(Eurekaなど)との連携も自動で処理されるため、開発者はシンプルな設定のみで分散環境での通信が実現できます。
-
主な機能 Feignは、微サービス間の通信を宣言型で記述できるWebサービスクライアントです。コントローラからサービスを呼び出すように、他のサービスのエンドポイントを簡単に呼び出せます。Spring Cloudでは、Eurekaと連携して自動的なロードバランシングを提供します。
-
動作確認手順
3.1 サービスレジストリの構築 依存関係の追加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
application.yml設定:
server:
port: 8080
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
起動クラス:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
ブラウザで http://localhost:8080 にアクセスすると、レジストリ管理画面が表示されます。
3.2 サービスプロバイダの作成 依存関係:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml:
server:
port: ${port:8010}
spring:
application:
name: provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
APIコントローラ:
@RestController
public class HelloController {
@Value("${server.port}")
private String port;
@GetMapping("/hi")
public String hi() {
return "hi~ my port === " + port;
}
@GetMapping("/hiWithTimeOut")
public String hiWithTimeOut() throws InterruptedException {
Thread.sleep(10000);
return "hi~ my port === " + port;
}
}
2つの異なるポート(8010, 8011)でインスタンスを起動することで、ロードバランシングの動作確認が可能です。
3.3 サービスコンシューマの実装 Maven依存関係:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
application.yml:
server:
port: 8082
spring:
application:
name: consumer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
feign:
hystrix:
enabled: true
client:
config:
provider:
loggerLevel: FULL
connectTimeout: 2000
readTimeout: 5000
default:
connectTimeout: 2000
readTimeout: 3000
logging:
level:
com.ldx.consumer.service.HelloService: debug
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 50000
起動クラス:
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
Feignインタフェース:
@FeignClient(name = "provider", fallback = HelloFallbackImpl.class)
public interface HelloService {
@GetMapping("/hi")
String hi();
@GetMapping("/hiWithTimeOut")
String hiWithTimeOut();
}
フォールバック実装:
@Component
public class HelloFallbackImpl implements HelloService {
@Override
public String hi() {
return "リモートサービスが利用不可です。後でもう一度お試しください。";
}
@Override
public String hiWithTimeOut() {
return "リクエストがタイムアウトしました。";
}
}
コントローラ:
@RestController
public class HelloController {
@Resource
private HelloService helloService;
@GetMapping("/hi")
public String hi() {
return helloService.hi();
}
@GetMapping("/hiWithTimeOut")
public String hiWithTimeOut() {
return helloService.hiWithTimeOut();
}
}
サービスプロバイダを停止した状態でリクエストを送信すると、フォールバック処理が動作し、期待通りの応答が返却されます。
- Hystrixとの統合
Feignは、Hystrixによるフォールバック機能を標準で提供します。
@FeignClientのfallback属性に実装クラスを指定することで、ネットワーク障害時のデフォルト応答を定義できます。また、fallbackFactoryを使用すれば、例外の詳細情報を取得でき、より精密なエラーハンドリングが可能です。
※ Spring Cloud Hoxton以降のバージョンでは、Hystrixのサポートが非推奨となっています。代替としてResilience4Jが推奨されています。
- カスタムエラーデコーダー
ErrorDecoderインターフェースを実装することで、レスポンスステータスコードに応じたカスタムエラー処理を実現できます。例えば、4xxや5xxステータスに対して特定のメッセージを返すような処理が可能です。
@Configuration
public class FeginErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
ServiceException ex = new ServiceException();
ex.setMethod(methodKey);
if (response.status() >= 400 && response.status() <= 499) {
ex.setCode(response.status());
ex.setMessage("パラメータまたはページが無効です");
} else if (response.status() >= 500 && response.status() <= 599) {
ex.setCode(response.status());
ex.setMessage("サーバーエラーが発生しました");
}
return ex;
}
}
- 拡張機能
6.1 HTTPクライアントの切り替え
FeignのデフォルトのURLConnectionではなく、Apache HttpClientを導入することで、接続プールやタイムアウト設定の柔軟な制御が可能になります。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>11.0</version>
</dependency>
feign:
httpclient:
enabled: true
6.2 リクエスト前処理
RequestInterceptorを実装することで、リクエストヘッダーの付加やボディの変更が行えます。
@Component
public class TokenRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
System.out.println("呼び出しメソッド: " + template.method() + ", URL: " + template.url());
}
}
6.3 GZIP圧縮の有効化 リクエスト・レスポンスの圧縮により通信コストを削減できます。
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
6.4 ログレベルの設定
ログ出力には、Logger.Levelの設定が必要です。
logging:
level:
com.ldx.consumer.service.HelloService: DEBUG
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
- @FeignClientの主な設定項目 | 属性名 | デフォルト値 | 説明 | 備考 | |---|---|---|---| | name | 空文字 | 呼び出し対象サービス名 | valueと同一 | | url | 空文字 | 直接アクセスするフルパス | Ribbonを使わない場合に使用 | | fallback | void.class | 失敗時のフォールバック実装 | Hystrix依存 | | fallbackFactory | void.class | 例外情報付きのフォールバック | Throwableを引数に受け取れる | | contextId | 空文字 | Bean名の識別子(複数クライアント時必須) | 衝突回避に使用 | | qualifier | なし | 注入時の識別子 | primary=false時に必須 |
7.1 contextIdの役割
同じサービス名を持つ複数の@FeignClientがある場合、contextIdを明示的に設定することで、ビーン名の衝突を回避できます。例:
@FeignClient(name = "user", contextId = "userClient1")
public interface UserClient1 { ... }
7.2 qualifierの使い方
primary=falseの場合、@Qualifierを使って特定のビーンを注入します。
@Autowired
@Qualifier("userClient")
private UserClient userClient;