Programming-[Backend]/Spring

[스프링 웹MVC] 11. 스프링 MVC - 요청 정보 처리 기능

컴퓨터 탐험가 찰리 2021. 8. 8. 21:36
728x90
반응형

 

요청 URL에 따라 Controller를 매핑해봤으니, 이제 받아온 요청의 정보들을 처리하는 기능들에 대해서 알아본다. 몇가지 어노테이션과 기본 내용들만 학습하면 된다.

 

 

 

1. 헤더 정보 조회

 

 

 

@RequestHeader

@RequestHeader 어노테이션을 이용하여 모든 헤더들의 정보를 MultiValueMap으로 받아올 수도 있고, 각 key값에 맞는 header 정보를 받아올 수도 있다. MultiValueMap은 Map과 유사한데, 하나의 key에 여러 값을 받을 수 있다. key값으로 조회하면 value값들의 배열을 반환한다. 요청 및 응답의 경우 같은 key에 여러 값을 받을 수 있으므로 MultiValueMap을 사용한다.

 

 

@CookieValue

쿠키 정보를 받아올 수 있다.

 

 

 

java/hello/springmvc/basic/request/RequestHeaderController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
@RestController
public class RequestHeaderController {
 
    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletRequest response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<StringString> headerMap,
                          @RequestHeader("host"String host,
                          @CookieValue(value = "myCookie", required = falseString 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";
    }
}
cs

 

 


 

2. 요청 파라미터 조회(쿼리 파라미터, HTML Form)

 

 

요청에 포함된 파라미터를 조회해본다. GET 형태의 쿼리 파라미터와 HTML Form 형식의 데이터에 대해서만 조회한다.

 

 

java/hello/springmvc/basic/request/RequestParamController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@Controller
public class RequestParamController {
 
    @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"));
 
 
        response.getWriter().write("ok");
    }
... 
cs

 

 

 

테스트를 위해서 postman을 이용해도 되지만, form 데이터 양식을 전달하는 html view 파일을 만든다. resources/static/basic/hello-form.html 위치에 작성한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!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>
 
cs

 

스프링부트를 사용하면 resources/static 폴더에 view 파일들이 담겨야되고, 이 파일들은 외부에 다 공개되는 파일들이다. url 경로는 만약 디렉토리가 resources/static/basic/hello-form.html 이라면, localhost:8080/basic/hello-form.html 으로 설정된다.

 

 

이제 아래 내용부터는 request-param-v1을 사용하기 편하도록 하나씩 개선해나간다.

 

 


 

 

v2 ~ v4 : @RequestParam 적용

 

@RequestParam은 HttpServletRequest.getParameter를 편리하게 이용할 수 있는 어노테이션이다. v3 에서는 @PathVariable 처럼 변수명과 파라미터명이 같으면 괄호안의 속성값을 생략할 수 있다는 것을 보여준다. v4에서는 v3와 같이 변수명과 파라미터명이 같은 상황에서는 @RequestParam 어노테이션 자체를 생략할 수 있다. 다만, 이것은 String, int, Integer와 같은 단순 타입일때만 생략이 가능하다. 다만 @RequestParam까지 생략해버리면 다른 사람이 봤을 때 헷갈릴 수도 있으므로 강사님은 @RequestParam은 생략하지 않는 것을 선호한다고 한다. 

 

※ @ResponseBody : 상위에 @Controller가 있더라도 @RestController로 변환하지 않고, 해당 router 부분에 적용함으로써 반환값을 View가 아닌 body로 변경해줄 수 있다. 다시말해, '@RestController = @Controller + 각 API 에@ResponseBody 적용' 이라고 할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@ResponseBody
    @RequestMapping("/request-param-v2")
    public String requestParamV2(
            @RequestParam("username") String memberName,
            @RequestParam("age") int memberAge) {
 
        log.info("username={}, age={}", memberName, memberAge);
        return "ok";
    }
 
    @ResponseBody
    @RequestMapping("/request-param-v3")
    public String requestParamV3(
            @RequestParam String username,
            @RequestParam int age) {
 
        log.info("username={}, age={}", username, age);
        return "ok";
    }
 
