들어가며
배치 프로젝트에서 OpenFeign Client를 활용한 신규 기능을 개발하던 중, 흥미로운 현상을 발견했습니다. @Configuration 없이 @Bean만 선언했는데도 RequestInterceptor가 정상적으로 작동하는 것이었습니다. 이는 Spring의 일반적인 빈 등록 원칙과는 다른 모습이었습니다.
이번 글에서는 이 현상의 원인을 파헤치고, OpenFeign의 설정 메커니즘과 Spring의 빈 생성 주기에 대해 깊이 있게 다뤄보겠습니다. ( 학습을 위주로 다룬 글이라, 다소 오개념이 있을 수 있습니다! )
문제 상황
배치 프로젝트에는 이미 4개의 FeignClient가 운영 중이었고, 새로운 GitLab API 연동을 위한 클라이언트를 추가하게 되었습니다.

이 과정에서 특정 헤더를 추가하기 위해 다음과 같은 설정 클래스를 작성했습니다:
- Gitlab API 호출 구문
- API 를 호출 할 때 필요한 Header 값(토큰)을 추가하기 위한 인터셉터


public class GitlabFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
requestTemplate.header("PRIVATE-TOKEN", "GITLAB PRIVATE 토큰");
};
}
}
@FeignClient(
name = "gitlabCommitClient",
url = "http://172.20.0.8/api/v4",
configuration = GitlabFeignConfig.class
)
public interface GitlabCommitClient {
// API 메서드들...
}
여기서 놀라운 점은 RequestInterceptor를 내포한 GitlabFeignConfig 클래스에
@Configuration을 명시하지 않았는데도 @Bean 을 인식하고, 정상적으로 작동했다는 것입니다.
✅ OpenFeign이란?
본격적인 분석에 앞서 OpenFeign의 개념부터 정리하겠습니다.
선언형 HTTP 클라이언트
OpenFeign은 선언형(Declarative) HTTP 클라이언트입니다. Java 인터페이스에 어노테이션만 붙이면 HTTP 호출 코드를 자동으로 생성하고 실행해주는 라이브러리 입니다.
@FeignClient(name = "user-service", url = "http://localhost:8080")
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
이 인터페이스는 실제로 HTTP 요청을 보내는 프록시 객체로 변환되며, 내부에서 다음 과정을 자동 처리합니다:
- 요청 URL 조합
- HTTP 메서드 선택
- JSON 직렬화/역직렬화
- 인터셉터 처리
OpenFeign의 핵심 구성 요소
1. FeignContext
- 각 Feign Client의 설정을 독립적으로 관리하는 서브 컨텍스트
- ApplicationContext의 하위 컨텍스트로 동작
2. FeignClientFactoryBean
- 동적으로 인터페이스의 구현체(프록시)를 생성하는 팩토리
- 주의: 이 클래스는 @Autowired로 주입하면 안 됩니다
3. RequestInterceptor
- HTTP 요청 전에 실행되는 필터
- 공통 헤더 추가, 인증 토큰 주입, 로깅 등에 활용
📌 Spring의 빈 생성과 @Configuration
@Bean과 @Configuration의 관계
일반적으로 Spring에서 빈을 등록하려면 다음과 같이 작성합니다:
@Configuration
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
return new ServiceB(serviceA()); // 동일한 serviceA 빈 반환
}
}
@Configuration이 붙은 클래스는 CGLIB 프록시로 감싸져서, 메서드 호출을 가로채 싱글톤을 보장합니다.
@Configuration 없이 오직 @Bean만 사용하면?
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
}
이 경우:
- Spring은 이 클래스를 스캔조차 하지 않습니다
- @Bean은 완전히 무시됩니다
- 빈으로 등록되지 않습니다
✅ 그렇다면 왜 @Configuration 없이 @Bean 이 작동했을까?
FeignContext의 특수한 빈 등록 메커니즘
핵심은 Feign이 해당 설정 클래스를 수동으로 등록하기 때문입니다.
@FeignClient(configuration = GitlabFeignConfig.class)로 지정하면:
- Spring이 @EnableFeignClients를 처리
- FeignClientsRegistrar가 실행
- 각 FeignClient의 configuration 속성을 읽음
- 해당 클래스를 FeignContext(서브 컨텍스트)에 직접 등록
- 그 클래스의 @Bean 메서드를 호출해 빈 생성

