구체적인 지명에 대한 정보는 Kakao Maps API를 사용하고 자동차 경로에 대한 정보는 Naver Maps API를 사용해야 하는 상황이다.
ObjectMapper란
JSON 형식에 대하여 응답을 직렬화하고 요청을 역직렬화 할 때 사용하는 기술
ObjectMapper 타입의 Bean을 2개 등록하기 때문에 주입을 받는 대상(WebClient)에서 해당 Bean에 대한 정보를 알아야 한다.
@Configuration
public class KakaoWebClientConfig {
private static final String HEADER_PREFIX = "KakaoAK ";
private final ObjectMapper objectMapper;
public KakaoWebClientConfig(@Qualifier("kakaoObjectMapper") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Bean(name = "KakaoWebClient")
public WebClient webClient(
@Value("${kakao-map.url}") final String url,
@Value("${kakao-map.key}") final String authHeader) {
final ExchangeStrategies exchangeStrategies =
ExchangeStrategies.builder()
.codecs(
configurer -> {
configurer.defaultCodecs().maxInMemorySize(-1);
configurer
.defaultCodecs()
.jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper));
})
.build();
return WebClient.builder()
.exchangeStrategies(exchangeStrategies)
.baseUrl(url)
.defaultHeader(HttpHeaders.AUTHORIZATION, HEADER_PREFIX + authHeader)
.build();
}
}
@Configuration
public class NaverWebClientConfig {
private static final String HEADER_CLIENT_ID = "X-NCP-APIGW-API-KEY-ID";
private static final String HEADER_CLIENT_SECRET = "X-NCP-APIGW-API-KEY";
private final ObjectMapper objectMapper;
@Autowired
public NaverWebClientConfig(@Qualifier("naverObjectMapper") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Bean(name = "NaverWebClient")
public WebClient webClient(
@Value("${naver-map.url}") final String url,
@Value("${naver-map.id}") final String clientId,
@Value("${naver-map.secret}") final String secretKey) {
final ExchangeStrategies exchangeStrategies =
ExchangeStrategies.builder()
.codecs(
configurer -> {
configurer.defaultCodecs().maxInMemorySize(-1);
configurer
.defaultCodecs()
.jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper));
})
.build();
return WebClient.builder()
.exchangeStrategies(exchangeStrategies)
.baseUrl(url)
.defaultHeader(HEADER_CLIENT_ID, clientId)
.defaultHeader(HEADER_CLIENT_SECRET, secretKey)
.build();
}
}
@Configuration
public class KakaoObjectMapperConfig {
@Qualifier("kakaoObjectMapper")
@Bean
public ObjectMapper kakaoObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
}
}
@Configuration
public class NaverObjectMapperConfig {
@Qualifier("naverObjectMapper")
@Bean
public ObjectMapper naverObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE);
}
}
Parameter 0 of method mappingJackson2HttpMessageConverter in org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration required a single bean, but 2 were found:
- kakaoObjectMapper: defined by method 'kakaoObjectMapper' in class path resource [com/twtw/backend/config/mapper/KakaoObjectMapperConfig.class]
- naverObjectMapper: defined by method 'naverObjectMapper' in class path resource [com/twtw/backend/config/mapper/NaverObjectMapperConfig.class]
분명히 각 WebClientConfig와 ObjectMapperConfig 파일에 @Qualifier 어노테이션을 사용하여 주입 대상을 명시해주었다.. 그렇다면 위 에러 코드는 왜 발생한 것일까????
- 에러코드를 유심히 보자 오류가 발생한 대상이 무엇인가?
- mappingJackson2HttpMessageConverter
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
private static final List<MediaType> problemDetailMediaTypes =
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
@Nullable
private String jsonPrefix;
/**
* Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
* You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
* @see Jackson2ObjectMapperBuilder#json()
*/
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
}
@Configuration
public class KakaoObjectMapperConfig {
@Primary
@Qualifier("kakaoObjectMapper")
@Bean
public ObjectMapper kakaoObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
}
}