[ 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 #6 ] 스프링 MVC - 기본 기능 (2)

김수호·2023년 9월 3일
0
post-thumbnail

지난 포스팅에 이어, 이번 포스팅에서는 5) ~ 10) 까지의 내용을 정리한다.

👉 목차는 다음과 같다.

1) 프로젝트 생성
2) 로깅 간단히 알아보기
3) 요청 매핑
4) 요청 매핑 - API 예시
5) HTTP 요청 - 기본, 헤더 조회
6) HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
7) HTTP 요청 파라미터 - @RequestParam
8) HTTP 요청 파라미터 - @ModelAttribute
9) HTTP 요청 메시지 - 단순 텍스트
10) HTTP 요청 메시지 - JSON

11) 응답 - 정적 리소스, 뷰 템플릿
12) HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
13) HTTP 메시지 컨버터
14) 요청 매핑 헨들러 어뎁터 구조
15) 정리

지난 포스팅을 통해서 매핑 방법을 이해했으니, 이제부터 HTTP 요청이 보내는 데이터들을 스프링 MVC로 어떻게 조회하는지 알아보자.


5) HTTP 요청 - 기본, 헤더 조회

이전에 서블릿 강의에서 HTTP 요청 메시지 헤더 정보등을 어떻게 조회하는지 알아보았었다. (서블릿이 여러 기능을 제공해줬었다.)

이번에는 스프링 MVC가 그것들을 더 편리하게 조회할 수 있는 여러가지 기능들을 제공한다. 따라서 그 중에서 먼저 기본, 헤더 조회에 대해서 알아볼 것이다.

 

👉 애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다. 이번 내용에서는 HTTP 헤더 정보를 조회하는 방법을 알아보자.

  • RequestHeaderController: src > main > java > hello > springmvc > basic > request 패키지를 생성하고, 내부에 RequestHeaderController 클래스를 생성하자. (생성 후 postman으로 실행해보자.)
    • 정상적으로 출력됨을 확인할 수 있다.
    • HttpMethod : HTTP 메서드를 조회한다. ( org.springframework.http.HttpMethod )
    • Locale : (가장 우선 순위가 높은) Locale 정보를 조회한다.
    • @RequestHeader MultiValueMap<String, String> headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
    • @RequestHeader("host") String host
      • 특정 HTTP 헤더를 조회한다.
      • 속성
        • 필수 값 여부: required
        • 기본 값 속성: defaultValue
    • @CookieValue(value = "myCookie", required = false) String cookie
      • 특정 쿠키를 조회한다.
      • 속성
        • 필수 값 여부: required
        • 기본 값: defaultValue

✔️ 참고

다음에는 본격적으로 요청 파라미터를 어떻게 받는지 알아보자.


6) HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

서블릿에서 학습했던 HTTP 요청 데이터를 조회하는 방법을 다시 떠올려보자.
그리고 서블릿으로 학습했던 내용을 스프링이 얼마나 깔끔하고 효율적으로 바꾸어주는지 알아보자.

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.

클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터
    • /url?username=hello&age=20
    • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식
  • POST - HTML Form
    • content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
    • 예) 회원 가입, 상품 주문, HTML Form 사용
  • HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용, JSON, XML, TEXT
    • 데이터 형식은 주로 JSON 사용
    • POST, PUT, PATCH를 주로 사용

 

👉 하나씩 알아보자.

요청 파라미터 - 쿼리 파라미터, HTML Form
HttpServletRequest의 request.getParameter()를 사용하면 다음 두가지 요청 파라미터를 조회할 수 있다.

  • GET, 쿼리 파라미터 전송
    • 예시) http://localhost:8080/request-param?username=hello&age=20
  • POST, HTML Form 전송
    • 예시)

GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다. 이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

지금부터 스프링으로 요청 파라미터를 조회하는 방법을 단계적으로 알아보자.

  • RequestParamController: src > main > java > hello > springmvc > basic > request 패키지 내부에 RequestParamController 클래스를 생성하자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • request.getParameter(): 여기서는 단순히 HttpServletRequest가 제공하는 방식으로 요청 파라미터를 조회했다.

 

Post Form 페이지 생성

