(발음은 페인)
Feign은 Java에서 HTTP 클라이언트를 선언형(Declarative)으로 작성할 수 있게 해주는 라이브러리
- 주로 Spring Cloud에서 Spring Cloud OpenFeign 형태로 사용
- REST API 호출을 인터페이스로 선언하면, 런타임 시 구현체를 자동 생성
- API 호출 시 HTTP 요청 생성, 전송, 응답 파싱 과정을 프록시 객체가 대신 처리
java 8 이상, spring cloud와 연계하기 위해선 java 17이상
기존 미사용 코드 환경
- content(XML)을 만들어 전송해야 함
- sign = base64(MD5(content + keyValue))를 전송 직전에 생성
- 공통 파라미터: sign_type=MD5¬ify_type=ORDER_CREATE&input_charset=UTF-8
Sign 유틸 클래스
public class SignUtil {
public static String generateSign(String contentXml, String key) {
try {
var md = java.security.MessageDigest.getInstance("MD5");
byte[] md5 = md.digest((contentXml + key).getBytes(java.nio.charset.StandardCharsets.UTF_8));
return java.util.Base64.getEncoder().encodeToString(md5);
} catch (Exception e) {
throw new IllegalStateException("Failed to generate sign", e);
}
}
}
요청 정보 설정 및 전송
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.client.RestTemplate;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class ClientWithoutFeign {
private final RestTemplate restTemplate = new RestTemplate();
private final String baseUrl = "https://request.com/api";
private final String customer = "CUSTOMER_ID";
private final String keyValue = "SECRET_KEY";
private String toXml(ShipmentRequestData dto) {
throw new UnsupportedOperationException("Implement XML serialization");
}
public String orderCreate(ShipmentRequestData dto) {
String contentXml = toXml(dto);
String sign = SignUtil.generateSign(contentXml, keyValue);
String url = UriComponentsBuilder
.fromHttpUrl(baseUrl + "/" + customer + "/receive")
.queryParam("sign_type", "MD5")
.queryParam("notify_type", "ORDER_CREATE")
.queryParam("input_charset", "UTF-8")
.queryParam("sign", sign)
.queryParam("content", URLEncoder.encode(contentXml, StandardCharsets.UTF_8))
.toUriString();
return restTemplate.getForObject(url, String.class);
}
}
- 직접 URL/파라미터/인코딩 조립
- 전송 로직(헤더, 인증, 로깅 등)도 직접 매번 작성
- 테스트 시, RestTemplate를 Mocking 해야 함
- 호출부가 늘수록 중복 코드 증가
Feign을 사용한 코드
의존성
- spring-cloud-starter-openfeign (Spring Cloud)
- spring-boot-starter-web
dependencies {
...
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
애플리케이션 어노테이션 추가
@SpringBootApplication
@EnableFeignClients
public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
Feign 인터셉터
import feign.RequestInterceptor;
import feign.RequestTemplate;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class FeignInterceptor implements RequestInterceptor {
private final String keyValue;
public FeignInterceptor(String keyValue) {
this.keyValue = keyValue;
}
@Override
public void apply(RequestTemplate template) {
String content = template.queries().getOrDefault("content", java.util.List.of())
.stream().findFirst().orElse("");
String sign = SignUtil.generateSign(content, keyValue);
template.query("sign_type", "MD5");
template.query("notify_type", "ORDER_CREATE");
template.query("input_charset", "UTF-8");
template.query("sign", sign);
template.query("content", URLEncoder.encode(content, StandardCharsets.UTF_8));
}
}
Feign 설정 빈 등록
import org.springframework.context.annotation.Bean;
public class FeignConfig {
@Bean
public FeignInterceptor FeignInterceptor() {
return new FeignInterceptor("SECRET_KEY");
}
}
Feign Client 인터페이스
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(
name = "client",
url = "${api.base-url}", // https://request/api
configuration = FeignConfig.class
)
public interface FeignClient {
@GetMapping("/{customer}/receive")
String orderCreate(
@org.springframework.web.bind.annotation.PathVariable("customer") String customer,
@RequestParam("content") String content
);
}
서비스 사용부
import org.springframework.stereotype.Service;
@Service
public class ServiceWithFeign {
private final FeignClient client;
public ServiceWithFeign(FeignClient client) {
this.client = client;
}
private String toXml(ShipmentRequestData dto) {
throw new UnsupportedOperationException("Implement XML serialization");
}
public String orderCreate(ShipmentRequestData dto, String customer) {
String contentXml = toXml(dto);
return client.orderCreate(customer, contentXml);
}
}
- 인터페이스만으로 HTTP 호출 선언
- 인터셉터에서 공통 로직(서명/인증/로깅) 자동 주입
- 테스트 시, LotteFeignClient를 Stub/Mock 인터페이스로 교체 쉬움
- 호출부는 메서드 호출처럼 간결
결론!
서명·헤더·로그·리트라이 등 공통 규칙이 많은 프로젝트일수록 Feign + Interceptor가 유지보수/테스트/안정성 면에서 유리
해당 예시에서는 OrderCreate만 사용하였으나, Delete, Update, findAll, ... 등등 공통 규칙이 많아 질 수록 유리할 것
'CS 정리' 카테고리의 다른 글
| 동기(Synchronous) & 비동기(Asynchronous) (0) | 2025.09.03 |
|---|---|
| 블로킹(Blocking) & 논블로킹(Non-Blocking) (0) | 2025.09.02 |
| Java + Kotlin 혼용 개발 (0) | 2025.07.31 |
| BunkerWeb, 도입 시 겪은 이슈: HTTP/2 프로토콜 (0) | 2025.07.29 |
| HTTPS(HyperText Transfer Protocol + SSL/TLS) (0) | 2025.07.23 |