DevLog ๐Ÿ˜ถ

[Spring] HTTP ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ, ์‘๋‹ต ๋ฐ์ดํ„ฐ ์ƒ์„ฑ, HttpMessageConverter์™€ ArgumentResolver ๋ณธ๋ฌธ

Back-end/Spring

[Spring] HTTP ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ, ์‘๋‹ต ๋ฐ์ดํ„ฐ ์ƒ์„ฑ, HttpMessageConverter์™€ ArgumentResolver

dolmeng2 2022. 8. 17. 18:23

- ๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ '์„ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค ๐Ÿ˜Š

 

์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ๋•Œ ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ์ˆ ์„ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์ดํ•ดํ•˜๊ณ , ์™„์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํ”„๋ง MVC์˜ ํ•ต์‹ฌ ์›๋ฆฌ์™€ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋” ๊นŠ์ด์žˆ๋Š” ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., -

www.inflearn.com


- ์ง€๋‚œ ํฌ์ŠคํŒ…๊ณผ ์ด์–ด์ง‘๋‹ˆ๋‹ค :D

 

[Spring] DispatcherServlet์˜ ๋™์ž‘ ๊ณผ์ •๊ณผ HanderMapping, HandlerAdapter, @RequestMapping

- ๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ '์„ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค ๐Ÿ˜Š ์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ๋•Œ ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ

cl8d.tistory.com

- ์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ๋Š” 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

 

RESTful API ์„ค๊ณ„ ๊ฐ€์ด๋“œ

1. RESTful API ์„ค๊ณ„ ๊ฐ€์ด๋“œ ๋ณธ ๋ฌธ์„œ๋Š” REST API๋ฅผ ์ข€ ๋” RESTful ํ•˜๊ฒŒ ์„ค๊ณ„ํ•˜๋„๋ก ๊ฐ€์ด๋“œํ•  ๋ชฉ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค. ๋”ฐ๋ผ์„œ, ๊ธฐ๋ณธ์ ์ธ REST API ๊ฐœ๋… ์„ค๋ช…์€ ์•„๋ž˜์˜ ๋งํฌ๋กœ ๋Œ€์‹ ํ•œ๋‹ค. REST API ์ œ๋Œ€๋กœ ์•Œ๊ณ  ์‚ฌ์šฉ

sanghaklee.tistory.com


 

| 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 ์˜ต์…˜์„ ์ค€๋‹ค.

 

๐Ÿšฉ ๊ทธ ์™ธ์— ๋‹ค์–‘ํ•œ ์˜ต์…˜์€ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์ž.

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more com

docs.spring.io


 

| 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์ด ๋“ค์–ด๊ฐ€๋Š” ๊ฑฐ์˜€๋‹ค...!

 

์ฐธ๊ณ ) ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ด ๋ถ„ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ๋ณด๋Š” ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค!

 

[Spring MVC] @ResponseBody ์‘๋‹ต์— ๋Œ€ํ•˜์—ฌ ์•Œ์•„๋ณด์ž(1)

API๋ฅผ ๋งŒ๋“ค๋•Œ, Jsonํ˜•ํƒœ์˜ ์‘๋‹ต๊ฐ’์„ ๋‚ด๋ ค์ฃผ๊ธฐ ์œ„ํ•˜์—ฌ @ResponseBody ๋ผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณธ๋ฌธ ์ž์ฒด๋ฅผ ์‘๋‹ต๊ฐ’์œผ๋กœ ๋‚ด๋ ค์ฃผ๊ธฐ์— ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ. ์ •ํ™•ํ•˜

duooo-story.tistory.com

 

- ์•„๋ฌดํŠผ, ์ด๋ฒˆ ํฌ์ŠคํŒ…์€ ์ฝ”๋“œ ์ข€ ๊นŒ๋ฉด์„œ ๋ณด๋Š๋ผ ์กฐ๊ธˆ ๋” ๊ฑธ๋ฆฐ ๊ฒƒ ๊ฐ™๋‹ค.

- ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ง„์งœ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋ฉด์„œ ์Šคํ”„๋ง MVC 1 ๊ฐ•์˜๋ฅผ ๋๋งˆ์น˜๊ณ ์ž ํ•œ๋‹ค!

 

Comments