DevLog ๐ถ
[Spring] HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ, ์๋ต ๋ฐ์ดํฐ ์์ฑ, HttpMessageConverter์ ArgumentResolver ๋ณธ๋ฌธ
[Spring] HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ, ์๋ต ๋ฐ์ดํฐ ์์ฑ, HttpMessageConverter์ ArgumentResolver
dolmeng2 2022. 8. 17. 18:23- ๊น์ํ ๋์ '์คํ๋ง MVC 1ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ '์ ๋ณด๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค ๐
- ์ง๋ ํฌ์คํ ๊ณผ ์ด์ด์ง๋๋ค :D
- ์ง๋ ํฌ์คํ ์์๋ DispatcherServlet์ ๋์ ๊ณผ์ ๋ฐ Spring MVC์ ๊ธฐ๋ณธ ๊ตฌ์กฐ์ ๋ํด ์์๋ณด์๋ค.
- ์ด๋ฒ ํฌ์คํ ์์๋ ๋ณธ๊ฒฉ์ ์ผ๋ก Spring MVC์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ๋ํด์ ์์๋ณด์.
| Spring MVC - ๊ธฐ๋ณธ ๊ธฐ๋ฅ
- SpringMVC์์ ์ ๊ณตํ๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ๋ํด์ ์์๋ณด์.
[MappingController.java]
@RestController
public class MappingController {
private Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/hello-basic")
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
}
- @RestController = @Controller + @ResponseBody
- ๋ฐํ ๊ฐ์ HTTP ๋ฉ์์ง ๋ฐ๋์ ๋ฐ๋ก ์ ๋ ฅํ๊ธฐ ๋๋ฌธ์, ์คํ ๊ฒฐ๊ณผ๋ก ํ์ด์ง์ ok๋ผ๋ ๊ธ์๊ฐ ๋จ๊ฒ ๋๋ค.
- ๋จ์ํ @RequestMapping์ ์ฌ์ฉํ๋ฉด Http ๋ฉ์๋์ ๋ฌด๊ดํ๊ฒ ํธ์ถ๋๋ค. (์ด๋ค ๋ฉ์๋๋ ํ์ฉ)
โ @GetMapping
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
- ์ฐธ๊ณ ๋ก, ์ด ๋ ๊ฐ์ง๋ ์์ ํ ๋์ผํ๋ค. ๊ทธ๋ฌ๋, ์ฌ๋งํ๋ฉด ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ๊ฒ ๋ ์ง๊ด์ ์ด๋ค.
- ์ด๋ฌ๋ฉด ํด๋น HTTP ๋ฉ์๋๋ก ๋ค์ด์ค๋ ์์ฒญ๋ง ๋ฐ๊ฒ ๋๋ค.
โ @PathVariable
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
- ์ด๋ฐ ์์ผ๋ก ๊ฒฝ๋ก ๋ณ์๋ ์ฌ์ฉํ ์ ์๋ค.
โญ data๋์ ์ userId๋ผ๊ณ ์ฌ์ฉํ๋ฉด @Pathvariable์ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ตํ ์ ์๋ค. (๊ทธ๋๋ ์ฌ๋งํ๋ฉด ์จ์ฃผ๋ ๊ฒ ๋ณด๊ธฐ๋ ํธํ๋ค)
- @PathVariable์ ๋ค์ค์ผ๋ก๋ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค. ์ด๋ฆ๋ง ๋ง์ถฐ์ฃผ๋ฉด ๋๋ค!
โ @GetMapping - params
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
- ์ ์ฌ์ฉํ์ง๋ ์์ง๋ง, ํ๋ผ๋ฏธํฐ์ mode=debug๋ผ๋ ๊ฒ ๋ค์ด๊ฐ์ผ ํด๋น ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค.
- ์ฆ, ๋จ์ํ /mapping-param์ผ๋ก๋ ํธ์ถ๋์ง ์๊ณ /mapping-param?mode=debug๋ก ํด์ผ ํธ์ถ๋๋ค.
- ํน์ ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๋ ํธ์ถ๋ ์ ์๋๋ก ์กฐ๊ฑด์ ๋ง๋ถ์ด๋ ๊ฒ์ด๋ค.
- ์ฐธ๊ณ ๋ก, !mode๋ mode!=debug ๊ฐ์ด ์กฐ๊ฑด์ ์ค ์๋ ์์ผ๋ฉฐ ๋ค์ค ์กฐ๊ฑด๋ ๊ฐ๋ฅํ๋ค.
โ @GetMapping - headers
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
- ํน์, ํน์ HTTP ํค๋๊ฐ ์๋์ง ์๋์ง์ ๋ฐ๋ผ์๋ ๊ฐ๋ฅํ๋ค.
โ ๋ฏธ๋์ด ํ์ ์กฐ๊ฑด ๋งคํ
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
- ์ด๋ฐ ์์ผ๋ก content-type์ ๊ธฐ๋ฐ์ผ๋ก ๋ฏธ๋์ด ํ์ ์ ๋งคํํ ์ ์๋ค.
(ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์๊ฒ ๋ณด๋ด๋ ๋ฐ์ดํฐ ํ์ ์ ๋ช ์)
- ๋ฏธ๋์ด ํ์ ์ด ๋ค๋ฅด๋ฉด 415๊ฐ ๋์จ๋ค (Unsupported Media Type)
- "application/*" ์ด๋ฐ ์์ผ๋ก๋ ํํ์ด ๊ฐ๋ฅํ๋ค.
- ์ฌ๋ฌ ๊ฐ๋ ๊ฐ๋ฅํ๋ค. consumes = {"text/plain", "application/*"}
- Content-type์ ๊ฒฝ์ฐ ํด๋น ํค๋์ ๋ฐ์ดํฐ๊ฐ ์ค์ง ์์์ ๋ ์๋ฒ ์ธก์์ ๊ฑฐ์ ํ ์ ์๋๋ก ํ๋ ๋งค๊ฐ์ฒด ์ ๋๋ก ์๊ฐํ๋ฉด ๋๋ค.
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
- ๋ฐ๋๋ก Accept ํค๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฏธ๋์ด ํ์ ์ ๋งคํํ ์ ์๋ค.
(์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํํ๋ ๋ฐ์ดํฐ ํ์ ๋ช ์)
- Accept์ ๊ฒฝ์ฐ ํด๋ผ์ด์ธํธ๊ฐ ์๋ต์ ๋ฐ์ ๋ ์ํ๋ ๋ฐ์ดํฐ ํ์ ์ ์ง์ ํ๋ ๊ฒ์ด๋ค.
- ๋ง์ง ์์ ๊ฒฝ์ฐ 406๊ฐ ๋์จ๋ค (Not Acceptable)
โ โญ API ์์
- ํ์ ๋ชฉ๋ก ์กฐํ (GET) /users
- ํ์ ๋ฑ๋ก (POST) /users
- ํ์ ์กฐํ (GET) /users/id/1
- ํ์ ์์ (PATCH) /users/id/1
- ํ์ ์ญ์ (DELETE) /users/id/1
๐ฉ RESTful API ์ค๊ณ (๋ด๊ฐ ๋ณด๋ ค๊ณ ์ ๋ฆฌ..)
- URI ๊ท์น
- ๋ง์ง๋ง์ /์ ๋ถ์ด๊ธฐ
- _๋์ ์ -๋ฅผ ์ฌ์ฉํ๊ธฐ (์๋ฌธ์ ์ฌ์ฉ, -์ผ๋ก ์๋ ํํ)
- ๋ฉ์๋์ ํ์ (get, delete...)๋ uri์ ํฌํจํ์ง ์์ ๊ฒ.
- POST, GET, PUT, DELETE๋ ๋ฐ๋์ ์ฌ์ฉ, OPTIONS, HEAD, PATCH๋ ์ถ๊ฐ์ ์ผ๋ก ์ฌ์ฉํ ๊ฒ.
- options๋ ํ์ฌ end-point๊ฐ ์ ๊ณต ๊ฐ๋ฅํ api method๋ฅผ ์๋ตํ๋ค.
- head๋ ์์ฒญ์ ๋ํ header ์ ๋ณด๋ง ์๋ตํ๋ฉฐ body๊ฐ ์๋ค.
- ์์์ ์ผ๋ถ๋ฅผ ์์ ํ ๋๋ put ๋์ ์ patch๋ฅผ ์ฌ์ฉํ๋ค.
์๋ฏธ์ ๋ง๋ Http status๋ฅผ ๋ฆฌํดํ๋ฉฐ, status ๋ง์ผ๋ก ์ํ ์๋ฌ๋ฅผ ๋ํ๋ผ ๊ฒ. (์๋ต ๊ฐ์ฒด์ ์ค๋ณต ํ์ X)
: ์ด๊ฑฐ๋ ์ข ์ ๋งคํ๋ฐ, 400 ๊ฐ์ ๊ฒฝ์ฐ๋ ํ๋ผ๋ฏธํฐ ์์น๋ ์ฌ์ฉ์ ์ ๋ ฅ ๊ฐ, ์๋ฌ ์ด์ ๋ฅผ ๋ฐ๋์ ์๋ฆฌ๋ ๊ฒ ์ข๋ค.
ex) location (ํ๋ผ๋ฏธํฐ ์์น - body, path, query) / param (์ด๋ค ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ?) / value (์ฌ์ฉ์ ์ ๋ ฅ๊ฐ) / msg (์ค๋ฅ ๋ฉ์์ง)
500 ์๋ฌ์ ๊ฒฝ์ฐ ์ฌ์ฉ์์๊ฒ ๋ํ๋ด์๋ ์ ๋๋ค. API server๋ ๋ฐ์ ๊ฐ๋ฅํ ๋ชจ๋ ์๋ฌ๋ฅผ ํธ๋ค๋งํ ๊ฒ. (์น ์๋ฒ ์๋ฌ ์ ์ธ)
- ๊ตฌ์ฑ ์์
- ๋ณ๊ฒฝ๋ ๋ฆฌ์์ค์ ์ํ ๊ด๊ณ (rel) : self -> ํ์ฌ ์์ ์ URL
- ์์ฒญ URL (href)
- ์์ฒญ Method (method)
๊ทธ ์ธ์ ๋ ์์ธํ ์ฌํญ์ ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค :D
| HTTP ์์ฒญ - ๊ธฐ๋ณธ, ํค๋ ์กฐํ
- Http ํค๋ ์ ๋ณด๋ฅผ ์กฐํํด๋ณด์.
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader("host") String host,
@CookieValue(value = "myCookie", required = false) String cookie
) {
log.info("request={}", request);
log.info("response={}", response);
log.info("httpMethod={}", httpMethod);
log.info("locale={}", locale);
log.info("headerMap={}", headerMap);
log.info("header host={}", host);
log.info("myCookie={}", cookie);
return "ok";
}
- ์ฌ๊ธฐ์ @RequestHeader๋ฅผ ์ฌ์ฉํ๋ฉด MultiValueMap ํ์์ผ๋ก HTTP ํค๋๋ฅผ ์กฐํํ ์ ์๋ค.
- MultiValueMap์ ํ๋์ ํค์ ์ฌ๋ฌ ๊ฐ์ ๋ฐ์ ๋ ์ฌ์ฉํ๋ค.
- HTTP header๋ HTTP ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์ฒ๋ผ ํ๋์ ํค์ ์ฌ๋ฌ ๊ฐ์ ๋ฐ์ ๋ ์ฌ์ฉํ๋ค.
- map.get("a")๋ผ๊ณ ํ์ ๋ Listํ์ผ๋ก ๋ฐํ๋๋ค!
- ํน์, ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ํน์ HTTP ํค๋ ๊ฐ์ ์กฐํํ ์ ์๋ค.
- ํน์ ์ฟ ํค๋ฅผ ์กฐํํ๊ธฐ ์ํด์๋ @GetCookie๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
- ํ์ ๊ฐ์ด๋ฉด required=true ์ต์ ์ ์ค๋ค.
๐ฉ ๊ทธ ์ธ์ ๋ค์ํ ์ต์ ์ ๊ณต์ ๋ฌธ์๋ฅผ ํ์ธํ์.
| HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ - ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ, HTML Form
- ํด๋ผ์ด์ธํธ -> ์๋ฒ๋ก ๋ฐ์ดํฐ ์ ๋ฌ ์ ์ฃผ๋ก 3๊ฐ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ค.
- ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ(?) / HTML Form / HTTP message body
- ์์ 2๊ฐ์ง ๋ฐฉ๋ฒ์ ๋ ๋ค ํ์์ด ๊ฐ๊ธฐ ๋๋ฌธ์ ๊ตฌ๋ถ์์ด ์กฐํ๊ฐ ๊ฐ๋ฅํ๋ฉฐ, ์ด๋ฅผ ์์ฒญ ํ๋ผ๋ฏธํฐ ์กฐํ๋ผ๊ณ ํ๋ค.
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
log.info("username={}, age={}", username, age);
response.getWriter().write("ok");
}
[hello-form.html] - /resources/static/basic ๋ฐ์ ๋์๋ค.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/request-param-v1" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">์ ์ก</button>
</form>
</body>
</html>
- form์ ํ์ฉํด์ ๋ณด๋ด์ฃผ๊ณ , HttpServletRequest๋ฅผ ํ์ฉํด์ ํ๋ผ๋ฏธํฐ ๊ฐ์ ๊ฐ์ ธ์์ฃผ์๋ค.
- response๋ฅผ ํตํด์ write๋ ํด์ฃผ์๋ค.
- ๊ทธ๋ฌ๋, ์ด ๋ฐฉ๋ฒ์ ๋งค์ฐ ๊ท์ฐฎ๋ค. ์คํ๋ง์์ ์ ๊ณตํ๋ @RequestParam์ ์ฌ์ฉํด๋ณด์.
โ @RequestMapping / @ResponseBody
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}, age={}", memberName, memberAge);
return "ok";
}
- โญ @RequestParam: ํ๋ผ๋ฏธํฐ์ ์ด๋ฆ์ผ๋ก ๋ฐ์ธ๋ฉ์ ํด์ค๋ค.
- @ResponseBody: View ์กฐํ๋ฅผ ๋ฌด์ํ๊ณ , Http ๋ฉ์์ง ๋ฐ๋์ ํด๋น ๋ด์ฉ์ ์ง์ ์ ๋ ฅํด์ค๋ค!
- ํ๋ผ๋ฏธํฐ ์ด๋ฆ๊ณผ ๋ณ์๋ช ์ด ๋์ผํ ๊ฒฝ์ฐ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ตํ ์ ์๋ค (@RequestParam๋ง ์จ๋ ๋จ)
- ๋ํ, String, Integer ๊ฐ์ ๋จ์ ํ์ ์ด๋ฉด @RequestParam๋ ์๋ต ๊ฐ๋ฅํ๋ค! (๊ทธ๋๋ ์ฌ๋งํ๋ฉด ์จ์ฃผ์)
- ์ด ๊ฒฝ์ฐ, required=false ์ต์ ์ด ์ ์ฉ๋๋ค. ํ๋ผ๋ฏธํฐ๊ฐ ํ์์ ์ผ๋ก ๋ค์ด๊ฐ์ผ ํ๋ค๋ฉด required=true ์ต์ ์ ์ง์ ํด์ฃผ์.
๐ฉ์ฌ๋ฌ ๊ฐ์ง ๊ฒฝ์ฐ
/request-param
: ํ๋ผ๋ฏธํฐ๊ฐ ์๊ธฐ ๋๋ฌธ์ 400 ๋ฐ์.
/request-param?username=
: ์ด ๊ฒฝ์ฐ ๋น ๋ฌธ์๊ฐ ์๋ค๊ณ ์ธ์ํ๊ณ ํต๊ณผ
- primitive ํ์ ์ null์ ์ ๋ ฅํ ์ ์๊ธฐ ๋๋ฌธ์ (500 ์์ธ) reference ํ์ ์ผ๋ก ๋ฐ๊พธ๊ฑฐ๋, defaultValue ์ฌ์ฉํด์ฃผ๊ธฐ.
- defaultValue์ ๊ฒฝ์ฐ ํ๋ผ๋ฏธํฐ์ ๊ฐ์ด ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ๊ฐ์ผ๋ก ์ ์ฉํด์ค ์ ์๋ค.
- ๋น ๋ฌธ์์ ๊ฒฝ์ฐ์๋ defaultValue๊ฐ ์ ์ฉ๋๋ค.
- required ์ต์ ์ ์๋ฏธ๊ฐ ์์ด์ง๋ค.
โ @RequestParam - Map์ผ๋ก ๋ฐ๊ธฐ
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
log.info("username={}, age={}", paramMap.get("username"),
paramMap.get("age"));
return "ok";
}
- ํ๋ผ๋ฏธํฐ๋ฅผ Map ํ์์ผ๋ก๋ ๋ฐ์ ์ ์๋ค.
- ํ๋ผ๋ฏธํฐ์ ๊ฐ์ด ์ฌ๋ฌ ๊ฐ๋ฉด MultiValueMap์ ์ฌ์ฉํด์ ๋ฐ์ ์ ์๋ค.
| HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ - @ModelAttribute
- ๋ณดํต ๊ฐ๋ฐ์ ์งํํ๋ฉด ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์์ ํ์ํ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ,
๊ทธ ๊ฐ์ฒด์ ๊ฐ์ ๋ฃ์ด์ฃผ๋ ์์ ์ ์งํํ ํ ๋ฐ ์คํ๋ง์์๋ ์ด๋ฅผ ์๋ํํ๋ค.
[HelloData.java] - ์ด์ ์ ์ฌ์ฉํ์๋ HelloData์ด๋ค.
@Data
public class HelloData {
private String username;
private int age;
}
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(),
helloData.getAge());
return "ok";
}
- @ModelAttribute HelloData helloData
1. HelloData ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
2. ์์ฒญ ํ๋ผ๋ฏธํฐ์ ์ด๋ฆ์ผ๋ก HelloData ๊ฐ์ฒด์ ํ๋กํผํฐ๋ฅผ ์ฐพ๋๋ค. ์ฌ๊ธฐ์๋ username, age๋ฅผ ์ฐพ๊ฒ ๋๋ ๊ฒ.
3. ์ดํ, ํด๋น ํ๋กํผํฐ์ Setter๋ฅผ ์ฐพ์์ ํ๋ผ๋ฏธํฐ์ ๊ฐ์ ๋ฐ์ธ๋ฉํ๋ค. (setUsername(), setAge())
- ๋ฌผ๋ก , ModelAttribute ์ญ์ ์๋ต์ด ๊ฐ๋ฅํ๋ค.
โญ ๊ธฐ๋ณธ์ ์ผ๋ก String, int, Integer ๊ฐ์ ํ์ ์ @RequestParam์ผ๋ก, ๊ทธ ์ธ๋ @ModelAttribute๋ก ์ง์ ๋๋ค.
cf) ArgumentResolver๋ก ์ง์ ๋์ด ์๋ ์ ๋ค์ ์ ์ธ!
๐ฉ @RequestParam vs @ModelAttribute
- @RequestParam์ ์์ฒญ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์์ฃผ๋ ์ด๋ ธํ ์ด์ ,
@ModelAttribute๋ ์์ฒญ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์์ ํน์ ๊ฐ์ฒด์ ํด๋น ๊ฐ์ setter๋ฅผ ํตํด ๋ฐ์ธ๋ฉ ํ Model ๊ฐ์ฒด์ ๋ด์์ฃผ๋ ์ด๋ ธํ ์ด์ .
- ๊ทธ๋ ๊ธฐ ๋๋ฌธ์, @RequestParam์๊ฒฝ์ฐ ๊ฐ์ฒด ์์ฑ ๋ฐ ๋ฐ์ธ๋ฉ ์์ ์ ์ง์ ํด์ผ ํ์ง๋ง,
@ModelAttribute๋ ๊ฐ์ฒด ์์ฑ ๋ฐ ๋ฐ์ธ๋ฉ, Model์ ๋ด์์ฃผ๋ ์์ ๊น์ง ์๋์ผ๋ก ์งํํด์ค๋ค!
| HTTP ์์ฒญ ๋ฉ์์ง - ๋จ์ ํ ์คํธ
- Http message body์ ๋ฐ์ดํฐ๋ฅผ ๋ด์์ ์์ฒญํ ๊ฒฝ์ฐ, ์ด๋ป๊ฒ ์ฒ๋ฆฌํด์ผ ํ ๊น?
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request,
HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream,
StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
response.getWriter().write("ok");
}
- ๋จ์ํ Http ๋ฉ์์ง ๋ฐ๋์ ๋ฐ์ดํฐ๋ฅผ inputStream์ ํ์ฉํด์ ์ฝ๋ ๋ถ๋ถ์ด๋ค.
- ๋ฐ์ดํธ ์ฝ๋์ด๊ธฐ ๋๋ฌธ์ ์ธ์ฝ๋ฉ ํ์ ๋ ์ง์ ํด์ค์ผ ํ๋ค.
โ InputStream, OutputStream ์ฌ์ฉํ๊ธฐ
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter)
throws IOException {
String messageBody = StreamUtils.copyToString(inputStream,
StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
responseWriter.write("ok");
}
- ์กฐ๊ธ ๋ ๊ฐ๋จํ๊ฒ InputStream, OutputStream์ ํ๋ผ๋ฏธํฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
- InputStream์ HTTP ์์ฒญ ๋ฉ์์ง ๋ฐ๋์ ๋ด์ฉ์ ์ง์ ์กฐํํ ์ ์์ผ๋ฉฐ, OutputStream์ ์๋ต ๋ฉ์์ง์ ๋ฐ๋์ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํ ์ ์๋ค.
- ์ฌ๊ธฐ์๋ ์์ ๋์ผํ๊ฒ ๋ง์ถฐ์ฃผ๊ธฐ ์ํด, HTTP ์์ฒญ ๋ฉ์์ง ๋ฐ๋์ ๋ด์ฉ์ ์ธ์ฝ๋ฉ ํด์ ์ ์ด์ฃผ์๋ค.
โ HttpEntity - RequestEntity / ResponseEntity
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
- ํน์ Http header์ body์ ์ ๋ณด๋ฅผ ์กฐํํ๊ธฐ ์ํด HttpEntity๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
- ๋ฉ์์ง ๋ฐ๋์ ์ ๋ณด๋ฅผ ์ง์ ์กฐํํ๋ ๊ฒ์ด๋ฉฐ, ์์ฒญ ํ๋ผ๋ฏธํฐ ์กฐํ ๊ธฐ๋ฅ๊ณผ๋ ๊ด๋ จ์ด ์๋ค.
- .getBody()๋ฅผ ํตํด ์ป์ด์ฌ ์ ์๋ค.
- .getHeader()๋ฅผ ํตํด์ ํค๋ ์ ๋ณด๋ ์กฐํํ ์ ์๋ค.
- HttpEntity์ ํ๋ผ๋ฏธํฐ๋ก ๋ฉ์์ง ๋ฐ๋ ์ ๋ณด๋ฅผ ์ ๋ ฅํ์ฌ ๋ฐํํ ์ ์๋ค.
- ํน์ RequestEntity์ ResponseEntity๋ฅผ ์ฌ์ฉํด๋ ๋๋ค. (HttpEntity๋ฅผ ์์๋ฐ์)
- RequestEntity<String> httpEntity / return new ResponseEntity<>("ok")
- RequestEntity์ ๊ฒฝ์ฐ HttpMethod๋ url ์ ๋ณด๋ฅผ ์ถ๊ฐํ ์ ์์ผ๋ฉฐ, ResponseEntity๋ HTTP ์ํ ์ฝ๋๋ฅผ ์ค์ ํ ์ ์๋ค.
โ @RequestBody / @ResponseBody
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
- ์ด๋ ธํ ์ด์ ์ ํ์ฉํด์๋ ์กฐํํ ์ ์๋ค.
- @RequestBody๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์์ง ๋ฐ๋ ์ ๋ณด๋ฅผ ์ง์ ์กฐํํ ์ ์์ผ๋ฉฐ, @ResponseBody๋ฅผ ํตํด ๋ฐ๋ ์ ๋ณด๋ฅผ ์ง์ ๋ฐํํ ์ ์๋ค.
- ๋จ, ํค๋ ์ ๋ณด์ ๊ฒฝ์ฐ HttpEntity๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ @RequestHeader๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
๐ฉโญ ์์ฒญ ํ๋ผ๋ฏธํฐ ์กฐํ: @RequestParam. @ModelAttribute
Http ๋ฉ์์ง ๋ฐ๋ ์ง์ ์กฐํ: @RequestBody
| HTTP ์์ฒญ ๋ฉ์์ง - JSON
[RequestBodyJsonController.java]
@Slf4j
@Controller
public class RequestBodyJsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request,
HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream,
StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
}
- ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์์ธ servletRequest๋ฅผ ํตํด ๋ฉ์์ง ๋ฐ๋์์ ์ง์ ์ฝ์ด์๋ค.
- ์ดํ, objectMapper์ readValue()๋ฅผ ํ์ฉํ์ฌ ์๋ฐ ๊ฐ์ฒด๋ก ์ฝ์ด์จ ๋ฉ์์ง๋ฐ๋๋ฅผ ๋ณํ์์ผฐ๋ค.
โ RequestBody / ResponseBody ์ด์ฉํ๊ธฐ
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws
IOException {
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
- ๊ฐ๋จํ๊ฒ @requestBody๋ฅผ ํตํด์ ๋ฉ์์ง ๋ฐ๋์ ๋ด์ฉ์ ๊บผ๋ด์ฌ ์ ์๋ค.
- ํ์ง๋ง, ์ด ๋ฐฉ์๋ ๋ฌธ์๋ฅผ ๋ฐ์์ ๋ค์ json์ผ๋ก ๋ณํํ๋ ๊ณผ์ ์ ๊ฑฐ์ณ์ผ ํ๋ค.
โ @RequestBody์ ๋ฐ๋ก ๊ฐ์ฒด ์ง์ ํ๊ธฐ
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
- ์ด๋ ๊ฒ ๋ฐ๋ก HelloData๋ฅผ ์ง์ ํ ์ ์๋ค!
- ์ด๋ฐ ์์ผ๋ก HttpEntity๋ @RequestBody๋ฅผ ์ฌ์ฉํ๋ฉด, HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ๊ฐ HTTP ๋ฉ์์ง ๋ฐ๋์ ๋ด์ฉ์ ์ํ๋ ๋ฌธ์๋ ๊ฐ์ฒด ๋ฑ์ผ๋ก ๋ณํํด์ค๋ค.
- JSON ์ญ์ ๋ณํ์ ํด์ค๋ค!
- โญ ์ฐธ๊ณ ๋ก, @RequestBody์ ๊ฒฝ์ฐ ์๋ต์ด ๋ถ๊ฐ๋ฅํ๋ค.
- ์๋ตํด๋ฒ๋ฆฌ๋ฉด ์๋์ผ๋ก @ModelAttribute๊ฐ ์ ์ฉ๋๊ธฐ ๋๋ฌธ์, ์์ฒญ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฒ๋ฆฌํ๊ฒ ๋๋ค.
โ HttpEntity ์ฌ์ฉํ๊ธฐ
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
- ๋ฌผ๋ก , ์ด๋ฐ ์์ผ๋ก httpEntity๋ฅผ ์ด์ฉํด๋ ์ ๋์ํ๋ค.
โ ResponseBody ์์ฉํ๊ธฐ
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
- @ResponseBody๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น ๊ฐ์ฒด๋ฅผ HTTP ๋ฉ์์ง ๋ฐ๋์ ๋ฃ์ด์ค ์ ์๋ค. (๋ฌผ๋ก HttpEntity๋ ๊ฐ๋ฅ)
๐ฉ @RequestBody : JSON ์์ฒญ -> Http ๋ฉ์์ง ์ปจ๋ฒํฐ -> ๊ฐ์ฒด
@ResponseBody : ๊ฐ์ฒด -> Http ๋ฉ์์ง ์ปจ๋ฒํฐ -> JSON ์๋ต
| HTTP ์๋ต - ์ ์ ๋ฆฌ์์ค, ๋ทฐ ํ ํ๋ฆฟ, HTTP ๋ฉ์์ง
- ์คํ๋ง์์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ 3๊ฐ์ง๋ก ๋๋๋ค.
1) ์ ์ ๋ฆฌ์์ค
- ์น ๋ธ๋ผ์ฐ์ ์ ์ ์ ์ธ HTML, css, js ์ ๊ณต ์ ์ฌ์ฉ
2) ๋ทฐ ํ ํ๋ฆฟ
- ์น ๋ธ๋ผ์ฐ์ ์ ๋์ ์ธ HTML ์ ๊ณต ์ ์ฌ์ฉ
3) HTTP ๋ฉ์์ง
- HTTP API ์ ๊ณต ์ HTTP ๋ฉ์์ง ๋ฐ๋์ JSON ํ์์ผ๋ก ๋ฐ์ดํฐ ๋ณด๋ด๊ธฐ
โ ์ ์ ๋ฆฌ์์ค
- ์คํ๋ง ๋ถํธ์์ ์ ๊ณตํ๋ ์ ์ ๋ฆฌ์์ค ์์น ๊ฒฝ๋ก
- ์์ ์์น: src/main/resources
- ํ์ ๊ฒฝ๋ก๋ ๋ค์๊ณผ ๊ฐ๋ค.
/static, /public, /resources, /META-INF/resources
์ฆ, src/main/resources/static/basic/hello-form.html์ด๋ผ๋ ํ์ผ์ด ์๋ค๋ฉด,
์น ๋ธ๋ผ์ฐ์ ์์๋ localhost:8080/basic/hello-form.html์ผ๋ก ์คํ๋๋ค.
- ์ ์ ๋ฆฌ์์ค๋ ํด๋น ํ์ผ์ ๋ํ ๋ณ๋ค๋ฅธ ๋ณ๊ฒฝ ์์ด ๊ทธ๋๋ก ์๋น์ค๋๋ค.
โ ๋ทฐ ํ ํ๋ฆฟ
- ๋ทฐ ํ ํ๋ฆฟ์ ๊ฑฐ์ณ HTML์ด ์์ฑ๋๊ณ , ๋ทฐ๊ฐ ์๋ต์ ๋ง๋ค์ด์ ์ ๋ฌํ๋ค.
- ๊ธฐ๋ณธ ๊ฒฝ๋ก๋ ๋ค์๊ณผ ๊ฐ๋ค. (๋์ค์ ํ๋กํผํฐ ํ์ผ์์ classpath ๋ณ๊ฒฝํด์ฃผ๋ฉด ๋๋ค)
src/main/resources/templates
[hello.html] - /response/hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${data}">empty</p>
</body>
- model์์ 'data'๋ผ๋ key๋ฅผ ๊ฐ์ง๋ ๊ฐ์ ๊บผ๋ด์ empty ๋ถ๋ถ์ ๋ฃ์ด์ค๋ค.
[ResponseViewController.java]
@Controller
public class ResponseViewController {
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mv = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mv;
}
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!");
return "response/hello";
}
@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
model.addAttribute("data", "hello!");
}
}
- ์์ 3๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ๋ทฐ๋ฅผ ๋์ ์ผ๋ก ๋ณํํ ์ ์๋ค.
- ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ModelAndView๋ฅผ ํตํด์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ฃผ๊ณ ๋ทฐ ์ด๋ฆ์ ์ค์ ํด์ฃผ๋ ๋ฐฉ๋ฒ,
- ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ๋ชจ๋ธ์ ๋ฐ์ดํฐ ๋ฃ์ด์ฃผ๊ณ Stringํ์ผ๋ก ๋ทฐ์ ๋ ผ๋ฆฌ ์ด๋ฆ์ ๋ฐํํ๋ ๋ฐฉ๋ฒ,
- ๋จ, ์ด ๊ฒฝ์ฐ๋ @ResponseBody๊ฐ ์์ด์ผ ํ๋ค. ์์ผ๋ฉด ๊ทธ๋๋ก response/hello๊ฐ ํ๋ฉด์ ์ฐํ!
- 3๋ฒ์ ๊ถ์ฅํ์ง๋ ์์ง๋ง, ๋ชจ๋ธ์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ฃผ๊ณ void๋ก ๋ฐํํ๋ ๋ฐฉ๋ฒ.
- @Controller๊ฐ ๋ถ์ด ์๊ณ HttpServletResponse, outputStream ๊ฐ์ HTTP ๋ฉ์์ง ๋ฐ๋ ์ฒ๋ฆฌ ํ๋ผ๋ฏธํฐ๊ฐ ์์ผ๋ฉด,
@RequestMapping์ ๋ถ์ด ์๋ URL์ ์ฐธ๊ณ ํด์ ๋ ผ๋ฆฌ ๋ทฐ ์ด๋ฆ์ผ๋ก ์ฌ์ฉํ๊ฒ ๋๋ค. ๋จ, ์ด ๋ฐฉ๋ฒ์ ๊ถ์ฅํ์ง ์๋๋ค.
โ HTTP API, ๋ฉ์์ง ๋ฐ๋์ ์ง์ ์ ๋ ฅ
- HTTP ๋ฉ์์ง ๋ฐ๋์ JSON๊ณผ ๊ฐ์ ํ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ค์ด ๋ณด๋ธ๋ค.
- ์ ์ ๋ฆฌ์์ค, ๋ทฐ ํ ํ๋ฆฟ์ ์ฌ์ฉํด๋ HTTP ์๋ต ๋ฉ์์ง ๋ฐ๋์ HTML ๋ฐ์ดํฐ๊ฐ ๋ด๊ธฐ์ง๋ง, ์ด๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋ฐ๋ก ์ ๋ฌํ๋ ๊ฒฝ์ฐ์ด๋ค.
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
response.getWriter().write("ok");
}
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@GetMapping("/response-body-string-v3")
@ResponseBody
public String responseBodyV3() {
return "ok";
}
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setUsername("hi");
helloData.setAge(20);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
@GetMapping("/response-body-json-v2")
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("hihi");
helloData.setAge(22);
return helloData;
}
- ๋ชจ๋ ๋ค ํ์ด์ง์ ์ ์ฐํ๋ ๊ฑธ ๋ณผ ์ ์๋ค.
1) HttpServletResponse๋ฅผ ์ด์ฉํด์ ๋ฉ์์ง ๋ฐ๋์ ์ง์ ๋ฉ์์ง ์ ๋ฌํ๊ธฐ
2) ResponseEntity๋ฅผ ํ์ฉํด์ Http ๋ฉ์์ง ๋ฐ๋ ์ ๋ณด ๋ฃ๊ธฐ + ์๋ต ์ฝ๋ ์ค์
3) @ResponseBody๋ฅผ ์ฌ์ฉํ๊ธฐ
4) ResponseEntity ๋ฐํ - ์ด๋ ๊ฐ์ฒด๋ฅผ ๋ฃ์ผ๋ฉด JSON ํ์์ผ๋ก ๋ณํ๋์ด์ ๋ฐํ๋๋ค.
5) @ResponseBody + @ResponseStatus ์ฌ์ฉ - ๊ฐ์ฒด ๋ฐํ ๋ฐ ์๋ต ์ฝ๋๋ฅผ ์ค์ ํด์ค๋ค.
๐ฉ @RestController = @Controller + @ResponseBody
- HTTP ๋ฉ์์ง ๋ฐ๋์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ ฅํ๋ ๋ฐฉ๋ฒ์ผ๋ก, Rest API๋ฅผ ๋ง๋ค ๋ ์ฌ์ฉํ๋ ์ปจํธ๋กค๋ฌ.
๐ฉ ResponseEntity๋ Http header๋ฅผ ์กฐ๊ธ ๋ ํธ๋ฆฌํ๊ฒ ๋ณ๊ฒฝ ๊ฐ๋ฅ, ์ํ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํด์ผ ํ ๋ ์ฌ์ฉํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค. ๊ทธ์ธ์ ๊ธฐ๋ฅ์ ๊ฑฐ์ ๋์ผํ๊ธฐ ๋๋ฌธ์... ์ํ ์ฝ๋๋ฅผ ๋ณํํ๋ ๊ฒ ์๋๋ผ๋ฉด ๊ทธ๋ฅ @ResponseBody๋ฅผ ์ฌ์ฉํด๋ ์ข์ ๋ฏ.
- ๊ทผ๋ฐ ๋ณดํต CSR์ ๊ฒฝ์ฐ (๋ฆฌ์กํธ๋ ๊ทธ๋ฐ ๊ฑฐ ์ฌ์ฉ) 5๋ฒ ๋ฐฉ๋ฒ์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ๊ฒ ๊ฐ๋ค! (@RestController ์ด์ฉ) ๋๋ ๊ทธ๋ ๊ณ ...!
| HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ
- HTTP API์ฒ๋ผ JSON ๋ฐ์ดํฐ๋ฅผ HTTP ๋ฉ์์ง ๋ฐ๋์์ ์ง์ ์ฝ๊ฑฐ๋ ์ธ ๋, HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ๋ฅผ ์ด์ฉํ๋ค.
- @ResponseBody๋ฅผ ์ฌ์ฉํ๋ฉด viewResolver ๋์ ์ HttpMessageConverter๊ฐ ๋์ํ๋ค.
- ์ด๋, ๊ธฐ๋ณธ์ ์ธ ๋ฌธ์๋ StringHttpMessageConverter, ๊ฐ์ฒด๋ MappingJackson2HttpMessageConverter๊ฐ ๋์.
- ์คํ๋ง์ ๋ค์์ ๊ฒฝ์ฐ์ HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํ๋ค.
HTTP ์์ฒญ -> @RequestBody, HttpEntity / RequestEntityHTTP ์๋ต -> @ResponseBody, HttpEntity / ResponseEntity
[HttpMessageConverter.java]
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
- ์ฐ์ , canRead()์ canWrite()๋ฅผ ํตํด์ ์ฝ๊ณ ์ธ ์ ์๋ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค. (ํด๋น ํด๋์ค, ๋ฏธ๋์ด ํ์ ์ ์ง์ํ๋์ง)
- getSupportedMediaTypes๋ ์ง์ํ๋ ๋ฏธ๋์ด ํ์ ์ ๋ํ ๋ฆฌ์คํธ๋ฅผ ๋ฆฌํดํ๋ ๊ฒ.
- read, write๋ ๋ฉ์์ง ์ปจ๋ฒํฐ๋ฅผ ํตํด ์ค์ ๋ก ์ฝ๊ณ ์ฐ๋ ๋์์ ์ํํ๋ ํจ์์ด๋ค.
๐ฉ Http ์์ฒญ์ด ๋ค์ด์์ ๋
- ๋ง์ฝ ์ปจํธ๋กค๋ฌ์์ @RequestBody or HttpEntity๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ฌ์ฉํ๊ณ ์๋ค.
- canRead()๋ฅผ ํธ์ถํ์ฌ ๋์ ํด๋์ค ํ์ (Object ํ์ )์ ์ง์ํ๋์ง ํ์ธํ๋ค.
- Http ์์ฒญ์ Content-type์ ํ์ธํ์ฌ ํด๋น ๋ฏธ๋์ด ํ์ ์ ์ง์ํ๋์ง ํ์ธ.
- ํต๊ณผํ๋ค๋ฉด read()๋ฅผ ํตํด์ ๊ฐ์ฒด ์์ฑ ๋ฐ ๋ฐํ ์์ ์งํ
๐ฉ Http ์๋ต์ ์์ฑํ ๋
- ์ปจํธ๋กค๋ฌ์์ @ResponseBody๋ HttpEntity๋ก ๊ฐ์ด ๋ฐํ๋๋ค.
- canWrite()๋ฅผ ํธ์ถํ์ฌ ๋ฉ์์ง๋ฅผ ์ธ ์ ์๋์ง ํ์ธํ๋ค.
- ๋์ ํด๋์ค ํ์ ์ ์ง์ํ๋์ง ํ์ธํ๋ค.
- Http ์์ฒญ์ Accept์ ํ์ธํ์ฌ ํด๋น ๋ฏธ๋์ด ํ์ ์ ์ง์ํ๋์ง ํ์ธ. (ํน์ @RequestMapping์ produces ๋ถ๋ถ ํ์ธ)
- ํต๊ณผํ๋ค๋ฉด write()๋ฅผ ํตํด์ HTTP ์๋ต ๋ฉ์์ง ๋ฐ๋์ ๋ฐ์ดํฐ ์์ฑ
โ ์คํ๋ง์์ ์ ๊ณตํ๋ ๋ฉ์์ง ์ปจ๋ฒํฐ
- ByteArrayHttpMessageConverter
: Object ํ์ ์ byte[]๋ง, ๋ฏธ๋์ดํ์ ์ ๋ชจ๋ ๊ฒ์ ์ง์ํ๋ค.
ex) @RequestBody byte[] paramter -> ๋ชจ๋ ์์ฒญ์ ๋ฐ์ดํธ ๋ฐฐ์ด๋ก ๋ฐ์ ์ ์๋ค!
: ๋ํ์ ์ผ๋ก ํ์ผ ์ ๋ก๋ ๊ฐ์ ์์๋ฅผ ์๊ฐํด๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
: ์๋ต ์์๋ ๋ฏธ๋์ดํ์ ์ด application/octet-stream์ผ๋ก ์ค์ ๋๋ค.
- StringHttpMessageConverter
: Object ํ์ ์ String, ๋ฏธ๋์ดํ์ ์ ๋ชจ๋ ๊ฒ์ ์ง์ํ๋ค.
: Http ๋ณธ๋ฌธ์ String์ผ๋ก ๊ฐ์ ธ์ค๊ฑฐ๋(@RequestBody String param), ๋ฆฌํด ๊ฐ์ผ๋ก ์ฌ์ฉํ๋ฉด ๋ฌธ์์ด์ ์ ๋ฌํด์ค๋ค.
: ์ด๋ content-type์ text/plain์ด ๋๋ค.
content-type: application/json
@RequestMapping
void hello(@RequetsBody String data) {}
- FormHttpMessageConverter
: Object ํ์ ์ MultiValueMap<String, String>์ด๊ณ , ๋ฏธ๋์ดํ์ ์ application/x-www-form-urlencoded๋ง ์ง์ํ๋ค.
: Form-data๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋ ์ฌ์ฉํ์ง๋ง, ๋ณดํต ์ด ๊ฒฝ์ฐ์๋ @ModelAttribute๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค.
- โญ MappingJackson2HttpMessageConverter
: Jackson์ ObjectMapper๋ฅผ ํ์ฉํด์ Json๊ณผ ์ค๋ธ์ ํธ ์ฌ์ด์ ๋ณํ์ ์ง์ํด์ค๋ค. ๋ฏธ๋์ดํ์ ์ application/json์ ์ง์ํ๋ค.
: Object ํ์ ์ผ๋ก ๊ฐ์ฒด๋ HashMap์ ์ฌ์ฉํ๋ค.
content-type: application/json
@RequestMapping
void hello(@RequetsBody HelloData data) {}
| HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ๋ ์ด๋์์ ์ฌ์ฉ๋๋ ๊ฑธ๊น?
- ๋ฐ๋ก, @RequestMapping์ ์ฒ๋ฆฌํ๋ ํธ๋ค๋ฌ ์ด๋ํฐ์ธ RequestMappingHandlerAdapter์ ์๋ค.
์ฐ์ , ๋น์ฅ ์ฐ๋ฆฌ๊ฐ ์์ฑํ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ณด์. @RequestBody, HttpEntity, InputStream, Model ๋ฑ ๋ค์ํ ํ๋ผ๋ฏธํฐ๊ฐ ์กด์ฌํ๋ค.
-> ์ด๋ค์ ๋ฐ๋ก 'ArgumentResolver'๊ฐ ์ฒ๋ฆฌํด์ค๋ค!
๐ฉ RequestMappingHandlerAdapter๋ ArgumentResolver๋ฅผ ํธ์ถํด์ ์ปจํธ๋กค๋ฌ๊ฐ ํ์๋ก ํ๋ ๋ค์ํ ํ๋ผ๋ฏธํฐ ๊ฐ์ ์์ฑํ๋ค.
- ์คํ๋ง์์ ์ ๊ณตํ๋ ArgumentResolver๋ ๋งค์ฐ ๋ง๋ค. (์ ํํ ์ด๋ฆ์ HandlerMethodArgumentResolver)
[HandlerMethodArgumentResolver.java]
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
- supportsParameter๋ฅผ ํตํด ํด๋น ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ํ๋์ง, resolveArgument๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๋๊ฒจ์ฃผ๋ ๊ฒ์ด๋ค!
- ๋์ถฉ ๋ญ ์ด๋ฐ ์์ผ๋ก ๋๊ฒ ๋ง๋ค๋ ๊ฒ...!
- ์๋ฌดํผ, ํด๋น ํ๋ผ๋ฏธํฐ ํ์(์ด๋ ธํ ์ด์ )์ ์ฒ๋ฆฌํด์ฃผ๋ ArgumentResolver๋ค์ด ๊ต์ฅํ ๋ง์ด ์กด์ฌํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค!
- ๋ฌผ๋ก , ์ฐ๋ฆฌ๊ฐ ์ปค์คํ ํด์ ์ํ๋ ArgumentResolver๋ ๋ง๋ค์ ์๋ค.
๐ฉ๋ง์ฐฌ๊ฐ์ง๋ก, ์ปจํธ๋กค๋ฌ์ ์๋ต๊ฐ๋ ์ฒ๋ฆฌํด์ฃผ๋ Handler๊ฐ ์กด์ฌํ๋ค.
- HandlerMethodReturnValueHandler, ์ค์ฌ์ ReturnValueHandler๋ผ๊ณ ํ๋ค.
[HandlerMethodReturnValueHandler.java]
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
- ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฆฌํดํ์ ์ ์ง์ํ๋์ง ํ์ธํ๊ณ , ์ง์ํ๋ค๋ฉด ์ํ๋ ๋ฐ์ดํฐ ํฌ๋งท์ผ๋ก ๋ณ๊ฒฝํด์ค๋ค.
- ์ฌ๊ธฐ์ ํ๋ผ๋ฏธํฐ ๊ฐ์ผ๋ก ๋ค์ด๊ฐ๋ returnValue, ๋ชจ๋ธ๊ณผ ๋ทฐ๋ฅผ ์ฒ๋ฆฌํ๋ mavContainer, ๊ทธ๋ฆฌ๊ณ webRequest๋ฅผ ํตํด ๊ฐ์ ๋ฐํํ๋ค.
- ๋ง์ฐฌ๊ฐ์ง๋ก ์ปค์คํ ์ด ๊ฐ๋ฅํ๋ฐ, ์ปค์คํ ์์ webRequest.getNativeResponse(HttpServletResponse.class)๋ฅผ ํ์ฉํ์ฌ HttpServletResponse๋ก ๋ฐํํด์ HTTP ๋ฉ์์ง ๋ฐ๋์ ๊ฐ์ ์ ๋๋ค๋ ์ง... ๊ทธ๋ฐ ๊ธฐ๋ฅ์ ํ ์ ์๋ค.
โ ๊ทธ๋์, HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ๋ ์ด๋ ์๋๋ฐ?
- HttpMessageConverter๋ ArgumentResolver๊ฐ ์ฌ์ฉํ๋ค!
๐ฉ HTTP ์์ฒญ์ด ๋ค์ด์์ ๋
- @RequestBody๋ฅผ ์ฒ๋ฆฌํ๋ ArgumentResolver๊ฐ ์๊ณ (RequestResponseBodyMethodProcessor),
HttpEntity๋ฅผ ์ฒ๋ฆฌํ๋ ArgumentResolver๊ฐ ์์ ๊ฒ์ด๋ค(HttpEntityMethodProcessor).
๊ทธ๋ฆฌ๊ณ , ์ด๋ฌํ ArgumentResolver๋ค์ HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํด์ ํ์ํ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค!
[RequestResponseBodyMethodProcessor.java]
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
///////// HERE! /////////////
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
- ์ฝ๋์์ Here์ด๋ผ๊ณ ๋์ด ์๋ ๋ถ๋ถ์์ ๋ณด๋ฉด readWithMessageConverters๋ฅผ ์ฌ์ฉํ๋ ๊ฑธ ๋ณผ ์ ์๋ค!
- ์ฌ๊ธฐ์ ๋ฆฌํด ๊ฐ์ผ๋ก Object๋ฅผ ์ฃผ๋ฉด์ ์ด๋ ํ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค๊ณ ๋ณผ ์ ์๋ค.
- HandlerMethodArgumentResolver๋ฅผ ๊ตฌํํ AbstractHttpMessageConverter๋ฅผ ๋ณด์.
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
...
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message = null;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
/////// #### HERE!
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
//// #### HERE! - canRead()
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
//// #### HERE! - read()
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
...
- ์ฝ๋๊ฐ ๊ธธ์ด์ ๋ณต์กํ์ง๋ง, ์ค๊ฐ์ for๋ฌธ์ ํตํด ๋ฉ์์ง ์ปจ๋ฒํฐ ๋ฐ๋ณต๋ฌธ์ ๋๋ฉด์ canRead()๋ฅผ ํตํด ์ฒดํฌํ๊ณ , ์ฒ๋ฆฌ ๊ฐ๋ฅํ๋ฉด read๋ก ๋ง๋ค์ด๋ด๋ ์์ด๋ค. ์ฆ, ๊ฒฐ๊ตญ argumentResolver๋ค์ด http ๋ฉ์์ง ์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํด์ ํ์ํ ๊ฐ์ฒด๋ฅผ ์์ฑํด์ค๋ค๊ณ ๋ณผ ์ ์๋ค!
๐ฉ HTTP ์๋ต ์์ฑํ ๋
- @ResponseBody์ HttpEntity๋ฅผ ์ฒ๋ฆฌํ๋ ReturnValueResolver๊ฐ ์๋ค. (์์ฒญ ๋๋ ๋ ๋ค ๋์ผํจ)
[RequestResponseBodyMethodProcessor.java]
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
///// #### HERE!
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
- ๋ง์ฐฌ๊ฐ์ง๋ก ์ฌ๊ธฐ์๋ messageConverter๋ฅผ ํธ์ถํด์ ์๋ต ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค์ด์ค๋ค!
- HttpMessageConverter๋ฅผ ๊ตฌํํ ํด๋์ค๋ ์ ๋ง ๋ง๋ค.
- ๊ทธ๋ฆฌ๊ณ , ์คํ๋ง์ ๋ค์์ ๋ชจ๋ ์ธํฐํ์ด์ค๋ก ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ ํ์ํ ๋ ํ์ฅํด์ ์ฌ์ฉํ๋ฉด ๋๋ค!
1) HandlerMethodArgumentResolver
2) HandlerMethodReturnValueResolver
3) HttpMessageConverter
- ๊ธฐ๋ฅ์ ํ์ฅํ ๋๋ WebMvcConfiguer๋ฅผ ์์๋ฐ์์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ฉด ๋๋ค.
(์ปค์คํ ์ด๋ ธํ ์ด์ ์ ๋ง๋ค์์ ๋ addArgumentResolver๋ฅผ ํตํด ์ปค์คํ ํ ArgumentResolver ์ถ๊ฐ ์ ๋๋ ๋ง์ด ์ฌ์ฉํ๋ค!)
๐ฉ ์ถ๊ฐ์ ์ผ๋ก ์กฐ๊ธ ํท๊ฐ๋ ค์ ์ ์ด๋๋ ๋ด์ฉ.
dispatcherServlet์ ๋์ ๊ณผ์ ์์,
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler())๋ฅผ ํตํด ModelAndView๋ฅผ ๋ฐํ๋ฐ๋ ๊ฑธ ๋ณผ ์ ์๋ค.๊ทธ๋ฌ๋, @ResponseBody์ ๊ฒฝ์ฐ viewResolver ๊ฐ์ ์์ ์ด ์ด๋ฃจ์ด์ง์ง ์๊ธฐ ๋๋ฌธ์,
์ด ๊ฒฝ์ฐ์๋ HttpMessageConverter๊ฐ response ๊ฐ์ฒด ๋ด์ ์๋ต ๋ฉ์์ง๋ฅผ ๋ฃ์ด๋๊ณ , mv์๋ null์ด ๋ค์ด๊ฐ๊ฒ ๋๋ค๊ณ ํ๋ค.
์... ์กฐ๊ธ ๋ ์ฐพ์๋ดค๋๋ฐ,
ํธ๋ค๋ฌ ์ด๋ํฐ๊ฐ ์คํ๋๋ฉด์(ha)
-> RequestMappingHandlerAdapter์์ invocableMethod.invokeAndHandle() ํธ์ถ
-> ํธ๋ค๋ฌ ์คํ, ๋ฐํํ์ ๊ฒฐ์
(๋ฉ์๋ ๋ด๋ถ)
getResponseStatusReason()์ด false์ด๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ณผ์ ์ผ๋ก mavContainer.setRequestHandled(false); ์ฒ๋ฆฌ.
returnValue์ ๊ฒฝ์ฐ returnValueHandler๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ด๋ RequestResponseBodyMethodProcessor๋ฅผ ํธ์ถํ๋ค.
์ฌ๊ธฐ์ mavContainer.setRequestHandled(true)๊ฐ ๋๊ณ ,
return์ผ๋ก ๋ด๊ฒจ์ง ๋ฉ์์ง๋ฅผ String ๊ฐ์ ๋ด์์ ServletServerHttpResponse Body์ ๋ด์ฉ์ ๊ธฐ๋กํ๊ฒ ๋๋ค.
-> ์ดํ, RequestMappingHandlerAdapter์์ return getModelAndiew(mavConatiner, modelFactory, webRequest); ์คํ
(๋ฉ์๋ ๋ด๋ถ)
if (mavContainer.isRequestHandled()) < ์ด๊ฒ true์ฌ์ ๊ฒฐ๊ณผ์ ์ผ๋ก null์ด ๋ฐํ๋๋ค!
--> ๊ทธ๋์ ModelAndView์ null์ด ๋ค์ด๊ฐ๋ ๊ฑฐ์๋ค...!
์ฐธ๊ณ ) ๋ ์์ธํ ๋ด์ฉ์ ์ด ๋ถ ๋ธ๋ก๊ทธ ๊ธ์ ๋ณด๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค!
- ์๋ฌดํผ, ์ด๋ฒ ํฌ์คํ ์ ์ฝ๋ ์ข ๊น๋ฉด์ ๋ณด๋๋ผ ์กฐ๊ธ ๋ ๊ฑธ๋ฆฐ ๊ฒ ๊ฐ๋ค.
- ๋ค์ ํฌ์คํ ์์๋ ์ง์ง ํ์ด์ง๋ฅผ ๋ง๋ค์ด๋ณด๋ฉด์ ์คํ๋ง MVC 1 ๊ฐ์๋ฅผ ๋๋ง์น๊ณ ์ ํ๋ค!