    @ResponseBody
    @RequestMapping("/request-param-v4")
    public String requestParamV4(String username, int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }
cs

 

 


 

required 속성과 defaultValue 속성 지정

 

필수값은 required 속성으로 지정해줄 수 있다. required 속성은 기본값이 true이며, false로 변경하면 요청 신호에 해당 파라미터가 넘어오지 않아도 컨트롤러에서 처리가 가능하다.

-> 넘어오지 않아야 null로 처리된다. 만약 username=""과 같이 빈 String으로 오면 값이 있는 것으로 본다.

 

defaultValue값을 지정하여 요청에 해당 필드 정보가 없을 때 기본으로 지정할 값을 설정할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ResponseBody
    @RequestMapping("/request-param-required")
    public String requestParamRequired(
            @RequestParam(required = true) String username,
            @RequestParam(required = false) Integer age) {
 
        log.info("username={}, age={}", username, age);
        return "ok";
    }
 
    @ResponseBody
    @RequestMapping("/request-param-default")
    public String requestParamDefault(
            @RequestParam(required = truedefaultValue = "guest"String username,
            @RequestParam(required = falsedefaultValue = "-1"int age) {
 
        log.info("username={}, age={}", username, age);
        return "ok";
    }
cs

 

 


 

 

Map객체, @ModelAttribute로 파라미터 한번에 받아오기

 

여러 개의 파라미터를 한번에 받아올 수 있는 방법이다. 각 파라미터값마다 앞에 @RequestParam을 붙여주면 불편하므로 @RequestParam에 Map 객체를 할당하여 여러 파라미터 값을 넣어줄 수 있다. 그리고 만약 HelloData.class와 같은 형태로 파라미터 값을 받아와서 필드에 set으로 매칭해주어야 할때는 @ModelAttribute를 사용하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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";
    }
 
    @ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV1(@ModelAttribute HelloData helloData) {
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }
 
    @ResponseBody
    @RequestMapping("/model-attribute-v2")
    public String modelAttributeV2(HelloData helloData) {
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }
cs

 

 

 

java/hello/springmvc/basic/HelloData.java

1
2
3
4
5
6
@Data
public class HelloData {
    private String username;
    private int age;
}
 
cs

 

 

@RequestParam을 여러개 써야하는 것을 하나로 통합해주고, HelloData 객체를 만들고 HelloData 객체에 파라미터의 값들을 set해주는 작업을 한 번에 다 처리해준다. @Data 어노테이션으로 처리되어있지만, 필드값에 해당하는 setter, getter 메서드 이름을 기반으로 파라미터를 찾아서 처리해주는 원리이다.

 

@ModelAttribute도 생략이 가능하다. 그러나 @RequestParam도 생략이 가능하므로 위에서 언급한 바와 같이 String, int, Integer와 같은 기본 타입은 @RequestParam으로 해석하고, HelloData와 같이 직접 작성한 타입은 @ModelAttribute로 해석하여 처리한다. 다만 HttpServletResponse 타입과 같이 Argument Resolver를 거치는 타입은 @ModelAttribute로 처리되지 않는다.

 

 

 

 


 

 

3. 요청 메시지 조회(http 요청 body-일반 text)

 

 

HTTP message body로 오는 경우, @RequestParam이나 @ModelAttribute를 사용할 수 없다(HTML Form 전송 방식 제외). 요청 Body의 정보를 조회하는 방법도 사용자의 편의에 따라 발전해왔다. 차례대로 그 과정을 살펴본다.

 

 

 

V1 : 기존 Servlet 방식

 

기존 Servlet을 공부했을때 사용하던 방식대로 request.getInputStream을 이용하여 Body 정보를 읽어보고, StreamUtils.copyToString으로 데이터를 변환하여 Body에 있는 정보를 읽어들인다.

 

 

 

java/hello/springmvc/basic/request/RequestBodyStringController.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@Controller
public class RequestBodyStringController {
 
    @PostMapping("/request-body-string-v1")
    public void requestBodyString(HttpServletRequest request, HttpServletResponse responsethrows IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
 
        log.info("messageBody={}", messageBody);
        response.getWriter().write("ok");
 
    }
}
cs

 

Postman 테스트 결과

 

 

V2 : 파라미터로 InputStream, Writer를 직접 받아온다

 

HttpServletResquest, HttpServletResponse 객체 전체가 필요한 것이 아니므로, 필요한 InputStream, Writer만 받아와서 처리할 수 있다.

 

1
2
3
4
5
6
@PostMapping("/request-body-string-v2")
    public void requestBodyStringV2(InputStream inputStream, Writer responseWriterthrows IOException {
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        log.info("messageBody={}", messageBody);
        responseWriter.write("ok");
    }
cs

 

 

 

V3 : HttpEntity 객체를 이용한다

 

Http 요청 및 응답의 정보를 담고 있는 HttpEntity 객체가 있다. 이를 이용하면 request, response의 정보를 조회하거나, 반환 객체로 활용할 수 있다.  

 

파라미터에 HttpEntity<>의 타입을 <String>으로 지정해주었다. 이렇게 HttpEntity는 타입을 지정해주면 타입에 맞게 변환작업을 해주기 때문에, StreamUtils.copyToString처럼 따로 변환을 해줄 필요가 없다.

 

강의에서 강조됬던 부분은, 이 HttpEntity도 위쪽 섹션에서 다룬 @RequestParam과 @ModelAttribute와는 아무런 상관이 없다는 점이다. 그래서 HttpEntity를 사용할때 헷갈리면 안된다.

 

1
2
3
4
5
6
@PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntitythrows IOException {
        String messageBody = httpEntity.getBody();
        log.info("messageBody={}", messageBody);
        return new HttpEntity<>("ok");
    }
cs

 

HttpEntity를 상속받은 RequestEntity<>, ResponseEntity<>를 파라미터와 반환값의 타입으로 사용해도 된다. 이때 유의할 사항은 아래 코드와 같이 ResponseEntity의 파라미터는 반드시 HTTP 상태정보를 입력해주어야 한다는 점이다.

 

return new ResponseEntity<>("ok", HttpStatus.ACCEPTED);

 

 

 

 

V4 : @RequestBody와 @ResponseBody 어노테이션을 활용한다

 

더 편하게 사용할 수 있는 방법이 @RequestBody와 @ResponseBody 어노테이션을 이용하는 방법이다. HttpEntity 객체를 사용할 때보다 훨씬 더 깔끔해진 것을 볼 수 있다. 이제껏 사용해왔던 @ResponseBody가 이런 과정에 의해서 발전되어온 것이다.

 

1
2
3
4
5
6
@ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody) {
        log.info("messageBody={}", messageBody);
        return "ok";
    }
cs

 

 

 

@ResponseStatus("HttpStatus 코드")

 

위와 같이 ResponseEntity<>를 사용할때는 Http 상태 코드값을 파라미터값을 통해 전달해준다. 그러나 HttpEntity<>를 사용하거나 @ResponseBody를 사용할때는 파라미터로 Http 상태 코드값을 반환해줄 수 없으므로 메서드 위에 @ResponseStatus("HttpStatus 코드")를 적용하는 방식으로 사용한다. 다만, 한 개의 메서드에서 if문 등 조건에 따라 다른 HttpStatus 코드가 반환되어야 하는 경우에는 ResponseEntity<>를 사용해야만 한다.

 


 

4. 요청 메시지 조회(http 요청 body-JSON 방식)

 

 

 

body에 JSON이 담겨져있는 경우도 일반 text 방식과 유사한 방식이다. 다만, 요청에 담긴 JSON 정보를 특정 객체의 필드값으로 매핑해야되기 때문에, ObjectMapper.readValue()를 이용한다. 전체 코드를 한번에 살펴보자.

 

 

V1 - V3

 

V1에서 V3로 가는 과정은 일반 text일때와 똑같다. 결국 @RequestBody와 @ResponseBody 어노테이션을 활용하면 된다. 다만 JSON의 필드값을 갖고 있는 HelloData 타입의 파라미터를 받을 수 있다는 점이 다르다.

 

V4, V5

 

V4는 일반 text와 동일하게 HttpEntity 객체를 활용할 수도 있다는 것을 표현한 것이다.

 

V5는 응답 객체의 타입도 지정할 수 있다는 것을 보여준다. V5의 postman 결과는 다음 그림과 같다.

 

 

 

 

java/hello/springmvc/basic/request/RequestBodyJsonController.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * {"username":"hello", "age":20}
 * content-type: application/json
 */
@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 helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
 
        response.getWriter().write("ok");
    }
 
    @ResponseBody
    @PostMapping("/request-body-json-v2")
    public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
 
        log.info("messageBody={}", messageBody);
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
 
        return "ok";
    }
 
    @ResponseBody
    @PostMapping("/request-body-json-v3")
    public String requestBodyJsonV3(@RequestBody HelloData data) {
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        return "ok";
    }
 
    @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";
    }
 
    @ResponseBody
    @PostMapping("/request-body-json-v5")
    public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        return data;
    }
cs

 

 

 


 

전체 핵심을 요약을 하자면, 쿼리파라미터로 넘어오는 요청은 @RequestParam, @ModelAttribute로 처리하고, Http Body로 넘어오는 요청 정보는 @RequestBody, @ResponseBody로 처리하면 된다.

 


참조

 

1. 인프런_스프링 MVC 1편 - 백엔드 웹개발 핵심 기술_김영한 님 강의

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1

728x90
반응형