๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Trouble Shooting

Spring @RequestBody ํ•„๋“œ ๋ฐ”์ธ๋”ฉ์ด ๋˜์ง€ ์•Š๋Š” ์ด์Šˆ (feat. Lombok, Jackson์˜ ๋„ค์ด๋ฐ ์ฐจ์ด)

by ์ฃผ๋ฐœ2 2024. 3. 20.
๋ฐ˜์‘ํ˜•

๊ฐœ์š”

Spring์—์„œ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ JSON ํ˜•์‹์œผ๋กœ ๋ฐ›๊ณ , @RequestBody๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JSON body์˜ ๋ฐ์ดํ„ฐ๋ฅผ Java Object์˜ ํ•„๋“œ์™€ ์—ญ์ง๋ ฌํ™”(Deserialize)๋ฅผ ํ†ตํ•ด ๊ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉ์ด ์ด๋ฃจ์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

ํ•˜์ง€๋งŒ, ํ•„๋“œ์˜ ํŠน์ • ๋„ค์ด๋ฐ + Lombok์˜ @Getter๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ฐ”์ธ๋”ฉ์ด ์ •์ƒ์ ์œผ๋กœ ๋˜์ง€ ์•Š๋Š” ์ด์Šˆ๊ฐ€ ์กด์žฌํ•˜๋Š”๋ฐ์š”, ์ด์™€ ๊ด€๋ จํ•˜์—ฌ ๊ฐ„๋žตํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

### Send POST request with json body
POST http://localhost:8080/api/v1/test
Content-Type: application/json

{
  "pId": "pId",
  "poId":  "poId"
}

 

์˜ˆ๋ฅผ ๋“ค์–ด, ์œ„์™€ ๊ฐ™์€ ์„œ๋ฒ„ ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ ์œ„ JSON ํ˜•์‹์œผ๋กœ body ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋ฉด,

์„œ๋ฒ„์˜ TestBody Object์— pId, poId๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉ์ด ๋ฉ๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ, ์œ„์˜ ๊ฒฝ์šฐ pId๋Š” null์ด ๋˜๊ณ , poId๋งŒ ์ •์ƒ์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉ์ด ๋˜๋Š”๋ฐ์š” ์ด์™€ ๊ด€๋ จํ•ด์„œ ์™œ pId๋Š” ๋ฐ”์ธ๋”ฉ์ด ๋˜์ง€ ์•Š๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

@RequestBody์˜ ๋™์ž‘๊ณผ์ • ๊ด€๋ จํ•ด์„œ๋Š” RequestResponseBodyMethodProcessor ํด๋ž˜์Šค์˜ resolveArgument() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด JSON ๋ฐ์ดํ„ฐ์™€ Java Object ๊ฐ„ ๋ฐ”์ธ๋”ฉ์ด ๋˜๋Š”๋ฐ์š”, ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ž์„ธํ•œ ์„ค๋ช…์€ ์ƒ๋žตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

(๊ถ๊ธˆํ•˜์‹œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๋น„์Šทํ•˜๊ฒŒ ๋ธŒ๋ ˆ์ดํฌ ํฌ์ธํŠธ๋ฅผ ๊ฑธ๊ณ , ๋”ฐ๋ผ ๋“ค์–ด๊ฐ€๋ณด์‹œ๋ฉด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๐Ÿ˜…)

 

 

 

๋ถ„์„

์ˆœ์„œ๋Œ€๋กœ๋Š” ์•„๋‹ˆ์ง€๋งŒ, ๋Œ€๋žต์ ์œผ๋กœ ์œ„ ํด๋ž˜์Šค๋“ค์— ๋Œ€ํ•ด ๋ธŒ๋ ˆ์ดํฌ ํฌ์ธํŠธ๋ฅผ ๊ฑธ์–ด๋†“๊ณ  ๋“ค์–ด๊ฐ€๋‹ค ๋ณด๋ฉด,

POJOPropertiesCollector ํด๋ž˜์Šค์—์„œ Object์˜ field๋ฅผ ์ฐพ์•„ ์„ค์ •ํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋Š”๋ฐ ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

 

@RequestBody์˜ JSON <-> Object ์—ญ์ง๋ ฌํ™”๋Š” ๋Œ€๋žต์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ํ๋ฆ„์œผ๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

AbstractJackson2HttpMessageConveter > ObjectMapper > Deserializer... > BeanDeserializerFactory > ... > POJOPropertiesCollector > DefaultAccessorNamingStrategy > ...

 

