반응형

# 스프링 MVC 기본 기능

## HTTP 요청 - 기본, 헤더 조회

  • 애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원.
  • RequestHeaderController.
package hello.springmvc.basic.request;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

@Slf4j
@RestController
public class RequestHeaderController {

    @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";
    }
}
  • HttpServletRequest
  • HttpServletResponse
  • HttpMethod : HTTP 메서드를 조회한다. org.springframework.http.HttpMethod
  • Locale : Locale 정보를 조회한다.
  • @RequestHeader MultiValueMap headerMap, 모든 HTTP 헤더를 MultiValueMap 형식으로 조회.
  • @RequestHeader("host") String host : 특정 HTTP 헤더를 조회.
속성
필수 값 여부 : required
기본 값 속성 : defaultValue
  • @CookieValue(value = "myCookie", required = false) String cookie : 특정 쿠키를 조회.
속성
필수 값 여부 : required
기본 값 : defaultValue
  • MultiValueMap  : MAP과 유사, 하나의 키에 여러 값을 받을 수 있다, HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용.
keyA=value1&keyA=value2
 

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

 

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가지 방법을 사용.

1. GET - 쿼리 파라미터

/url?username=hello&age=20

메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달

예) 검색, 필터, 페이징등에서 많이 사용.

2. POST - HTML Form

content-type: application/x-www-form-urlencoded

메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20

예) 회원 가입, 상품 주문, HTML Form 사용.

3. HTTP message body

HTTP message body에 데이터를 직접 담아서 요청

HTTP API에서 주로 사용, JSON, XML, TEXT

데이터 형식은 주로 JSON 사용

POST, PUT, PATCH

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

  • HttpServletRequest 의 request.getParameter() 사용 시 다음 두가지 요청 파라미터 조회.
  • GET 쿼리 파리미터 전송 방식, POST HTML Form 전송 방식 둘다 형식이 같으므로 구분없이 조회할 수 있다. (요청 파라미터(request parameter) 조회)

RequestParamController

package hello.springmvc.basic.request;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@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"));
        log.info("username={}, age={}", username, age);

        response.getWriter().write("OK");
    }
}

hello-form.html

<!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>
  • 리소스는 /resources/static 아래에 두면 스프링 부트가 자동으로 인식.

## HTTP 요청 파라미터 - @RequestParam

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

requestParamV2.

@ResponseBody   // @RestController 동일한 역할.
@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 message body에 직접 해당 내용 입력.

requestParamV3.

  • HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
        @RequestParam String username,
        @RequestParam int age) {
    log.info("username={}, age={}", username, age);

    return "OK";
}

requestParamV4.

  • String , int , Integer 등 단순 타입이면 @RequestParam 생략 가능.
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
    log.info("username={}, age={}", username, age);

    return "OK";
}

파라미터 필수 여부 - requestParamRequired

  • @RequestParam.required : 파라미터 필수 여부 (기본값 : 파라미터 필수(true))
  • true인 값이 없으면 400 예외 발생.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) int age) {
    log.info("username={}, age={}", username, age);

    return "OK";
}
  • 기본형(primitive)에 null 입력 시 (@RequestParam(required = false) int age 인 경우 null 입력 시.) null 을 int 에 입력하는 것은 불가능(500 예외 발생)
  • 따라서 null 을 받을 수 있는 Integer 로 변경하거나, defaultValue 사용.

기본 값 적용 - requestParamDefault

@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age) {
    log.info("username={}, age={}", username, age);

    return "OK";
}
  • 파라미터에 값이 없는 경우 defaultValue 를 사용 시 기본 값을 적용할 수 있다.
  • 이미 기본 값이 있기 때문에 required 는 의미없다.
  • defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다.
/request-param?username=

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

@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으로 조회할 수 있다.
@RequestParam Map
	- Map(key=value)

@RequestParam MultiValueMap
	- MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])
  • 파라미터 값이 1개가 확실하면 Map 사용, 그렇지 않다면 MultiValueMap 사용.

## HTTP 요청 파라미터 - @ModelAttribute

HelloData.

package hello.springmvc.basic;

import lombok.Data;

@Data
public class HelloData {
    private String username;
    private int age;
}

modelAttributeV1

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

    return "OK";
}
  • 스프링MVC는 @ModelAttribute 가 있으면 다음을 실행.
HelloData 객체 생성.

요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾고 
해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.

예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.
  • 프로퍼티
객체에 getUsername() , setUsername() 메서드가 있으면, 
이 객체는 username 이라는 프로퍼티를 가지고 있다.

