Spring

LocalDate ํฌ๋งท ์˜ค๋ฅ˜๋กœ ์‹œ์ž‘๋œ OpenFeign ์˜ Auto Configuration ํƒ์ƒ‰

์•ˆ์ข…ํ˜ 2025. 5. 10. 20:18

๊ฐœ์š”

OpenFeign์„ ์‚ฌ์šฉํ•ด admin ์„œ๋ฒ„์™€ core ์„œ๋ฒ„ ๊ฐ„์— HTTP ํ†ต์‹ ์„ ํ•˜๋˜ ์ค‘, ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

  • admin ์„œ๋ฒ„์—์„œ LocalDate ํƒ€์ž…์„ yyyy-MM-dd ํฌ๋งท์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ core ์„œ๋ฒ„์—์„œ๋Š” LocalDate ํฌ๋งท์ด yyyy-MM-dd ๋ฅผ ๊ธฐ๋Œ€ํ–ˆ์ง€๋งŒ, yy. M. d. ์œผ๋กœ ์™€์„œ ํฌ๋งท ๋ณ€ํ™˜์— ์‹คํŒจํ•˜๋Š” ์—๋Ÿฌ ๋กœ๊ทธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋Š” ์šฐ์•„ํ•œํ˜•์ œ๋“ค ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ์—์„œ ์ œ์‹œํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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

 

ํ•˜์ง€๋งŒ ์ €๋Š” ์™œ ์ด๋Ÿฐ ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•œ์ง€, ์ฆ‰ ์–ด๋–ค ๊ณผ์ •์„ ํ†ตํ•ด ํฌ๋งท์ด ๊นจ์กŒ๋Š”์ง€ ๊ทธ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ•ด๋ณด๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ๊ณผ์ •์„ ์•„๋ž˜์— ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

 

1. ๋””๋ฒ„๊ฑฐ๋กœ ์›์ธ ์ฐพ๊ธฐ

๋จผ์ € @FeignClient๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์ชฝ์— ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๋ฅผ ๊ฑธ๊ณ , LocalDate๊ฐ€ ์–ธ์ œ yyyy-MM-dd → yy. M. d. ํ˜•์‹์œผ๋กœ ๋ฐ”๋€Œ๋Š”์ง€๋ฅผ ์ถ”์ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋””๋ฒ„๊น…์„ ํ†ตํ•ด SpringMvcContract ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ ํฌ๋งท์ด ๋ณ€ํ˜•๋˜๋Š” ๊ณผ์ •์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

SpringMvcContract  ์˜ getExpander

 

๋” ๊นŠ์ด ๋“ค์–ด๊ฐ€ ๋ณด๋‹ˆ, ์ตœ์ข…์ ์œผ๋กœ๋Š” DateTimeFormatterBuilder$CompositePrinterParser ํด๋ž˜์Šค์—์„œ yyyy-MM-dd ํ˜•์‹์ด yy. M. d.๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

SpringMvcContract ์ฝœ์Šคํƒ

@FeignClients ์–ด๋…ธํ…Œ์ด์…˜์˜ ๊ธฐ๋ณธ ๋™์ž‘์ด ์™œ ์ด๋ ‡๊ฒŒ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

 

2. @EnableFeignClients  

๋จผ์ €, ์–ด๋–ค @FeignClients ๊ฐ€ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” @EnableFeignClients ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ FeignClientsRegistrar ํด๋ž˜์Šค๊ฐ€ ๋™์ž‘ํ•˜์—ฌ, @EnableFeignClients์— ๋ช…์‹œ๋œ value, basePackages, defaultConfiguration, clients ๊ฐ’์„ ์ฝ๊ณ  ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„์š”ํ•œ Bean๋“ค์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

 

์ด ๊ณผ์ •์—์„œ @EnableFeignClients ์˜ defaultConfiguration ๋Š” defaults ๋กœ FeignClientsConfiguration ์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

@EnableFeignClients ์˜ defaultConfiguration()

 

3. FeignClientsConfiguration