먼저 테스트용 HTML Form을 만들어야 한다.
리소스는 /resources/static 아래에 두면 스프링 부트가 자동으로 인식한다. (/resources/static 은 외부에 공개되는 경로이다.)

  • /resources/static 디렉토리 아래 basic 디렉토리를 생성하고, 내부에 hello-form.html 파일을 생성하자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • (참고) Jar를 사용하면 webapp 경로를 사용할 수 없다. 이제부터 정적 리소스도 클래스 경로에 함께 포함해야 한다.

7) HTTP 요청 파라미터 - @RequestParam

스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다.

👉 코드로 확인해보자.

  • requestParamV2: 좀 전에 생성한 RequestParamController 클래스에 다음 코드를 추가하자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • @RequestParam : 파라미터 이름으로 바인딩.
      • @RequestParam의 name(value) 속성이 파라미터 이름으로 사용
        • @RequestParam("username") String memberName => request.getParameter("username")
    • (참고) @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력.
  • requestParamV3: RequestParamController 클래스에 다음 코드를 적용해보자.
    • 정상적으로 수행된 것을 확인할 수 있다.
    • HTTP 파라미터 이름이 변수 이름(변수명)과 같으면 @RequestParam(name="xx") 생략 가능
  • requestParamV4: RequestParamController 클래스에 다음 코드를 적용해보자.
    • 정상적으로 실행된 것을 확인할 수 있다.
    • String, int, Integer 등의 단순 타입이면 @RequestParam 도 생략 가능.
    • (주의) @RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다. required 옵션은 바로 다음에 설명한다.
    • (참고) 이렇게 애노테이션을 완전히 생략해도 되는데, 너무 없는 것도 약간 과하다는 주관적 생각이 있다. (@RequestParam 이 있으면 명확하게 요청 파리미터에서 데이터를 읽는다는 것을 알 수 있다.)

 

파라미터 필수 여부 - requestParamRequired

  • requestParamRequired: RequestParamController 클래스에 다음 코드를 적용해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • @RequestParam.required
      • 파라미터 필수 여부.
      • 기본값이 파라미터 필수( true )이다.
    • /request-param 으로 요청
      • username 이 없으므로 400 예외가 발생한다.
    • (주의) 파라미터 이름만 사용하는 경우
      • /request-param?username=: 파라미터 이름만 있고 값이 없는 경우, 빈문자로 통과 (null이 아니고 빈 문자열임.)
    • (주의) 기본형(primitive)에 null 입력
      • /request-param 요청
        • @RequestParam(required = false) int age
          • nullint 에 입력하는 것은 불가능(500 예외 발생)
          • 따라서 null 을 받을 수 있는 Integer 로 변경하거나, 또는 다음에 나오는 defaultValue 사용

 

기본 값 적용 - requestParamDefault

  • requestParamDefault: RequestParamController 클래스에 다음 코드를 적용해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • 파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다. 이미 기본 값이 있기 때문에 required 는 의미가 없다.
    • defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다. ( ex. /request-param-default?username= )

 

파라미터를 Map으로 조회하기 - requestParamMap

  • requestParamMap: RequestParamController 클래스에 다음 코드를 적용해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • 파라미터를 Map, MultiValueMap으로 조회할 수 있다.
      • @RequestParam Map
        • Map(key=value)
      • @RequestParam MultiValueMap
        • MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])
      • 파라미터의 값이 1개가 확실하다면 Map 을 사용해도 되지만, 그렇지 않다면 MultiValueMap 을 사용하자.

8) HTTP 요청 파라미터 - @ModelAttribute

실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다.
보통 다음과 같이 코드를 작성할 것이다.

@RequestParam String username;
@RequestParam String int age;

HelloData data = new HelloData();
data.setUsername(username);
data.setAge(age);

스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공한다.

