DevBoi

[Spring] Fegin 잘 쓰기 본문

Develop/[Spring]

[Spring] Fegin 잘 쓰기

HiSmith 2023. 6. 14. 15:43
반응형

오늘은 Fegin에 대해서 잘 쓰는 법을 알아보자

우선 Fegin이 뭘까? 

 

Fegin

-Netfilx에서 개발하나, Http client binder이다.

-Fegin을 사용하면, 웹 서비스 클라이언트를 (외부 호출하는 앱을) 쉽게 작성할 수 있다.

-인터페이스, 어노테이션만 하면 끝이다.

 

1) Gradle 

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

 

2) Main class 추가

@SpringBootApplication
@EnableFeignClients
public class SmithStudyApplication {

	public static void main(String[] args) {
		SpringApplication.run(SmithStudyApplication.class, args);
	}

}

 

이제 테스트를 해보자

로컬에서 서비스 매쉬처럼 다른 컨트롤러로 보내보는 것이다.

물론, 실제로 쓸떄는 다른 서버 url을 호출하겠지만, 테스트만 해보자

@FeignClient(name="smithClient", url ="http://localhost:8080")
public interface ExampleClient{
  @GetMapping(value="/test3")
  void getUsers();
}
@RestController
public class BookController {

  @Autowired
  ExampleClient exampleClient;

  @RequestMapping("/test2")
  public void test(){
    exampleClient.getUsers();

  }
  @RequestMapping("/test3")
  public void test3(){
    System.out.println("test3!!");
  }

}

 

해당 feign을 이용해서, Test2를 호출하게 되면, Test3메소드까지 가는지를 테스트 하는것이다.

특이 점이 있다면, 내부에서 이동하는게 아니라, 다시 Request를 만들어서 나갈것이고,

처음 사용할때 혼돈이 있었던게, feign이 붙은 녀석을 어떻게 주입하지 였다.

답은, 아래에 있었다.

@EnableFeignClients(basePackages = "com.practice.demo")

 

해당 어노테이션은 이런 모양새로 생겼다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	/**
	 * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
	 * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
	 * {@code @ComponentScan(basePackages="org.my.pkg")}.
	 * @return the array of 'basePackages'.
	 */
	String[] value() default {};

 

해당 FeignClientRegistrar.class가 해당 빈의 구현체이고

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,

하단에 해당 로직에서, Feign관련 클래스들을 빈으로 등록을 해준다.

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

물론 이런걸 신경쓰지말고 편하게 써~ 라는 취지로 만들었겠지만... 심심해서 잠깐 얕게 봤다.

 

무튼 이제, Feign관련 Config를 생성해보자, 해당 Config를 생성하게 되면 주의 점이 몇가지 있다고한다.

Spring configuration만들듯이 만들면 망한다. 이유는, 해당 빈으로 만들면 모든 Feign에서 동작한다고하기 떄문이다.

 

 

 

1) RequestInterceptor

그러면 예저 소스를 보자, 일단 간단하게 RequestInterceptor 구현체를 만들었다.

public class FeignConfig {

  @Bean
  public RequestInterceptor AConfig(){
    return new BearerAuthRequestInterceptor("teset");
  }
  public class BearerAuthRequestInterceptor implements RequestInterceptor {
    private String token;

    public BearerAuthRequestInterceptor(String token) {
      this.token = token;
    }

    @Override
    public void apply(RequestTemplate template) {
      System.out.println("BearerAuthRequestInterceptor apply !!!_smith");
      template.header("Authorization", "Bearer " + token);
    }
  }
}

 

그리고 클라이언트 소스도 변경했다.

@FeignClient(name="smithClient", url ="http://localhost:8080",configuration = FeignConfig.class)
public interface ExampleClient{
  @GetMapping(value="/test3")
  void getUsers();
}

 

해당으로 하면, 정상적으로 메소드를 콜하기전에, 해당 인터셉터가 동작하게 설정할 수 있다.

뭐 Request 헤더에 뭔가를 넣거나, 아니면 뭐 다른 로직을 처리하고싶으면 마음대로 넣으면 동작한다.

 

 

2) 날짜 타입 변경 ? 보정?

무튼 해당 처리 이외에도 자바 8이상엗서는 시간에 대한 설정이 디폴트로 이상한게 되어있는듯하다

  @Bean
  public FeignFormatterRegistrar localDateFeignFormatterRegister() {
    return registry -> {
      DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
      registrar.setUseIsoFormat(true);
      registrar.registerFormatters(registry);
    };
  }

 

