SpringBoot

[SpringBoot] Spring Cloud OpenFeign

DoMyBestForDeveloper 2026. 1. 20. 18:15

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