๊ทธ๋Ÿผ ์•ž์˜ ํด๋ž˜์Šค๋Š” ์ผ๋ถ€ ์ƒ๋žตํ•˜๊ณ , POJOPropertiesCollector ํด๋ž˜์Šค๋ถ€ํ„ฐ ๊ฐ„๋žตํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

POJOPropertiesCollector

collectAll()

 

POJOPropertiesCollector#collectAll()

collectAll() ๋ฉ”์„œ๋“œ์— ์ฃผ์„๋„ ๋งŽ๊ณ , ์ด๊ฒƒ์ €๊ฒƒ ๋ฉ”์„œ๋“œ๋“ค๋„ ๋งŽ์€๋ฐ์š”

์œ„์—์„œ Java Object ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ _addFields(props), _addMethods(props) ๋“ฑ์ž…๋‹ˆ๋‹ค.

 

์œ„์—์„œ _addFields(props) ๋ฉ”์„œ๋“œ๋ฅผ ๊ฑฐ์น˜๋ฉด์„œ pId, poId ํ•„๋“œ๊ฐ€ ์„ค์ •์ด ๋˜์—ˆ์ง€๋งŒ,

์ด์ƒํ•˜๊ฒŒ๋„ _addMethods(props) ๋ฉ”์„œ๋“œ๋ฅผ ๊ฑฐ์น˜๋ฉด "pid"๋ผ๋Š” ์•Œ ์ˆ˜ ์—†๋Š” ํ•„๋“œ๊ฐ€ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