👉 코드로 확인해보자. (먼저 요청 파라미터를 바인딩 받을 객체를 만들고, @ModelAttribute를 적용해보자.)

  • HelloData: src > main > java > hello > springmvc > basic 패키지 내부에 HelloData 클래스를 생성하자.
    • (롬복) @Data = @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 자동으로 적용해준다.
  • modelAttributeV1: RequestParamController 클래스 내부에 아래 코드를 적용해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • 마치 마법처럼 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다.
    • 스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.
      • HelloData 객체를 생성한다.
      • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
        • 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.
        • (참고) 프로퍼티
          • 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 이름의 프로퍼티를 가지고 있다고 말한다. username 프로퍼티의 값을 변경하면 setUsername() 이 호출되고, 조회하면 getUsername() 이 호출된다.
           class HelloData {
               getUsername();
               setUsername();
           }
    • (참고) 바인딩 오류
      • age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다. 이런 바인딩 오류를 처리하는 방법은 검증 부분에서 다룬다.
  • modelAttributeV2: RequestParamController 클래스 내부에 아래 코드를 적용해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • @ModelAttribute 는 생략할 수 있다. 그런데 @RequestParam 도 생략할 수 있으니 혼란이 발생할 수 있다.
    • 스프링은 @ModelAttribute 생략시 다음과 같은 규칙을 적용한다.
      • String, int, Integer 같은 단순 타입인 경우 = @RequestParam 적용
      • 나머지 = @ModelAttribute 적용 (argument resolver 로 지정해둔 타입 외)
        • (참고) argument resolver라는 것으로 지정해둔 타입(ex. HttpServletResponse, ...)은 @ModelAttribute가 적용되지 않는다. argument resolver는 뒤에서 학습한다.

지금까지 우리는 클라이언트에서 서버로 요청 데이터를 전달할 때 사용하는 아래 3가지 방법 중, 요청 파라미터(①, ②)를 조회하는 것에 대해서 알아보았다.

  • ① GET - 쿼리 파라미터
  • ② POST - HTML Form
  • ③ HTTP message body에 데이터를 직접 담아서 요청

다음부터는 ③ HTTP message body에 데이터가 직접 넘어오는 경우에 대해서 알아보자.


9) HTTP 요청 메시지 - 단순 텍스트

이전에 서블릿에서 학습한 내용을 떠올려보자.

HTTP message body에 데이터를 직접 담아서 요청하는 경우

  • HTTP API에서 주로 사용 (JSON, XML, TEXT 등을 담을 수 있다.)
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam , @ModelAttribute 를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

 

👉 먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.
( HTTP 메시지 바디의 데이터를 InputStream 을 사용해서 직접 읽을 수 있다. )

  • RequestBodyStringController: src > main > java > hello > springmvc > basic > request 패키지 내부에 RequestBodyStringController 클래스를 생성하자. (생성 후 Postman으로 실행해보자.)
    • 정상적으로 실행됨을 확인할 수 있다.
    • (참고) 바이트 코드를 문자로 받을 때는 어떤 인코딩으로 해서 문자로 바꿀 것인지를 항상 지정해줘야 한다. (지정하지 않으면 디폴트를 사용한다. (OS에 기본 설정된 것 또는 자바 실행시 기본으로 설정된 것 등))

👉 좀 더 개선해보자.

  • RequestBodyStringController - requestBodyStringV2: 클래스 내 아래 코드를 추가해보자. (추가 후 Postman을 사용해서 테스트 해보자.)
    • 정상적으로 실행됨을 확인할 수 있다.
    • 스프링 MVC는 다음 파라미터를 지원한다.
      • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회.
      • OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력.

👉 이렇게 Stream으로 받고 하는것도 그닥 효율적이진 않아보인다. 좀 더 개선해보자.

  • RequestBodyStringController - requestBodyStringV3: 클래스 내 아래 코드를 추가해보자. (추가 후 Postman을 사용해서 테스트 해보자.)
    • 정상적으로 실행됨을 확인할 수 있다.
    • 스프링 MVC는 다음 파라미터를 지원한다.
      • HttpEntity: HTTP header, body 정보를 편리하게 조회
        • 메시지 바디 정보를 직접 조회
        • 요청 파라미터를 조회하는 기능과는 전혀 관계 없음 @RequestParam(X), @ModelAttribute(X)
      • HttpEntity는 응답에도 사용 가능
        • 메시지 바디 정보 직접 반환
        • 헤더 정보도 포함 가능
        • HttpEntity를 응답하게 되면, view를 조회하지 않는다.
      • HttpEntity를 상속받은 다음 객체들도 같은 기능을 제공한다.
        • RequestEntity
          • HttpMethod, url 정보가 추가, 요청에서 사용
        • ResponseEntity
          • HTTP 상태 코드 설정 가능, 응답에서 사용
          • return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED)
        • 참고
  • RequestBodyStringController - requestBodyStringV4: 클래스 내 아래 코드를 추가해보자. (추가 후 Postman을 사용해서 테스트 해보자.)
    • 정상적으로 실행됨을 확인할 수 있다.
    • @RequestBody
      • @RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
      • 참고로 헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.
      • 이렇게 HTTP 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와는 전혀 관계가 없다. (HttpMessageConverter라는 매커니즘이 동작한다.)
    • 요청 파라미터 조회 vs HTTP 메시지 바디 조회
      • 요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
      • HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody
    • @ResponseBody
      • @ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다.
      • 물론 이 경우에도 view를 사용하지 않는다.