FeignClientsConfiguration์„ ์‚ดํŽด๋ณด๋ฉด, ์•ž์„œ ๋””๋ฒ„๊น… ์ค‘ ๋ณด์•˜๋˜ SpringMvcContract ํด๋ž˜์Šค๊ฐ€ Bean์œผ๋กœ ๋“ฑ๋ก๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

* SpringMvcContract๋Š” @FeignClient์—์„œ ์‚ฌ์šฉํ•˜๋Š” @RequestMapping, @GetMapping, @RequestParam ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ Spring MVC ๋ฐฉ์‹์œผ๋กœ ํ•ด์„ํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

 

SpringMvcContract ๋Š” feignContract๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋“ฑ๋ก๋˜๋ฉฐ, @ConditionalOnMissingBean์„ ํ†ตํ•ด Contract ํƒ€์ž…์˜ Bean์ด ๋ฏธ๋ฆฌ ๋“ฑ๋ก๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด๋กœ SpringMvcContract๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

4. ํฌ๋งทํ„ฐ ๋“ฑ๋ก ๊ตฌ์กฐ ์ดํ•ดํ•˜๊ธฐ

SpringMvcContract Bean์ด ์ƒ์„ฑ๋  ๋•Œ๋Š” feignConversionService๋ผ๋Š” Bean์ด ํ•จ๊ป˜ ์ฃผ์ž…๋˜๋Š”๋ฐ,
์ด feignConversionService๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ feignFormatterRegistrars ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ์ˆœํšŒํ•˜๋ฉฐ ํฌ๋งทํ„ฐ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

 

์ด๋•Œ ์‚ฌ์šฉ๋˜๋Š” feignFormatterRegistrars๋Š” FeignClientsConfiguration์˜ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜์ด๋ฉฐ, ์ดˆ๊ธฐ์—๋Š” ์•„๋ฌด ํฌ๋งทํ„ฐ๋„ ๋“ฑ๋ก๋˜์ง€ ์•Š์€ ๋นˆ ArrayList๋กœ ์ดˆ๊ธฐํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ , ์•„๋ž˜ ์‚ฌ์ง„์˜ ๋นจ๊ฐ„ ์ค„์—์„œ FormattingConversionService ๋Š” FormatterRegistry ๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๊ธฐ์—

๋นจ๊ฐ„์ค„์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ ‡๊ฒŒ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

registrar.registerFormatters(registry);

 

์—ฌ๊ธฐ์„œ registrar๋Š” ํฌ๋งทํ„ฐ๋ฅผ ๋ณด์œ ํ•œ ๊ฐ์ฒด์ด๊ณ , registry๋Š” ์ด ํฌ๋งทํ„ฐ๋“ค์„ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

 

์‰ฝ๊ฒŒ ๋น„์œ ํ•˜์ž๋ฉด

registrar : LocalDate๋ฅผ yyyy-MM-dd ๋กœ ํฌ๋งทํ•˜๊ณ  ์‹ถ์–ด!
registry : ์ข‹์•„! ๊ทธ ํฌ๋งท ๋‚ด๊ฐ€ ๊ธฐ์–ตํ•ด์„œ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ• ๊ป˜!

 

์ด์ฒ˜๋Ÿผ registrar์™€ registry์˜ ๊ด€๊ณ„๋Š” N : 1 ๋กœ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ registrar๊ฐ€ ์กด์žฌํ•  ์ˆ˜ ์žˆ๊ณ , ์ด๋“ค์€ ํ•˜๋‚˜์˜ registry์— ์ฐจ๋ก€๋กœ ํฌ๋งทํ„ฐ๋ฅผ ๋“ฑ๋กํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
์ฆ‰, feignFormatterRegistrars(registrar)๋ผ๋Š” ๋ฆฌ์ŠคํŠธ์— ๋“ค์–ด ์žˆ๋Š” ์—ฌ๋Ÿฌ ํฌ๋งทํ„ฐ๊ฐ€ ํ•˜๋‚˜์˜ FormattingConversionService (registry)์— ๋ชจ๋‘ ๋“ฑ๋ก๋˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

 