// FeignClientsRegistrar 내부 코드 일부
private void registerClientConfiguration(
BeanDefinitionRegistry registry,
Object name,
Object configuration
) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition()
);
}
디버깅 포인트
이 과정을 확인하려면 아래의 클래스들에 브레이크포인트를 걸어 확인할 수 있습니다.
1. FeignClientsRegistrar.registerFeignClients()
- Feign Client의 메타데이터를 읽고 빈으로 등록하는 진입점
2. FeignClientFactoryBean.getObject()
- Feign Client 프록시를 생성하는 시점
3. FeignContext 내부
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
context.register(configClasses); // <<< 핵심!
context.refresh();
이 register() 호출이 @Configuration 없이도 클래스를 강제로 빈 설정으로 인식시키는 지점입니다.
❓ 따로 등록하는 이유는?
독립적인 설정 관리
두 개의 서로 다른 FeignClient가 있다고 가정해봅시다:
@FeignClient(name = "clientA")
public interface ClientA { }
@FeignClient(
name = "clientB",
configuration = MyFeignConfig.class
)
public interface ClientB { }
| 설정된 config | 없음 | MyFeignConfig 명시 |
| RequestInterceptor 적용 | ❌ | ✅ |
| 컨텍스트 | 기본 FeignContext | 별도 FeignContext |
| 헤더 포함 여부 | ❌ | ✅ |
각 Client는 완전히 독립된 설정을 가집니다.
그렇다면, @Configuration을 추가하면 문제가 될까?
동작은 원활하게 가능하겠지만, 실무에서 아무 생각 없이 @Configuration 으로 빈을 등록할 시 문제가 될 수 있습니다.
공유 빈의 위험성
@Configuration // 추가
public class MyFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> template.header("X-Auth", "1234");
}
}
@Configuration을 추가하면:
- 전체 Spring 컨텍스트에 등록될 수 있습니다
- 컴포넌트 스캔 대상이 되면 모든 FeignClient에 공유될 위험이 있습니다.
- Gitlab Token 을 위한 Header 값이 전역 Clinets 에게 할당되어 보내질 수 있습니다.
Spring Cloud OpenFeign 공식 권장사항
Feign configuration classes should not be component-scanned, else they'll apply to all clients.
실제로 OpenFeign 공식 권장사항에서도 설정 클래스는 아래의 내용을 권장합니다.
- 명시적으로만 적용
- @ComponentScan 대상에서 제외
- Client별로 독립적으로 관리
✅ 내부 configuration을 수동으로 돌렸을 때 디버깅으로 확인
1번 실험 : @FeignClient 내부에 특정 configuration을 명시하기
@FeignClient(
name = "gitlabCommitClient",
configuration = GitlabFeignConfig.class // GitlabCommitClient - configuration 명시
)
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
System.out.println("[DEBUG] 적용됨: " + requestTemplate.path());
requestTemplate.header("PRIVATE-TOKEN", "token");
};
}

2번 실험 : @FeignClient 내부에 configuration 누락
// TelegramClient - configuration 비어있음
@FeignClient(name = "telegramClient")

3번 실험 : 다른 @FeignClient 내부에 특정 configuration을 명시하기

정리
- @Configuration 없이 @Bean만 있어도 Feign에서는 작동합니다
- Feign이 FeignContext에 직접 등록하기 때문
- @FeignClient 내부에 원하는 configuration 클래스 파일을 심어줘야 함
- 하지만 @Bean은 반드시 명시해야 합니다
- @Bean 없으면 메서드가 호출되지 않음
- @Configuration을 함부로 추가하면 안 됩니다
- 전역 컨텍스트에 등록되어 모든 Client에 영향을 줄 수 있음
마치며
Spring과 OpenFeign의 빈 관리 메커니즘은 생각보다 복잡하고 정교합니다. @Configuration 없이도 작동하는 현상의 이면에는 FeignContext라는 독립적인 컨텍스트 구조가 있었고, 이를 통해 Client별 독립적인 설정이 가능해졌습니다.
빈 등록에 대한 신중함은 단순히 코드가 작동하는 것을 넘어, 예상치 못한 사이드 이펙트를 방지하고 유지보수하기 좋은 코드를 만드는 핵심입니다. 특히 실무에서는 기존 기능에 영향을 주지 않으면서 새로운 기능을 추가하는 것이 중요하기에, 이러한 내부 동작 원리에 대한 이해가 필수적이라는 것을 배웠습니다.
참고 자료
'[TIL]' 카테고리의 다른 글
| TIL - CDC로 향하는 가는 첫 번째 과정(1) : OracleDB 트랜잭션 로그(Redo log)를 읽어서 Kafka에 적재하기 (0) | 2025.01.10 |
|---|---|
| [ TIL ] RECOVER_YOUR_DATA : RDS 해킹 일지 (1) | 2024.11.13 |
| Kubernetes 분산 서버에 의한 Oauth2 소셜 인증 실패 및 극복 방법 (sticky session service 추가) (2) | 2024.10.13 |
| JWT 보안성 강화시키기 (Access Token, Refresh Token -RTR, BlackList 그리고 Redis) (0) | 2024.10.02 |
| ELK 에 대해서 알아보자! ( ElasticSearch - Logstash - Kibana ) (2) | 2024.09.22 |