해당 설정을 통해, 날짜나 DateTImeFormatter에 ISO 타입으로 보낼수 있게 설정을 해주어야 한다.

 

3. 에러 핸들링 가능

FeignException을 상속 받은, 물론 상위는 란타임 예외이지만..

무튼 재시도가능한 예외가 있다.

해당 Exception으로 던져지면, 아래의 메소드에서 처리가 한번더 가능해진다.

@Bean
  public Retryer retryer() {
    return new Retryer.Default(1000, 2000, 3);
  }

 

Exception throw되는 곳은 여러군데 넣어도되지만, 에러 디코드에서 responseStatusCode를 보고 판별이 가능하다.

ErrorDecoder라는 빈과 궁합이 잘맞다.

무튼 해당 빈도 구현해보자

@Bean
  public ErrorDecoder decoder() {
    return (methodKey, response) -> {
      if (response.status()==500) {

        return new RetryableException(500,"retry",);
      }

      return new IllegalStateException(format("%s 요청이 성공하지 못했습니다. - status: %s, headers: %s", methodKey, response.status(), response.headers()));
    };
  }

RetryableException 생성자의 파라미터가 많아서 안썼는데, 이건 찾아보면 금방쓸수있다.

무튼 해당 디코더로 응답상태를 보고, 파악할 수 있다. 좀더 간결하게 소스를 짤수도있지만 이건 실무에서나 하고

혼자 공부하는 용도로는 대충 짜자일단...시간...없다....

 

 

4. Httpclient의 재구성

Feign에서는 Httpclient를 두가지 쓸수있다.

아래 소스를 보자 FeignAutoConfiguration에서 자동으로 설정해주는 소스의 일부이다.

@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(ApacheHttpClient.class)
	@ConditionalOnMissingBean(CloseableHttpClient.class)
	@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
	@Conditional(HttpClient5DisabledConditions.class)
	protected static class HttpClientFeignConfiguration {

 

@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(OkHttpClient.class)
	@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
	@ConditionalOnProperty("feign.okhttp.enabled")
	protected static class OkHttpFeignConfiguration {

 

컨디셔널하게, OkHttpClient로 하지, 아파치로 할지 결정하게 된다.

해당, 관련되서 OkHttpClient는 아래와 같은 장점이 있다.

OkHttp에는 HTTP/2, 내장 응답 캐시, 웹 소켓 및 더 간단한 API가 있습니다. 
더 나은 기본값이 있으며 효율적으로 사용하기가 더 쉽습니다. 더 나은 URL 모델, 더 나은 쿠키 모델,
더 나은 헤더 모델 및 더 나은 호출 모델이 있습니다. OkHttp를 사용하면 호출을 쉽게 취소할 수 있습니다.
OkHttp는 안전하고 광범위하게 호환되는 TLS 기본값을 신중하게 관리했습니다.
Okhttp는 훌륭한 REST용 API인 Retrofit과 함께 작동합니다. 
또한 데이터 스트림을 위한 훌륭한 라이브러리인 Okio와 함께 작동합니다.
OkHttp는 하나의 작은 종속성(Okio)이 있는 작은 라이브러리이며 배울 코드가 적습니다. 
OkHttp는 내부적으로 이를 사용하는 10억 대의 Android 4.4+ 장치와 함께 더 광범위하게 배포됩니다.

 

해당 프로퍼티 값을 설정함으로써 두가지에 대한 httpclient를 골라서 쓰면된다.

뭐가 더 좋다.,..라는건 상황마다 달라서 장담하기 어렵다.

 

6) 편하게 구조화되서 쓰기

또하나의 빈을 두고 객체를 빈으로 등록하는 방법이 있다.

예를 들어서, 소스로 보면 아래와 같다.

 public interface RequestSample {

  @RequestLine("GET /test3")
  @Headers("Content-type")
  String auth (@Param String key);
}
@Bean
  ExampleClient exampleClient(){
    return Feign.builder().logger(new Slf4jLogger())
      .target(ExampleClient.class,"/test3");
  }

이런식으로의 빈 등록 및 사용도 가능하다.

해당 식으로 관리하게 되면, 파일 단위가 아니라 빈 단위로 여러가지 Client를 조합해서 관리를 할 수도 있다.

물론 Configuration파일은 외부에서 만드는 것이 좋다.

 

 

이만 끗~

반응형