✔️ 참고

  • 스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는데, 이때 HTTP 메시지 컨버터( HttpMessageConverter )라는 기능을 사용한다. 이것은 조금 뒤에 HTTP 메시지 컨버터에서 자세히 설명한다.

10) HTTP 요청 메시지 - JSON

이번에는 HTTP API에서 주로 사용하는 JSON 데이터 형식을 조회해보자.

기존 서블릿에서 사용했던 방식과 비슷하게 시작해보자.

  • RequestBodyJsonController: src > main > java > hello > springmvc > basic > request 패키지 아래 RequestBodyJsonController 클래스를 생성하자. (생성 후 Postman을 사용해서 테스트 해보자.)
    • 정상적으로 실행됨을 확인할 수 있다.
    • HttpServletRequest를 사용해서 직접 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환한다.
    • 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 를 사용해서 자바 객체로 변환한다.
  • RequestBodyJsonController - requestBodyJsonV2 (@RequestBody 문자 변환): RequestBodyJsonController 클래스에 아래 코드를 추가해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • 이전에 학습했던 @RequestBody 를 사용해서 HTTP 메시지에서 데이터를 꺼내고 messageBody에 저장한다.
    • 문자로 된 JSON 데이터인 messageBody 를 objectMapper 를 통해서 자바 객체로 변환한다.

🤔 문자로 변환하고 다시 json으로 변환하는 과정이 불편하다. @ModelAttribute처럼 한번에 객체로 변환할 수는 없을까?

  • RequestBodyJsonController - requestBodyJsonV3 (@RequestBody 객체 변환): RequestBodyJsonController 클래스 내 아래 코드를 추가해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • @RequestBody 객체 파라미터 (ex. @RequestBody HelloData data)
      • @RequestBody 에 직접 만든 객체를 지정할 수 있다.
      • HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다. HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해주는데, 우리가 방금 V2에서 했던 작업을 대신 처리해준다. 자세한 내용은 뒤에 HTTP 메시지 컨버터에서 다룬다.
    • (참고) @RequestBody는 생략 불가능
      • @ModelAttribute 에서 학습한 내용을 떠올려보자. 스프링은 @ModelAttribute, @RequestParam과 같은 해당 애노테이션을 생략시 다음과 같은 규칙을 적용한다.
        • String , int , Integer 같은 단순 타입 = @RequestParam
        • 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)
      • 따라서 이 경우 HelloData에 @RequestBody 를 생략하면 @ModelAttribute 가 적용되어버린다. ( HelloData data -> @ModelAttribute HelloData data )
      • 그러므로 생략하면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다. (위 코드에서 @RequestBody를 제거하고 다시 동일하게 실행해보면, ok 메시지는 정상적으로 보이나, HelloData의 username에는 null, age에는 0이 들어간다. (객체만 생성하고 값이 아무것도 세팅되지 않은 것.))
    • (주의) HTTP 요청시에 content-type이 application/json인지 꼭! 확인해야 한다. 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.

👉 물론 앞서 배운 것과 같이 HttpEntity를 사용해도 된다.

  • RequestBodyJsonController - requestBodyJsonV4: RequestBodyJsonController 클래스 내 아래 코드를 추가해보자.

👉 응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.

  • RequestBodyJsonController - requestBodyJsonV5: RequestBodyJsonController 클래스 내 아래 코드를 추가해보자.
    • 정상적으로 실행됨을 확인할 수 있다.
    • HttpMessageConverter가 들어올 때(요청)도 적용될 수 있지만, 나갈 때(응답)도 적용될 수 있다.
    • @ResponseBody
      • 응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.
      • 물론 이 경우에도 HttpEntity 를 사용해도 된다.
        • (참고) @ResponseBody 생략 가능

✔️ 정리

  • @RequestBody 요청
    • JSON 요청 -> HTTP 메시지 컨버터 -> 객체
  • @ResponseBody 응답
    • 객체 -> HTTP 메시지 컨버터 -> JSON 응답

강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.

profile
현실에서 한 발자국

0개의 댓글