Back-End/Spring Boot

[Spring Boot] HTTP 요청 파라미터, 메시지 ( @RequestParam, @ModelAttribute, @RequestBody, HttpEntity)

s워니얌 2022. 10. 14. 21:59

 

지난 블로그에서 서블릿에서 HTTP 요청, 응답  메시지를 확인하고 전송할 수 있는 법을 알아봤다. 스프링에선 HttpServletRequest, HttpServletResponse 객체 생성 없이 어노테이션을 사용해서 간단하게 클라이언트에서 서버로 데이터를 전달할 수 있다. 그 방법을 알아보려고 한다.

 

 

https://wonisdaily.tistory.com/119

 

[Servlet] HttpServletRequest, HttpServletResponse 파헤치기

HttpServletRequest, HttpServletResponse에 대해 알아보기 전 Servlet에 대해 알아보려면 아래 링크 참조! https://wonisdaily.tistory.com/117 [Web] 서블릿(Servlet)이란? (+서블릿 컨테이너, 생명주기) 📑 서..

wonisdaily.tistory.com

 

 

 

📑 HTTP 요청 데이터

 

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

 

1. GET - 쿼리 파라미터
: /url?username=hello&age=20 같이 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달하는 것
ex) 검색, 필터, 페이징 등등

2. POST - HTML Form
: content-type:application/x-www-form-urlencoded 로 content-type을 설정해야하며 메시지 바디에 쿼리 파라미터 형식으로 데이터를 전달한다. username=hello&age=20
ex) 회원가입, 상품 주문, HTML Form 사용

3. HTTP message body에 데이터를 직접 담아서 요청
: HTTP API에서 주로 사용하는데, JSON,XML,TEXT 등의 형식으로 보낼 수 있다. 주로 JSON을 사용하며 POST, PUT, PATCH 메서드를 사용한다.

 

 

 

📑 요청 파라미터 - 쿼리, html form

 

HttpServletRequest의 request.getParameter()를 사용하면 GET/POST 어떤 방식이든 (쿼리파라미터든 form으로 보낸 데이터든) 두 가지 요청 파라미터를 조회할 수 있다. 

 

 

🎃 @RequestParam : 파라미터 이름으로 바인딩

 

ex) @RequestParam("username") String name 이렇게 메서드의 매개변수로 넣어주면 파라미터로 보낸 데이터 중 username이라는 값을 아래 메서드에서 String name으로 사용하겠다는 의미.

즉) 코드에 주석에서도 나와있지만 String name = request.getParameter("username") 과 같은 의미인 것이다. 

 

 

🎃 @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용을 입력.

 

ex) 스프링에서 메서드의 반환형을 String으로 주면 return에 저장된 문자열은 view name으로 인지한다. 그럼 해당 jsp나 뷰 형식이 있어야 되는데, 여기선 간단히 메세지만 전달하려면 ?? 

그럴 때 메서드 위에 @ResponseBody를 붙여준다. !! 만약 클래스, 해당 컨트롤러 전체에서 사용하고 싶다면 클래스 이름 위에 @RestController를 붙여주면 된다.

 

 

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

//@RequestParam("username") String memberName
//String username = request.getParameter("username");
//위의 두 과정이 같은 값을 초래한다. @RequestParam 어노테이션 사용하는 게 더 편하네

//스프링은 String 반환값인 경우 return에 view name을 입력한다.
//그래서 @Controller 대신 @RestController를 적었는데 만약) 메서드 부분만 사용할것이면
//@ResponseBody 를 적어주면 된다.
//이경우 return 의 "ok"값을 HTTP 응답 메시지에 콱 박아버려서 반환한다.
@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";

}

 

 

예제의 requestParamV3 메서드를 보면 HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx")를 생략하고 @RequestParam String username 이런 형식으로 쓸 수 있다. 

 

 

 

🎃 RequestParam 필수 여부 - required

 

 

기본으로 만약 @RequestParam을 사용한다면 데이터가 꼭 들어와야한다. 기본값이 required=true로 설정되어 있기 때문에 !! 그럴 때 required=false로 설정하면 해당 데이터 이름의 값이 안 들어와도 된다.

 

@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
        //아무것도 안적으면 true
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
    //Integer는 null이 들어올 수 있지만, int에 null이 들어오면 오류
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

여기서 추가적으로 살펴볼 것은 만약 /request-param?username= 이런식으로 파라미터 이름만 있고 값은 없다? 그럼 그냥 빈문자로 값이 들어온다.  username=, age=null 이런 형태로 찍힌다.

 

 

 

 

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

 

 

파라미터들을 간단하게 key, value 형식으로 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";

}

 


 

 

🎃 @ModelAttribute

 

: 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어줘야 한다. 파라미터를 받아서 객체로 사용하기 위해 HelloData라는 클래스를 만들어 본다.

 

