๊ด๋ จ ๊ธ - [Spring] ๐งจ JSON ์ญ์ง๋ ฌํ (JSON ํ์ฑ ์ค๋ฅ) & Bean Validation ์์ธ ์ฒ๋ฆฌ
JsonParseException์ด ๋ฐ์ํ์ ๋, ๋จ์ํ 400 ์๋ฌ๋ง ๋์ง๋ ๊ฒ๋ณด๋ค๋
์ด๋ค ์ค(line)์์ ์ด๋ค ๊ฐ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋์ง ๊ตฌ์ฒด์ ์ผ๋ก ์๋ต์ ๋ด์์ฃผ๊ณ ์ถ์๋ค.
ํ์ง๋ง, @RequestBody๋ ๋ด๋ถ์ ์ผ๋ก InputStream์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์
ํ ๋ฒ ์ฝํ๋ฉด ๋ ์ด์ ๋ค์ ์ฝ์ ์ ์๋ค.
๐ ๊ทธ๋์ Request Body๋ฅผ ์บ์ฑํ๊ณ , ์์ธ๊ฐ ๋ฐ์ํด๋ ๋ณธ๋ฌธ ๋ด์ฉ์ ๋ค์ ๊บผ๋ด ์ธ ์ ์๋๋ก ์ฒ๋ฆฌํด๋ณด๊ธฐ๋ก ํ๋ค.
์ด ๊ธ์์๋
๐ContentCachingRequestWrapper๋ฅผ ์ ์ฉํFilter๊ตฌ์ฑ
ย ย ย ย ย ์๋ฆฌ ๋ฐ ์๋ช ์ฃผ๊ธฐ(โ์ธ์ ์บ์ ์ ๋ฆฌ!?)
๐@RestControllerAdvice๋ฅผ ํ์ฉํ ์์ธ ์๋ต ์ปค์คํฐ๋ง์ด์ง
๋ฑ JsonParseException ์ฒ๋ฆฌ์ ๊ดํ ์ ์ฒด ํ๋ฆ์ ์ ๋ฆฌํ๊ณ , ํด๋น ๋ด์ฉ์ ๊ตฌํํด๋ณธ๋ค.
{ "hi": ""2, }JSON parse error: Unexpected character ('2' (code 50)) ... at line: 1, column: 10
๐งต ์ด๋ค text์์ ์์ธ๊ฐ ํฐ์ก๋์ง ๋ณด์ฌ์ฃผ๊ณ ์ถ์๋ฐโฆ?
โ ์ด๋ ๋ผ์ธ์์ ํฐ์ง ์์ธ์ธ์ง ์ ๊ณตํ๊ธฐ ์ํด RequestBody ์ถ์ถ ์๋ โ โ๋ถ๊ฐ!โ
๐ง ContentCachingRequestWrapper ์ฌ์ฉํ์ฌ Request Body ์บ์ฑ
ContentCachingRequestWrapper ๋?
HttpServletRequest๋ฅผ ๊ฐ์ธ๋ Wrapper๋ก,
InputStream๊ณผ Reader์์ ์ฝ์ ๋ด์ฉ์ ์บ์ฑํด ๋์ค์ ๋ฐ์ดํธ ๋ฐฐ์ด๋ก ๋ค์ ์ฝ์ ์ ์๋๋ก ๋์์ค๋ค.
๐ ์ฆ, ์์ธ๊ฐ ๋ฐ์ํ๋๋ผ๋
ย ย ย ย ย request.getContentAsByteArray()๋ฅผ ํตํด Request Body๋ฅผ ๋ก๊ทธ๋ก ๋จ๊ธธ ์ ์๊ฒ ๋๋ ๊ฒ!
๋ฉ๋ชจ๋ฆฌ ๊ฑฑ์ ์?
โ request์ lifecycle์ด ๊ฐ๊ณ , ์๋ต์ด ๋๋ ๋ ํจ๊ป GC ๋์์ด ๋๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฑฑ์ ํ์ x 
โ ๋์ค์ ๋ถํํ
์คํธ๋ฅผ ์งํํด๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
๐ป CachingRequestFilter ๊ตฌํ
๐ง ContentCachingRequestWrapper ๋ก request body ์บ์ฑ
CachingRequestFilter ๋ณต์ฌ ์ด๊ณณ

ContentCachingRequestWrapper๋ก ๊ฐ์DispatcherServlet์ผ๋ก ์บ์ฑ๋ request ๊ฐ์ฒด ์ ๋ฌDispatcherServlet์ผ๋ก ์ ์ด๋ฅผ ๋๊น (Spring MVC ์ง์
)servletContext์ ํํฐ ๋ฑ๋ก - ํํฐ ๋ฑ๋ก ์ฝ๋ ๋ณต์ฌ ์ด๊ณณ
servletContext.addFilter๋ก ์์์ ๊ตฌํํ ์บ์ฑํํฐ ๋ฑ๋ก"/*" - ๋ชจ๋ ์์ฒญ์ ๋ํด ํํฐ ์ ์ฉ๐ JsonErrorControllerAdvice ๊ตฌํ

@RestControllerAdvice(annotations = RestController.class)getRequestBody ๋ฉ์๋extractErrorLineFromJson ๋ฉ์๋line ๋ฒํธ๋ฅผ ์ถ์ถ{ "hi" : 1{
"lineNumber": 1,
"rejectValue": "{ \"hi\" : 1",
"message": "JSON ๋ฌธ๋ฒ ์ค๋ฅ - Unexpected end-of-input: expected close marker for Object (start marker at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1])"
}๋ง๋ฌด๋ฆฌ
์ด๋ ๊ฒ JsonParseException์ด ๋ฐ์ํ์ ๋, ์ค๋ฅ๊ฐ ๋ ๋ผ์ธ๊ณผ ๊ทธ ๋ด์ฉ์ ์๋ต์ ๋ด์์ฃผ๋ ์์ ์ ์ง์ ๊ตฌํํด๋ณด์๋ค.
๊ตฌ๊ธ์ ๋ ๋ค๋๋ Exception Handler ์ฝ๋๋ฅผ ํ์ฉํ๋ ๋ฐ์๋ ์ต์ํ์ง๋ง, ์ด๋ ๊ฒ ์ค์ ์์ธ ๊ตฌ์กฐ๋ฅผ ๋ฏ์ด๋ณด๊ณ ๊ฐ์ ํด๋ณธ ๊ฒฝํ์ ์ฒ์์ธ ๊ฒ ๊ฐ๋ค.
์ด ์์ ์ด ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์๊ฒ๋ ๋๋ฒ๊น ์ ๋ ์์ํ๊ฒ, ์๋ฒ ์ธก์์๋ ์ค๋ฅ ์ํฉ์ ๋ณด๋ค ๋ช ํํ ๊ธฐ๋กํ ์ ์๋ ๋ฐ์ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋ค.
์์ํ์ง๋ง ์ด๋ฐ ์ค์ฉ์ ์ธ ์์ธ ์ฒ๋ฆฌ ํ๋๊ฐ ์ฌ์ฉ์ ๊ฒฝํ๊ณผ ํ์ ์ ํ์ง์ ๋ฐ๊พธ๋ ์์์ ์ด ๋ ์ ์๋ค๊ณ ๋ฏฟ๋๋ค.
๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!! ํ๋ฆฐ ๋ด์ฉ์ด๋ ๋ ๋์ ๋ฐฉ์์ด ์๋ค๋ฉด ๋๊ธ๋ก ํธํ๊ฒ ์๋ ค์ฃผ์ธ์ ๐