RestClient Customization Scope?

viiviii·2024년 5월 5일
0

최근 RestClient를 사용하게 되어 문서를 읽던 중,
사용 범위에 따른 3가지 방식을 소개하는데 이 부분의 의미가 너무 헷갈렸다.

요약하자면 대충 이런 내용인데

최대한 좁은 범위에 적용하려면:

  • 자동 구성된 RestClient.Builder를 주입받아 호출해라. 인스턴스는 stateful하다
  • Builder에 대한 변경은 이후에 생성된 모든 클라이언트에 반영된다
  • 동일한 빌더로 여러 클라이언트를 생성하려면 clone()을 사용하여 빌더를 복제하는 것을 고려해라

헷갈린 포인트

  • 범위를 좁힌다는 건 영향을 적은 곳에서 받겠다는 건데
  • Builder를 주입받아서 설정을 변경하면 이 Builder를 사용한 모든 곳에 반영된다고?

내가 상상한 코드는 대충 아래와 같았음

class A  {
    A(@Autowired RestClient.Builder builder) {
          // builder 설정 변경
    }
}

class B  {
    B(@Autowired RestClient.Builder builder) {
       // ❓❓ 여기서 A의 변경 사항이 적용 된다고? ❓❓
    }
}

결론

동일한 빌더라는 건 같은 참조 값을 의미하는 것이었음

void some(@Autowired RestClient.Builder builder) {
     builder.defaultHeader(...);
     var clientA = builder.defaultHeader(CHANGE).build();
     ....
     var clientB = builder.build();
}

이런 경우에 복제(clone)해서 쓰라는 것.

RestClient.Builder bean

내가 저 설명을 보고 쫄게된 이유는 RestClient.Builder bean이 어떻게 구성되며 작동하는지 몰랐기 때문이다.

RestClient.BuilderRestClientAutoConfiguration이 bean으로 자동으로 구성해준다.

@AutoConfiguration(after = { HttpMessageConvertersAutoConfiguration.class, SslAutoConfiguration.class })
@ConditionalOnClass(RestClient.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestClientAutoConfiguration {

	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
		....
    }
}

위 코드를 통해

  • 애플리케이션이 non-reactive app이면 자동 적용되고(@AutoConfiguration, NotReactiveWebApplicationCondition)
  • RestClient.Builder는 요청이 있을 때 마다 새로 생성됨을 알 수 있다(@Scope("prototype"))

prototype image

https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-prototype

테스트

아래와 같은 경우에 테스트가 실패하니 clone 하라는 것임

@Test
void test(@Autowired RestClient.Builder builder) {
    //given
    var server = MockRestServiceServer.bindTo(builder).build();

    builder.defaultHeader(ACCEPT, APPLICATION_JSON_VALUE);

    //when
    var clientA = builder.defaultHeader(ACCEPT, TEXT_PLAIN_VALUE).build(); // 👈 clone() 하시오
    server.expect(requestTo("a"))
            .andExpect(header(ACCEPT, TEXT_PLAIN_VALUE))
            .andRespond(withSuccess());

    var clientB = builder.build();
    server.expect(requestTo("b"))
            .andExpect(header(ACCEPT, APPLICATION_JSON_VALUE))
            .andRespond(withSuccess());

    //then
    clientA.get().uri("a").retrieve().toBodilessEntity();
    clientB.get().uri("b").retrieve().toBodilessEntity();
}

결국 builder 특성상 당연한 이야기였고 문서가 친절한 것이었다,,, 쫄지말자!

0개의 댓글