username 프로퍼티의 값을 변경하면 setUsername() 호출, 조회하면 getUsername() 호출.
  • 바인딩 오류 (BindException)
age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 발생.

@ModelAttribute 생략 - modelAttributeV2

@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

    return "OK";
}
  • @ModelAttribute 생략 가능, 그런데 @RequestParam 도 생략 가능하여 혼란이 발생할 수 있다.
  • 스프링은 해당 생략 시 다음의 규칙을 적용.
String , int , Integer 같은 단순 타입 = @RequestParam 

나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

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

  • HTTP message body에 데이터를 직접 담아서 요청 : HTTP API에서 주로 사용, JSON, XML, TEXT, 데이터 형식은 주로 JSON 사용, POST, PUT, PATCH
  • 요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우 @RequestParam , @ModelAttribute 를 사용할 수 없다.
  • HTTP 메시지 바디의 데이터를 InputStream 을 사용해서 직접 읽을 수 있다.

 RequestBodyStringController.

package hello.springmvc.basic.request;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Slf4j
@Controller
public class RequestBodyStringController {

    @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");
    }
}

requestBodyStringV2. - Input, Output 스트림, Reader

@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");
}

스프링 MVC는 다음 파라미터를 지원.

InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회

OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 출력

requestBodyStringV3. - HttpEntity 

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
    String body = httpEntity.getBody();

    log.info("messageBody={}", body);

    return new HttpEntity<>("OK");
}

스프링 MVC는 다음 파라미터를 지원.

  • HttpEntity : HTTP header, body 정보를 편리하게 조회 메시지 바디 정보를 직접 조회 (요청 파라미터를 조회하는 기능과 관계 없음 @RequestParam X, @ModelAttribute X)
  • HttpEntity는 응답에도 사용 가능, 메시지 바디 정보 직접 반환, 헤더 정보 포함 가능 (view 조회X)

HttpEntity 를 상속받은 다음 객체들도 같은 기능 제공.

  • RequestEntity : HttpMethod, url 정보가 추가, 요청에서 사용.
  • ResponseEntity : HTTP 상태 코드 설정 가능, 응답에서 사용
return new ResponseEntity("Hello World", responseHeaders, HttpStatus.CREATED)

requestBodyStringV4 - @RequestBody (가장 자주 사용!!!)

@PostMapping("/request-body-string-v4")
public HttpEntity<String> requestBodyStringV4(@RequestBody String messageBody) throws IOException {
    log.info("messageBody={}", messageBody);

    return new HttpEntity<>("OK");
}
  • @RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
  • 헤더 정보가 필요하면 HttpEntity 또는 @RequestHeader 를 사용.
  • 이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와 전혀 관계가 없다.

정리.

  • 요청 파라미터를 조회하는 기능 : @RequestParam , @ModelAttribute
  • HTTP 메시지 바디를 직접 조회하는 기능 : @RequestBody

## HTTP 요청 메시지 - JSON

RequestBodyJsonController.

package hello.springmvc.basic.request;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.HelloData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Slf4j
@Controller
public class RequestBodyJsonController {

    private ObjectMapper objectMapper = new ObjectMapper();

    @PostMapping("/request-body-json-v1")
    public void requestBodyJson(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");
    }
}
  • HttpServletRequest를 사용해 직접 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환.
  • 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 를 사용해 자바 객체로 변환.

requestBodyJsonV2 - @RequestBody 문자 변환

@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";
}

requestBodyJsonV3 - @RequestBody 객체 변환

@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

    return "OK";
}

@RequestBody 객체 파라미터

  • @RequestBody HelloData data @RequestBody 에 직접 만든 객체를 지정할 수 있다.
  • HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 원하는 문자나 객체 등 으로 변환.
  • HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환.

@RequestBody 생략 불가능.

  • 스프링은 @ModelAttribute , @RequestParam 생략시 다음과 같은 규칙을 적용.
String , int , Integer 등 단순 타입 = @RequestParam 적용

나머지 = @ModelAttribute 적용(argument resolver 로 지정해둔 타입 외)

requestBodyJsonV4 - HttpEntity

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) throws IOException {
    HelloData helloData = httpEntity.getBody();
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

    return "OK";
}

requestBodyJsonV5

@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) throws IOException {
    log.info("username={}, age={}", data.getUsername(), data.getAge());

    return data;
}
  • @ResponseBody 응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다. 이 경우에도 HttpEntity 를 사용해도 된다.
  • @RequestBody 요청 : JSON 요청 > HTTP 메시지 컨버터 > 객체
  • @ResponseBody 응답 : 객체 > HTTP 메시지 컨버터 > JSON 응답
반응형

+ Recent posts