์ด์ œ ์ด ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ–ˆ๋”๋‹ˆ, ํ•  ์ผ์ด ๋ช…ํ™•ํ•ด์กŒ์Šต๋‹ˆ๋‹ค. FeignFormatterRegistrar๋ฅผ ์ •์˜ํ•ด์„œ ์›ํ•˜๋Š” ํƒ€์ž…(LocalDate)์˜ ํฌ๋งท์„ ์ƒ์„ฑํ•˜๊ณ , registry ์— ๋“ฑ๋กํ•ด์ฃผ๋Š” ๊ฒƒ์ด์—ˆ๊ณ , ๊ทธ๋ ‡๊ฒŒ ํ•ด์„œ ๋“ฑ์žฅํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋ฐ”๋กœ ์ฒ˜์Œ์— ๋ดค๋˜ ์ฝ”๋“œ ์˜€์Šต๋‹ˆ๋‹ค.

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

๋žŒ๋‹ค์‹์—์„œ registry๋Š” FormatterRegistry ์ธํ„ฐํŽ˜์ด์Šค์ด๊ณ , ๊ตฌํ˜„์ฒด๋กœ FormattingConversionService ์ž…๋‹ˆ๋‹ค.

 

์ง€๊ธˆ๊นŒ์ง€ ๊ฐœ์š”์— ํ•ด๋‹นํ•˜๋Š” ์„ค๋ช…์ด์—ˆ๊ณ , ์ถ”๊ฐ€๋กœ OpenFeign ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉฐ ์•Œ๊ฒŒ ๋œ ๊ฒƒ๋“ค์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

 

๋ฒˆ์™ธ 1. OpenFeign์˜ ๊ธฐ๋ณธ Client ์ธ Default ๊ฐ์ฒด

OpenFeign์—์„œ ํŠน๋ณ„ํ•œ ์„ค์ • ์—†์ด ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ HTTP Client๋Š” Client.Default ์ด๋ฉฐ, ๋‚ด๋ถ€์ ์œผfh HttpURLConnection์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

HttpURLConnection์€ java.net ํŒจํ‚ค์ง€์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ํ‘œ์ค€ ํด๋ž˜์Šค์ด๋ฉฐ HTTP ์ปค๋„ฅ์…˜ ๊ด€๋ จ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , HttpURLConnection ๋Š” ๊ตฌํ˜„ ๊ณผ์ •์—์„œ sun.net.www.http.HttpClient๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

์ฆ‰, Client ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ธ Default ๊ฐ์ฒด๋Š” HttpURLConnection์„ ์‚ฌ์šฉํ•˜์—ฌ HTTP ํ†ต์‹ ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฒˆ์™ธ 2. ๋‹ค๋ฅธ Http Client ๋ฅผ ์‚ฌ์šฉํ•  ์ˆœ ์—†์„๊นŒ? FeignAutoConfiguration ๋ฅผ ํ™•์ธํ•˜๊ธฐ

OpenFeign ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” OkHttpClient or Http2Client or Apache HttpClient5 ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์˜์กด์„ฑ๊ณผ yml ํŒŒ์ผ์— ์„ค์ •๊ฐ’์„ true ํ•ด์ฃผ๋ฉด Feign์˜ ๊ธฐ๋ณธ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ด์•ผ๊ธฐ์ž…๋‹ˆ๋‹ค.

To use OkHttpClient-backed Feign clients and Http2Client Feign clients, make sure that the client you want to use is on the classpath and set spring.cloud.openfeign.okhttp.enabled or spring.cloud.openfeign.http2client.enabled to true respectively.

 

์˜ˆ๋ฅผ ๋“ค์–ด, OkHttpClient๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

# yml
spring.cloud.openfeign.okhttp.enabled=true

 

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด, org.springframework.cloud.openfeign ํŒจํ‚ค์ง€์— ์œ„์น˜ํ•œ FeignAutoConfiguration ํด๋ž˜์Šค๊ฐ€ ์œ„์—์„œ ์„ค์ •ํ•œ spring.cloud.openfeign.okhttp.enabled ๊ฐ’์„ ์ฝ์–ด์™€ OkHttpClient๋ฅผ Feign์˜ ๊ธฐ๋ณธ ํด๋ผ์ด์–ธํŠธ๋กœ ๊ตฌ์„ฑํ•ด ์ค๋‹ˆ๋‹ค.