(poId ํ•„๋“œ์™€ ๋‹ค๋ฅด๊ฒŒ pId, pid๋Š” getter, setter๋„ null์ธ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

 

๊ทธ๋Ÿผ _addMethods(props)์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์œผ๋‹ˆ, ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ์กฐ๊ธˆ ๋” ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

_addMethods(Map<String, POJOPropertyBuilder> props)

ํ˜„์žฌ ๋ฉ”์„œ๋“œ๊ฐ€ getter์ด๋ฉด _addGetterMethod()๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , setter์ด๋ฉด _addSetterMethod()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

 

์‹ค์ œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ํ•„๋“œ "pId"์— ๋Œ€ํ•ด Lombok์ด ์ƒ์„ฑํ•˜๋Š” Getter ๋ฉ”์„œ๋“œ์˜ ๋„ค์ด๋ฐ์€ getPId()์ด๊ณ ,
์ด์–ด์„œ _addGetterMethod()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

 

_addGetterMethod(Map<String, POJOPropertyBuilder> props, AnnotatedMethod m, AnnotationIntrospector ai)

_addGetterMethod()์—์„œ ๋ฉ”์„œ๋“œ์˜ ๋„ค์ด๋ฐ์„ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ์š”,

ํ˜„์žฌ ๋กœ์ง์—์„œ๋Š” _accessorNaming.findNameForRegularGetter(m, m.getName())๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

 

DefaultAccessorNamingStrategy

AccessorNamingStrategy >&nbsp;DefaultAccessorNamingStrategy

findNameForRegularGetter(AnnotatedMethod am, String name)

DefaultAccessorNamingStrategy#findNameForRegularGetter

์œ„์˜ findNameForRegularGetter() ๋ฉ”์„œ๋“œ์—์„œ๋Š” ๋งˆ์ง€๋ง‰ return์—์„œ legacyManglePropertyName() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

 

legacyManglePropertyName(final String basename, final int offset)

์œ„ 159๋ผ์ธ๋ถ€ํ„ฐ ์‹œ์ž‘๋˜๋Š” ์ฝ”๋“œ๊ฐ€ Object์˜ ํ•„๋“œ๋ช…์„ ์ƒ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

 

ํ•„๋“œ๋ช…์€ pId์ด์ง€๋งŒ, 169๋ผ์ธ์˜ append() ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด๋ฉด ์†Œ๋ฌธ์ž๋กœ ๋ณ€๊ฒฝํ•œ ๋ฌธ์ž๋ฅผ ๋„ฃ๊ธฐ ๋•Œ๋ฌธ์— I๊ฐ€ ์•„๋‹Œ i๋ฅผ ์ €์žฅํ•˜์—ฌ ํ•„๋“œ๋ช…์„ ๋งŒ๋“ค์–ด๋‚ด๊ณ , ์ด๋กœ์ธํ•ด ํ˜ธ์ถœ๋ถ€์ธ POJOPropertiesCollector#_addGetterMthod()์—์„œ๋Š” pid๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ ์ €์žฅํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

POJOPropertiesCollector#_addGetterMthod()

 

 

size = 3 (pId, poId, pid)
size = 2 (poId, pid)

์˜๋„ํ•˜์ง€ ์•Š์€ pid๊ฐ€ ์ถ”๊ฐ€๋กœ ํ”„๋กœํผํ‹ฐ์— ์ €์žฅ์ด ๋˜๊ณ , ์ดํ›„ _removeUnwantedProperties(props) ๋ฉ”์„œ๋“œ์—์„œ๋Š” ํŠน์ • ์กฐ๊ฑด(visible)์— ๋”ฐ๋ผ pId๋ฅผ ํ•„๋“œ์—์„œ ์ œ๊ฑฐํ•˜์—ฌ ์˜๋„ํ•˜์ง€ ์•Š์€ "pid" ๋ผ๋Š” ๋„ค์ด๋ฐ์„ ๊ฐ€์ง„ ํ•„๋“œ๊ฐ€ ์ €์žฅ๋˜์–ด์„œ ๊ธฐ์กด์˜ "pId" ๋„ค์ด๋ฐ์„ ๊ฐ€์ง„ ํ•„๋“œ๋Š” null ์ฒ˜๋ฆฌ๊ฐ€ ๋˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

 

 

๊ทธ๋Ÿผ ์™œ ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ• ๊นŒ์š”?

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋ง์”€๋“œ๋ฆฌ๋ฉด Lombok์—์„œ ์ƒ์„ฑํ•˜๋Š” getter/setter ๋ฉ”์„œ๋“œ์˜ ๋„ค์ด๋ฐ๊ณผ, Jackson์—์„œ ์ธ์‹ํ•˜๋Š” getter/setter ๋ฉ”์„œ๋“œ์˜ ๋„ค์ด๋ฐ์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

 

์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์—๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ, "pId"์˜ ๊ฒฝ์šฐ getter์˜ ๋„ค์ด๋ฐ์ด getPId()๊ฐ€ ๋˜์–ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์ง€๋งŒ(์‹ค์ œ Lombok์˜ ๋„ค์ด๋ฐ), IntelliJ์—์„œ๋Š” getpId()๋กœ ๋งŒ๋“ค์–ด ์ฃผ๊ณ , Jackson์—์„œ๋„ getpId()์˜ ๊ฒฝ์šฐ์—๋งŒ ์ธ์‹ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

 

์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์ผ€์ด์Šค๋Š” ์•„๋‹ˆ๊ณ , ์œ„์˜ ๊ฒฝ์šฐ ์ฒ˜๋Ÿผ getter/setter์˜ ๋„ค์ด๋ฐ์ด ์—ฐ์†๋œ 2๊ฐœ์˜ ๋Œ€๋ฌธ์ž๊ฐ€ ๋  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

(aBc, pId, cId ...)

 

 

์‹ค์ œ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ธฐ

์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๋ณด์‹œ๋ฉด ์•„์‹œ๊ฒ ์ง€๋งŒ, Lombok๊ณผ IntelliJ๊ฐ€ ๋งŒ๋“ค์–ด๋‚ด๋Š” getter์˜ ๋„ค์ด๋ฐ์€ ์„œ๋กœ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

 

 

JavaBeans์˜ ์ปจ๋ฒค์…˜

https://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/

์œ„ ์ด๋ฏธ์ง€๋Š” Oracle์— ์กด์žฌํ•˜๋Š” JavaBeans ๊ทœ์•ฝ์—์„œ 8.8 Capitalization of inferred names. ์ค‘ ์ผ๋ถ€ ๋ฌธ์žฅ์ž…๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰ ๋ฌธ์žฅ์—์„œ "๊ฐ€์žฅ ์ฒ˜์Œ ๋‘ ๋ฌธ์ž๊ฐ€ ์—ฐ์†์œผ๋กœ ๋Œ€๋ฌธ์ž๋ผ๋ฉด, ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค"๋ผ๊ณ  ๋‚˜์™€์žˆ๋Š”๋ฐ์š”, ์ด๋Ÿฌํ•œ ์ด์œ  ๋•Œ๋ฌธ์—

pId์˜ ๊ฒฝ์šฐ JavaBeans ๊ทœ์•ฝ์„ ๋”ฐ๋ฅด๋ฉด IntelliJ๊ฐ€ ๋งŒ๋“ค์–ด๋‚ด๋Š” getpId()๊ฐ€ ๋งž๋Š” ๋„ค์ด๋ฐ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

(์ €๋Š” getPId()๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์—ˆ๋Š”๋ฐ, ์ด์™€ ๊ด€๋ จํ•˜์—ฌ stackoverflow ์—๋„ ์—ฌ๋Ÿฌ ์˜๊ฒฌ๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.)

 

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ํ•„๋“œ์˜ ๋„ค์ด๋ฐ์— ๋Œ€ํ•ด์„œ Lombok์„ ์‚ฌ์šฉํ•˜๋ฉด JavaBeans์™€๋Š” ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ์š”,

์ผ๋ฐ˜์ ์ธ ์ƒํ™ฉ์€ ์•„๋‹ˆ์ง€๋งŒ ๋งŽ์€ Java ๊ฐœ๋ฐœ์ž๋“ค์ด Lombok์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ฏธ๋ฃจ์–ด๋ณด๋ฉด, ๋™์ผํ•œ ์ด์Šˆ๋ฅผ ๊ฒช์—ˆ๋˜ ์‚ฌ๋žŒ์ด ์กด์žฌํ–ˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ์š”, ์ฐพ์•„๋ณด๋‹ˆ ์—ญ์‹œ ์กด์žฌํ–ˆ์Šต๋‹ˆ๋‹ค.

 

https://github.com/FasterXML/jackson-databind/issues/3538

 

field name changed when deserializing bean · Issue #3538 · FasterXML/jackson-databind

Describe the bug A clear and concise description of what the bug is. RequestBody annotation bean with double upper word,filed will change. for example,bean with field name qAExportFieldList,when de...

github.com

์œ„ ์ด์Šˆ์—์„œ ๋งˆ์ง€๋ง‰ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๋ณด๋‹ˆ, (์ œ๊ฐ€ ์ดํ•ดํ•œ ๊ฒŒ ๋งž๋‹ค๋ฉด) ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋น„ํ‘œ์ค€์ ์ธ(JavaBeans์˜ ๋„ค์ด๋ฐ ๊ทœ์•ฝ์„ ๋”ฐ๋ฅด์ง€ ์•Š๋Š”)

ํ•„๋“œ๋ช…๊ณผ getter/setter๊ฐ€ ์—ฐ๊ฒฐ(๋ฐ”์ธ๋”ฉ?) ๋˜๋„๋ก ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด Jackson์ด ํ”„๋กœํผํ‹ฐ ๋ช…์„ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋„๋ก ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ 95%์ •๋„ ํ™•์‹ ํ•œ๋‹ค๊ณ  ๋ง์„ ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋„ค์š”.

 

์œ„ cowtowncoder๊ฐ€ ๋‚จ๊ธด ๋‚ด์šฉ์„ ์ž˜๋ชป ์ดํ•ดํ•˜์—ฌ ๊ด€๋ จ ์ด์Šˆ๋ฅผ ๋“ฑ๋กํ•ด๋†“๊ธด ํ–ˆ๋Š”๋ฐ, ํ•ด๋‹น ์ด์Šˆ๊ฐ€ ๋ฐ˜์˜๋˜์–ด ์ˆ˜์ •์ด ๋˜๋Š”๊ฒƒ์€ ์‰ฝ์ง€ ์•Š์•„ ๋ณด์ด๋„ค์š”.. ใ…Žใ…Ž

https://github.com/FasterXML/jackson-databind/issues/4421

 

Problem with property name on deserialization wrt non-standard property names · Issue #4421 · FasterXML/jackson-databind

(related to earlier issue #3538) Describe the bug If the field name is pId, the JSON data is not mapped to an object using @RequestBody annotation. Lombok's @Getter, @Setter annotation, the naming ...

github.com

 

 

 

ํ•ด๊ฒฐ๋ฒ•

๋‚ด์šฉ์€ ๊ต‰์žฅํžˆ ๊ธธ์ง€๋งŒ, ์‚ฌ์‹ค ํ•ด๊ฒฐ๋ฒ•์€ ๊ต‰์žฅํžˆ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

 

1. @JsonProperty ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ (JacksonAnnotationIntrospector ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ)

 

 

2. Lombok ์–ด๋…ธํ…Œ์ด์…˜ ๋Œ€์‹ , IDE๊ฐ€ ๋งŒ๋“ค์–ด์ฃผ๋Š” getter/setter ์‚ฌ์šฉ

 

 

3. ํ•„๋“œ์˜ ๋„ค์ด๋ฐ์„ ๋ณ€๊ฒฝ(pId -> poId) ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉ์ด ์ž˜ ๋ฉ๋‹ˆ๋‹ค.

 

 

 

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€