★ 롬복 라이브러리에서 @Data 어노테이션은 getter,setter,tostring ,,,, 등 여러 어노테이션을 자동으로 적용해준다. 매우 편리한 기능! 

 

package hello.springmvc.basic;

import lombok.Data;

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

 

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

//@ModelAttribute 생략 가능
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData){
    log.info("helloData = {}", helloData);
    return "ok";
}

 

modelAttributeV1 메서드를 살펴보면 @ModelAttribute 어노테이션 다음 HelloData 형태의 helloData 객체를 생성했다. 이렇게 되면 HelloData 객체가 생성되며 여기에 요청 파라미터 값들이 모두 들어가게 된다.

 

http://localhost:8080/model-attribute-v1?username=hello&age=20 이렇게 전송하면 알아서 객체에 값이 들어간다는 말인데 어떻게 되는걸까?? 

 

 

 

 

스프링 MVC는 @ModelAttribute가 있으면 다음과 같이 동작한다.

 

1. HelloData 객체를 생성한다. 
2. 요청 파라미터 이름으로 HelloData 객체의 프로퍼티를 찾는다. (프로퍼티는 setxxx, getxxx)
3. 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)한다.
ex) 파라미터 이름이 username이면 setUsername() 메서드를 찾아서 호출해 값을 입력한다.
setUsername()은 값을 변경, getUsername()은 값을 조회

 

@ModelAttribute도 생략할 수 있으나, 조심하자! 되도록이면 @RequestParam, @ModelAttribute , 뒤에서 설명할 @RequestBody 같은 어노테이션은 생략하지 말자. 어느 어노테이션인지 일목요연하게 보기 편하다.

 

참고로 스프링은 어노테이션 생략 시 다음과 같은 규칙을 적용한다.

📌 String, int, Integer 같은 단순 타입 = @RequestParam

📌 나머지(HelloData,,,,,같은 객체 클래스) = @ModelAttribute

 

 

 

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

 

HTTP 요청 메시지는 body에 직접 데이터를 담아서 요청한다. 요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam, @ModelAttribute를 사용할 수 없다. 

 

SpringMVC는 InputStream(Reader)와 OutputStream(writer) 파라미터를 지원하기 때문에 HTTP 요청, 응답 메시지 바디의 내용을 직접 조회하고 출력할 수 있다. 그러나 더 간단한 방법이 있다.

 

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


}


/**
 * InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
 * OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
 */
@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");

}

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
    //만약 문자가 HTTP body에 있으면 HttpEntity<String> 스프링이 얘를 보고 넣어줄게

    String messageBody = httpEntity.getBody();
    log.info("messageBody={}", messageBody);
    return new HttpEntity<>("ok"); //첫번째 파라미터에 바디 메시지를 넣을 수 있다.

}

 

 

 

🎃 HttpEntity - HTTP header, body 정보를 편리하게 조회한다.

 

메시지 바디 정보를 직접 조회하며 .getBody() , .getHeaders() 등등 편리한 메서드를 다룬다. 요청 메시지는 물론이고 HttpEntity는 응답에도 사용 가능하다. 

 

RequestEntity, ResponseEntity로 나눠서 사용할 수 있는데 ResponseEntity는 HTTP 상태 코드 설정, 응답에서 주로 다음과 같은  식으로 사용된다. return new ResponseEntity("Hello World", responseHeaders, HttpStatus.CREATED)

 

 

 

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {

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

}

 

 

🎃 @RequestBody - HTTP 바디 정보 더 편리하게 조회

 

@RequestBody를 이용하면 더 편리하게 바디 정보 조회 가능하다. 참고로 헤더 정보가 필요하면 HttpEntity나 @RequestHeader를 사용하면 된다. 이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam, @ModelAttribute와는 전혀 관계가 없다. 

 

 


 

📑 HTTP 요청 메시지 - JSON

 

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-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData){

    log.info("username={}, age={}",helloData.getUsername(), helloData.getAge());
    return "ok";
}

 

 

@RequestBody를 통해 HelloData data라는 @RequestBody에 직접 만든 객체를 지정할 수 있다. HttpEntity나 @RequestBody를 사용하면 HTTP 메시지 컨버터가 HTTP 바디 내용을 우리가 원하는 문자나 객체 등으로 변환해준다. JSON데이터를 자바 객체로 역직렬화하기 위한 ObjectMapper 객체도 필요 없어지는 것. 여러줄의 코드를 단 한 두줄로 축약할 수 있다. HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON 객체로 변환해준다. 

 

 

📌@RequestBody 요청 : JSON -> HTTP 메시지 컨버터 -> 객체
📌 @ResponseBody 요청 : 객체 -> 메시지 컨버터 -> JSON 응답
반응형