FeignAutoConfiguration ๊ฐ€ yml ์„ค์ •๊ฐ’์„ ์ฝ์–ด OkHttpClient ๋กœ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ณ€๊ฒฝ

 

๊ทธ๋ฆฌ๊ณ , FeignAutoConfiguration์€ OkHttpClient๋ฟ๋งŒ ์•„๋‹ˆ๋ผ HttpClient 5์™€ ๊ฐ™์€ ๋‹ค๋ฅธ Client ๊ตฌํ˜„์ฒด๋„ ์ง€์›ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ HttpClient 5์˜ ๊ฒฝ์šฐ์—๋Š” yml์— ๋ช…์‹œ์ ์ธ enabled ์„ค์ •์„ true๋กœ ํ•˜์ง€ ์•Š์•„๋„ matchIfMissing = true ์„ค์ •์„ ํ†ตํ•ด ์˜์กด์„ฑ๋งŒ ์กด์žฌํ•˜๋ฉด ํ•ด๋‹น ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.(Spring Cloud 2023.0.3 ๋ฒ„์ „ ๊ธฐ์ค€์ž…๋‹ˆ๋‹ค) 

 

๋งˆ์น˜๋ฉฐ

์ด๋ฒˆ์— ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๋А๋‚€ ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์ „์—” @Enable~, @ConditionalOn~, @ConditionalOnMissingBean, ํ˜น์€ AutoConfiguration ์—์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆด ์ฐพ์ง€ ๋ชปํ•˜๋Š”์ง€ ์ž˜ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ํ† ๋น„์˜ ์Šคํ”„๋ง๋ถ€ํŠธ ๊ฐ•์˜๋ฅผ ๋“ค์œผ๋ฉฐ, ์ด๋Ÿฌํ•œ ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ฌด์—‡์„ ์œ„ํ•ด ์กด์žฌํ•˜๋Š”์ง€, ๋„ค์ด๋ฐ์€ ์™œ ๊ทธ๋ ‡๊ฒŒ ๋˜์—ˆ๋Š”์ง€, META-INF ์˜ .imports ํŒŒ์ผ์„ ์ฝ์–ด ํ›„๋ณด๊ตฐ๋“ค์„ ImportSelector ๋กœ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋“ฑ์„ ์ดํ•ดํ•˜์˜€๊ณ , ์ด ๋•๋ถ„์— Feign์˜ ์ฝ”๋“œ๋ฅผ ๋”ฐ๋ผ๊ฐˆ ์ˆ˜ ์žˆ์ง€ ์•Š์•˜๋‚˜ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

 

๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ์ฝ๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€ํ”ผํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ธฐ์— ์•ž์œผ๋กœ๋„ ์ด๋Ÿฐ ๊ณผ์ •์„ ํ†ตํ•ด ์ดํ•ด์˜ ๊นŠ์ด๋ฅผ ๋”ํ•ด์ฃผ๊ณ , ๋ฌธ์ œ ํ•ด๊ฒฐ ๋Šฅ๋ ฅ์„ ํ–ฅ์ƒ ์‹œํ‚ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ํ† ์Šค ํŽ˜์ด๋จผ์ธ ์—์„œ Feign์˜ Default๋ฅผ HttpClient 5๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์‚ฌ๋ก€๋ฅผ ๋‹ด์€ ๊ธ€์ธ 'Feign ์ฝ”๋“œ ๋ถ„์„๊ณผ ์„œ๋ฒ„ ์„ฑ๋Šฅ ๊ฐœ์„ ' ์„ ์ถ”๊ฐ€๋กœ ์ฝ์œผ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„ ๋งํฌ๋ฅผ ์ฒจ๋ถ€๊ฐœํ•˜๋ฉฐ ๋งˆ๋ฌด๋ฆฌ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

์ฐธ๊ณ 

https://docs.spring.io/spring-cloud-openfeign/reference/spring-cloud-openfeign.html#spring-cloud-feign-overriding-defaults

https://github.com/OpenFeign/feign

https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp

https://square.github.io/okhttp/