CS 정리

Feign 사용해보기

문쿼리 2025. 8. 11. 23:15

(발음은 페인)

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&notify_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, ... 등등 공통 규칙이 많아 질 수록 유리할 것