Spring Cloud OpenFeign
Netfix에서 개발한 HTTP 클라이언트 라이브러리를 Spring Cloud에서 통합한 선언적 HTTP 클라이언트 라이브러리로,
Spring Cloud에서 Open Feign을 스프링 MVC 어노테이션을 사용해 웹 서비스 클라이언트를 쉽게 작성할 수 있도록 통합함
OpenFeign은 마이크로서비스 아키텍처에서 특히 유용하며, 코드를 더 간결하고 유지보수하기 쉽게 만듦
특징
| 특징 | 설명 |
| 선언적 REST 클라이언트 | 인터페이스와 어노테이션만으로 HTTP API 클라이언트를 작성할 수 있음 |
| 스프링 MVC 어노테이션 지원 | @RequestMapping, @PathVariable, @RequestParam 등 스프링 MVC 어노테이션을 그대로 사용할 수 있음 |
| 유연한 설정 | 인터셉터, 디코더, 인코더 드을 커스터마이징 가능 |
| 로드 밸런싱 | Ribbon과 통합되어 클라이언트 사이드 로드 밸런싱을 지원함 |
| 서킷 브레이커 | Hystrix/Resilience4j와 통합해 장애 허용 패턴을 구현할 수 있음 |
외부 통신 클라이언트: Rest Template, WebClient, OpenFeign
- Spring Boot 환경에서 사용하는 외부 통신 클라이언트로는 RestTemplate, WebClient, OpenFeign를 이용함. 각각에 대해 비교
1. RestTemplate
- Spring 3.0부터 제공된 전통적인 HTTP 클라이언트로 동기식으로 데이터 통신이 처리되며 응답을 받을 때까지 블로킹 방식으로 응답을 기다리는 통신이 이뤄짐
2. WebClient
- WebFlux의 일부인 Webclien는 비동기적인 방식으로 HTTP 요청을 보내고 응답을 기다리지 않는 non-blocking 방식으로 통신이 이뤄짐
3. OpenFeign
- Spring Cloud의 일부인 OpenFeign는 선언적 REST Client로, 인터페이스와 어노테이션만으로 HTTP API 클라이언트를 작성하며 동기식으로 데이터 처리되며, 응답을 받을 때까지 블로킹 방식으로 응답을 기다리는 통신이 이뤄짐
| 특징 | RestTemplate | WebClient | OpenFeign |
| 구현 방식 | '명령형' 방식으로 HTTP 요청을 직접 구현 | '함수형' 방식으로 체이닝 구현 | '선언형' 방식으로 인터페이스만 정의 |
| 코드 복잡도 | URL, HTTP 메서드, 요청/응답 처리를 모두 직접 작성 | 메서드 체이닝으로 직관적 구현 | 어노테이션 기반으로 간단하게 정의 |
| 비동기 지원 | 동기-블로킹 방식만 지원 | 비동기-논블로킹 방식 지원 | 등기-블로킹 방식만 지원 |
| 유지보수성 | 코드가 길어지고 반복적인 작성 필요 | 모듈화된 코드로 유지보수 용이 | 인터페이스 수정만으로 변경 가능 |
| 테스트 용이성 | MockRestServiceServer 사용 필요 | WebTestClient로 쉽게 테스트 | 인터페이스 기반으로 쉽게 Mock 가능 |
| 오류 처리 | try-catch로 직접 처리 | onError()등으로 선언적 처리 | ErrorDecoder로 중앙 집중적 처리 가능 |
| 로드 밸런싱 | 별도 설정 필요 | LoadBalancerExchange 통합 | Ribbon과 통합되어 자동 지원 |
| 서킷브레이커 | 별도 구현 필요 | Resilience4j와 쉽게 통합 | Hystrix/Resilience4j와 쉽게 통합 |
1. 동기식 요청(Synchronous request) & 블로킹 요청(Blocking Request)
- 요청을 보내고 응답을 받을 때까지 블로킹되는 방식을 의미
2. 비동기식 요청(Asynchrouse Request) & 논 블로킹 요청(Non-Blocking Request)
- 요청을 보내고 응답을 받지 않는 논 블로킹 방식을 의미
OpenFeign 예시
선언형 방식으로 인터페이스 내에 메서드와 어노테이션을 기반으로 HTTP 요청에 대한 구현을 함
인터페이스의 파라미터, 리턴타입을 지정하고 URL만 매핑하면 외부 통신을 수행할 수 있음
@FeignClient(name = "user-service", url = "http://api.example.com")
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
OpenFeign과 관련된 주요 어노테이션
1. @EnableFeignClients
- 주로 메인 애플리케이션 클래스나 구성 클래스 내에 선언하며, @ComponentScan과 유사하게 작동하여 @FeignClient가 선언된 인터페이스들을 찾아내는 역할
- 찾아낸 구현체를 Spring Bean에 등록해 Open Feign 관련 자동 구성을 활성화
@EnableFeignClients 속성
| 속성 | 리턴 타입 | 설명 |
| value | Class<?><[]> | FeignClient를 스캔할 패키지들을 지지어 |
| basePackages | String[] | value와 동일한 역할을 하며 FeginClient를 스캔할 패키지 지정 |
| basePackageClasses | Class<?><[]> | 지정된 클래스들이 있는 패키지를 스캔 |
| defaultConfiguration | Class<?><[]> | 모든 Feign 클라이언트에 적용될 기본 구성 클래스 지정 |
| clients | String[] | 특정 FeignClient 클래스들만 등록하도록 명시적으로 지정 |
@EnableFeignClients 어노테이션 속성 예시
- @FeignClients가 선언된 스캔을 위해 basePackages 속성으로 패키지를 지정하거나 defaultConfiguration 속성으로 특정 클래스를 지정
- 기본적으로 @EnableFeignClients 속성 없이 선언하는 경우 모든 패키지나 클래스를 스캔함
@EnableFeignClients(
basePackages = "com.example.clients",
defaultConfiguration = DefaultFeignConfig.class
)
@SpringBootApplication
public class Application {
// ...
}
2. FeignClient
- HTTP 클라이언트를 생성하기 위한 인터페이스를 선언하는 어노테이션임. 이 어노테이션을 사용하면 REST API를 호출하는 인터페이스를 쉽게 정의할 수 있음
- 선언만으로 REST 클라이언트를 정의하며, Spring Runtime 단계에서 해당 인터페이스의 구현체를 자동으로 생성
- @GetMapping, @PostMapping 등의 Spring MVC 어노테이션을 그대로 사용할 수 있음
속성
| 속성 | 리턴 타입 | default value | 설명 |
| name | String | - | FeignClient의 이름을 지정 |
| url | String | "" | 대상 서버의 URL을 지정 |
| configuration | Class<?>[] | FeignClientsConfiguration.class | FeignClient에 대한 사용자 정의 구성 클래스를 지정 |
| fallback | Class<?>[] | void.class | 장애 발생 시 대체할 구현 클래스 지정함 |
| fallbackFactory | Class<?> | void.class | 동적 fallback 객체 생성을 위한 Factory 클래스를 지정함 |
| path | String | "" | 모든 메서드에 적용될 공통 prefix 경로를 지정함 |
| decode404 | boolean | false | 404 응답을 에러로 처리할지 여부를 지정 |
| primary | boolean | true | 동일한 타입의 여러 빈이 있을 때 우선 선택할지 여부를 지정함 |
예시
- name 속성: name="user-service": Feign 클라이언트의 이름을 지정
- url 속성: url="http://api.example.com": 요청을 보낼 대상 서버의 기본 URL을 지정
- @GetMapping("/users/{id}"): HTTP GET 요청을 위한 메서드로, 특정 사용자 정보를 조회
- @PostMapping("/users"): HTTP POST 요청을 위한 메서드로, 새로운 사용자를 생성
@FeignClient(name = "user-service", url = "http://api.example.com")
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
}
Spring Cloud OpenFeign 환경설정
의존성 추가
- Spring Cloud OpenFeign을 이용하기 위한 "spring-cloud-starter-openfeign" 라이브러리를 추가해야 함
- 또한, org.springframework.cloud:spring-cloud-dependencies도 함께 추가해야 함. 이를 추가하는 이유는 OpenFeign의 경우 Spring Cloud의 다른 컴포넌트들과 함께 작동하기 위해서 추가해야 함
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.6'
id 'io.spring.dependency-management' version '1.1.6'
}
ext {
set('springCloudVer', "2023.0.3")
}
dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-openfeign:4.1.3"
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVer}"
}
}
MainApplication.java 설정
- @FeignClient 어노테이션이 적용된 인터페이스들을 스캔하여 빈으로 등록
- @FeignClient 어노테이션의 속성 basPackages 설정해 특정 패키지만 스캔하도록 basePackages 속성을 통해 스캔 범위를 지정할 수 있음
package com.main.example
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients // FeignClient 사용을 선언합니다.
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
Service.java(interface) 설정
- Spring Cloud OpenFeign을 사용해 "https://jsonplaceholder.typicode.com"와의 외부 통신을 위해 interface를 구성
- 해당 서비스 페이지에서 함수 구성 및 통신을 함께 진행
package com.main.example.service;
import com.adjh.springbootexternalnetwork.dto.PostResponseDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
* json placeholder 외부 통신을 하는 예시
*
* @author : example
* @fileName : OpenFeignService
* @since : 30/01/26
*/
@FeignClient(
name = "json placeholder",
url = "<https://jsonplaceholder.typicode.com>"
)
public interface OpenFeignService {
@GetMapping("/posts")
List getPosts();
@GetMapping("/posts/{id}")
PostResponseDto getPostById(@PathVariable("id") String id);
}
추가 비스니스 로직 처리: class
- OpenFeignService 인터페이스의 인스터스를 생성해 해당 서비스의 값을 받아서 추가적인 비즈니스 로직을 구성
- getFilterPosts()의 경우는 게시물 id가 30 초과인 경우로 필터링을 적용해 비즈니스 로직 처리
- getFilterBodySummary() 경우 body값이 100자가 초과되는 경우 " … "처리를 수행하며, 초과되지 않는 경우는 기존의 값을 그대로 리턴
import com.adjh.springbootexternalnetwork.dto.PostResponseDto;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* OpenFeign 통신을 통해 가져온 응답 값을 통해 비즈니스 로직을 처리합니다.
*/
@Service
public class OpenFeignBusinessService {
private final OpenFeignService openFeignService;
public OpenFeignBusinessService(OpenFeignService openFeignService) {
this.openFeignService = openFeignService;
}
// 특정 조건으로 게시물 필터링
public List<PostResponseDto> getFilteredPosts() {
List<PostResponseDto> posts = openFeignService.getPosts();
return posts.stream()
.filter(post -> post.getId() > 30)
.collect(Collectors.toList());
}
// 게시물 데이터 가공
public PostResponseDto getFilterBodySummary(String id) {
PostResponseDto post = openFeignService.getPostById(id);
// 컨텐츠 요약 추가
if (post.getBody().length() > 100) {
post.setBody(post.getBody().substring(0, 100) + "...");
} else {
post.setBody(post.getBody());
}
// 추가 데이터 처리 로직
return post;
}
}
xxController.java 설정
- 특정 엔트포인트를 OpenFeign을 이용한 인터페이스 서비스를 호출해 반환해주는 Controller
import com.adjh.springbootexternalnetwork.service.OpenFeignService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* OpenFeign 테스트 Controller
*/
@RestController
@RequestMapping("api/v1/openFeign")
public class OpenFeignController {
private final OpenFeignService openFeignService;
public OpenFeignController(OpenFeignService openFeignService) {
this.openFeignService = openFeignService;
}
/**
* 게시물을 전체를 조회합니다.
*
* @return
*/
@PostMapping("/getPosts")
public ResponseEntity<Object> getPosts() {
Object resultObj = openFeignService.getPosts();
return new ResponseEntity<>(resultObj, HttpStatus.OK);
}
/**
* 특정 게시물을 조회합니다.
*
* @param postId
* @return
*/
@PostMapping("/getPost/{postId}")
public ResponseEntity<Object> getPostById(@PathVariable String postId) {
Object resultObj = openFeignService.getPostById(postId);
return new ResponseEntity<>(resultObj, HttpStatus.OK);
}
}
Spring Cloud OpenFeign 활용 : Header 설정
Spring Cloud OpenFeign Header 설정
- OpenFeign을 통해 데이터 통신을 수행할 때, 추가적인 Header를 보내야 하는 경우 존재.
@RequestHeader 사용하는 방법
- 메서드 별로 직접 헤더를 지정하는 방식을 의미. 이는 각 API호출마다 다른 헤더 값이 필요한 경우에 유용함
- @RequestHeader 어노테이션을 통해 메서드 파라미터로 헤더값을 전달할 수 있음. 인증 토근과 같인 동적으로 변하는 헤더값을 처리하는 데 적합
/**
* json placeholder 외부 통신을 하는 예시
*
*/
@FeignClient(name = "json-placeholder", url = "<https://jsonplaceholder.typicode.com>")
public interface OpenFeignService {
@GetMapping("/todos")
List getTodosAddHeader(@RequestHeader("Authorization") String token);
@GetMapping("/todos/{id}")
PostResponseDto getTodoAddHeader(
@RequestHeader("Authorization") String authToken,
@RequestHeader("x-refresh-token") String refreshToken,
@PathVariable("id") String id);
}
OpenFeignController
- 두 개의 엔드포인트는 동일하게 @RequestHeader를 클라이언트로부터 받아오도록 구성
@RestController
@RequestMapping("api/v1/openFeign")
public class OpenFeignController {
private final OpenFeignService openFeignService;
private final OpenFeignBusinessService openFeignBusinessService;
public OpenFeignController(OpenFeignService openFeignService, OpenFeignBusinessService openFeignBusinessService) {
this.openFeignService = openFeignService;
this.openFeignBusinessService = openFeignBusinessService;
}
/**
* 게시물을 전체를 조회합니다.
*
* @return
*/
@PostMapping("/getTodos")
public ResponseEntity<Object> getPostsAddHeader(@RequestHeader("Authorization") String token) {
Object resultObj = openFeignService.getTodosAddHeader(token);
return new ResponseEntity<>(resultObj, HttpStatus.OK);
}
/**
* 특정 게시물을 조회합니다.
*
* @param postId
* @return
*/
@PostMapping("/getTodo/{postId}")
public ResponseEntity<Object> getPostByIdAddHeader(
@RequestHeader("Authorization") String token,
@RequestHeader("x-refresh-token") String refreshToken,
@PathVariable String postId) {
Object resultObj = openFeignService.getTodoAddHeader(token, refreshToken, postId);
return new ResponseEntity<>(resultObj, HttpStatus.OK);
}
}'SpringBoot' 카테고리의 다른 글
| [SpringBoot] Spring Cloud 이해하기 (0) | 2026.01.24 |
|---|