[스프링 MVC - 백엔드 웹 개발 기술] HTTP 요청 파라미터 - 쿼리 스트링과 HTML Form, @RequestParam, @ModelAttribute, 텍스트, JSON 요청 방식
by ymkim01. HTTP 요청 파라미터 - QueryString, HTML Form
- 이번 시간에는 쿼리 스트링와 HTML Form 데이터 전송 방식에 대해 알아본다.
01-1. 클라이언트 → 서버 → 데이터 전송 방식
- 클라이언트 → 서버로 데이터 전송하는 방식은 아래 3가지 존재
- GET - 쿼리 스트링(Query String)
- /user?username=hello&age=20
- URL 쿼리 파라미터에 데이터 포함 후 전달
- 예) 검색, 필터, 페이징 등에서 사용
- POST - HTML Form
- Content-type : application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파라미터 형식으로 전달
- username=hello&age=20
- HTTP Message Body
- HTTP API에서 사용 - JSON, XML, TEXT
- 데이터 형식은 주로 JSON
01-2. 쿼리스트링, HTML Form 전송 방식 실습
- HttpServletRequest의 request.getParameter() 사용하면 아래 2가지 요청 받을 수 있음
GET - 쿼리 파라미터 전송
POST - HTML Form 전송
POST /request-param ...
contenst-type: application/www-form-urlencoded
username=hello&age=20
- 쿼리 파라미터, HTML Form 이든 구분없이 조회 가능
- 위와 같은 2가지 방식을 요청 파라미터 방식 이라 한다
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");
}
}
- 쿼리 스트링, HTML Form 전송의 파라미터를 받기 위해 해당 컨트롤러 생성
- 두 요청 방식에 상관없이 **request.getParameter(”인자”)**를 통해 값을 받는다
/static/basic/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>
- HTML Form 전송 방식을 위해 해당 HTML 생성
- /static/* 경로는 외부에서 바로 접근이 가능하기에 해당 경로에 HTML 파일 생성
02. HTTP 요청 파라미터 - @RequestParam
- 스프링이 제공하는 @RequestParam을 사용하면 요청 파라미터 쉽게 사용 가능
02-1. requestParamV2 - @RequestParam 사용
package hello.springmvc.basic.request;
...
@Slf4j
@Controller
public class RequestParamController {
@ResponseBody // @RestController와 같은 효과, Body에 HTTP Message를 반환
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge
) {
log.info("memberName = {}, memberAge = {}", memberName, memberAge);
return "ok";
}
}
- @RequestParam : 파라미터 이름 바인딩
- @ResponseBody : View 조회 무시, HTTP Message body 직접 입력
- @RequestParam(”username”) String memberName은 아래와 동일
- → request.getParameter(”username”);
02-2. requestParamV3 - @RequestParam(”변수명”) 제거
@ResponseBody // @RestController와 같은 효과, Body에 HTTP Message를 반환
@RequestMapping("/request-param-v3")
public String requestParamV3(
@RequestParam String username,
@RequestParam int age
) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
- @RequestParam의 ‘(”username”)’ 은 생략이 가능하다
- 생략 하려면 client에서 전송하는 변수명과 반드시 동일해야 함
02-3. requestParamV4 - @RequestParam 자체 제거
@ResponseBody // @RestController와 같은 효과, Body에 HTTP Message를 반환
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
- String, int, Integer 등의 단순 타입이면 @RequestParam 어노테이션 자체 제거 가능
- 생략 하려면 client에서 전송하는 변수명과 동일해야 함
- @RequestParam 어노테이션 자체를 없애는것은 그렇게 좋은 것 같지는 않음
02-4. required 사용
@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";
}
- @RequestParam.required
- 파라미터 필수값 여부
- 기본값 true
02-5. defaultValue 사용
@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";
}
- default value를 통해 값이 없는 경우에 대해 처리
- 빈 문자의 경우에도 설정값이 적용이 된다
02-6. Param Map
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object>paramMap) {
log.info("username = {}, age = {}", paramMap.getOrDefault("username", ""), paramMap.getOrDefault("age", 0));
return "ok";
}
- @RequestParam Map<String, Object> paramMap 을 통해 모든 파라미터를 매핑할 수 있음
- Map은 Casting 등의 불필요한 연산이 필요하기에 웬만하면 지양하는 것이 좋음
03. @ModelAttribute
- 실제 개발을 하면 요청 파라미터(@RequestParam)를 받는다
- 객체를 생성하고, 해당 객체에 파라미터 값을 셋팅한다
- 보통 코드는 다음과 같이 작성
@RequestParam String username;
@RequestParam int age;
HelloData data = new HelloDate();
data.setUsername(username);
data.setAge(age);
- 스프링은 위와 같은 작업을 자동화해주는 @ModelAttribute 제공
- 먼저 요청 파라미터를 바인딩 받을 객체를 생성한다
03-1. HelloData
package hello.springmvc.basic;
import lombok.Data;
@Data
public class HelloData {
private String username;
private int age;
}
- @Data : 아래 어노테이션 자동 생성
- @Getter
- @Setter
- @ToString
- @EqualsAndHashCode
- @RequiredArgsConstructor
- 기존에 파라미터 바인딩 방식은 다음 코드와 같다
03-2. @RequestParam 바인딩
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@RequestParam String username, @RequestParam int age) {
HelloData helloData = new HelloData();
helloData.setUsername(username);
helloData.setAge(age);
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
- @RequestParam을 통해 파라미터 바인딩
- HelloData 객체 생성 후 setter를 통해 객체에 값 셋팅
- 하지만 @ModelAttribute를 통해 위 과정 자동화 가능
03-3. @ModelAttribute 바인딩
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
- @RequestParam 어노테이션 제거 효과
- @ModelAttribute 실행 방식
- HelloData 객체 생성
- 요청 파라미터 이름(username, age)으로 HelloData 객체의 프로퍼티를 찾는다
- 즉, 요청 파라미터(username, age)의 getter/setter를 찾는다
- 해당 프로퍼티의 setter를 호출하여 파라미터 값 바인딩
- ex) 파라미터명 username → setUsername() 호출 후 바인딩
- @ModelAttribute 바인딩 오류
- http://localhost:8080/model-attribute-v1?username=sss&age=sasdsad
- 데이터 타입 바인딩 오류 → Exception 발생
- 바인딩 오류가 발생하면 별도로 바인딩 처리도 가능하다
03-4. @ModelAttribute 바인딩 생략
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
- @ModelAttribute 생략 가능
- @RequestParam 생략 가능
- argument resolver 지정 타입 외 모두 @ModelAttribute로 지정이 된다
- ex) argument resolver : HttpServletRequest, HttpServletResponse
03-5. @ModelAttribute vs @RequestParam vs @RequestBody
[Spring] @RequestParam, @RequestBody, @ModelAttribute의 차이
- 클라이언트의 요청을 객체로 바인딩 하는 3가지 방법
- @RequestParam
- @RequestBody
- @ModelAttribute
바인딩 종류 | 특징 |
@RequestParam | - 1개의 HTTP 요청 파라미터를 얻기 위해 사용, 기본값 지정 가능 - 필수 여부 true이기에 없으면 required=false 선언 |
@ModelAttribute | - 폼 형태(form) HTTP Body와 요청, 파라미터 객체 바인딩 - 기본적으로 생성자로 값 설정, 생성자로 설정되지 않은 필드 setter로 설정 - 생성자 혹은 setter가 없으면 값 저장이 안됨 - Body 값 받기 위해서 multipart/form-data 형식 전송 필요 - application/x-www-form-urlencoded 받을 수 있는데? - 바인딩 타입에 맞춰 자동 검증 |
@RequestBody | - application/json 형태 HTTP Body → MessageConverter → Java 객체 변환 - HTTP 요청 본문 → Java 객체 → ObjectMapper 사용 - 위 작업을 위해 → MappingJackson2HttpMessageConverter → Spring 도움 받음 - 메시지 변환 과정에서 객체 기본 생성자를 통해 객체 생성 - 내부적으로 Reflection을 사용해 값을 할당 - 값을 주입하기 위한 생성자, Setter 필요 없음 |
- Content-Type : application/json
- @RequestBody을 통해 바인딩
- Content-Type : application/x-www-form-urlencoded
- HTML Form(default type)
- AJAX(default type)
- @RequestParam을 통해 바인딩
- @ModelAttribute을 통해 바인딩
- Content-Type : multipart/form-data
- 파일이나 이미지 같은 바이너리 데이터가 포함된 데이터 전송 시 사용
- 클라이언트가 요청 시 폼 데이터를 여러 부분으로 나눠서 전송
지금까지 알아본 내용은 “요청 파라미터” 에 관한 내용이다. 다음에는 HTTP RequestBody에 데이터가 넘어오는 경우에 대해 알아본다.
04. HTTP 요청 메시지 - 텍스트
- 서블릿에서 학습한 내용을 떠올리면 다음과 같다
- HTTP message body에 데이터를 직접 담아 요청
- HTTP API에서 주로 사용, JSON, XML, TEXT
- 데이터 형식은 주로 JSON 사용
- POST, PUT, PATCH
- 요청 파라미터와 다르게 HTTP 메시지 바디에 데이터가 넘어오는 경우
- @RequestParam | @ModelAttribute 사용 불가
- 물론 HTML Form 형식 데이터는 데이터로 인정
- 하지만 여기서의 포인트는 → @RequestBody 사용해야함
- @RequestParam | @ModelAttribute 사용 불가
- 먼저 가장 단순한 텍스트 메시지를 HTTP message body에 담아 전송하고 읽어보자
- HTTP 메시지 바다 내용은 InputStream 을 통해 읽을 수 있다
04-1. RequestBodyStringController
HTTP Text 응답 후 출력 - V1
/**
* HTTP Body에 TEXT 데이터 요청
*/
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream(); // body 정보를 읽는다
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("message = {}", messageBody);
response.getWriter().write("ok");
}
- HttpServletRequest, HttpServletResponse를 통해 HTTP Message Body 내용 출력
- 이전에 서블릿에서 했던 내용과 크게 다른 부분은 없음
InputStream, Writer 사용 - V2
/**
* 파라미터 인자에 InputStream, Writer를 통해 값 반환
*/
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(
InputStream inputStream,
Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("message = {}", messageBody);
responseWriter.write("ok");
}
- InputStream, Writer를 통해 요청 값 출력
HttpEntity 사용 - V3
/**
* HttpEntity 사용
*/
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
log.info("message = {}", messageBody);
return new HttpEntity<>("ok");
}
- InputStream, Writer와 같은 부분도 자동화 하고 싶은 경우 사용
- HttpMessageConvert가 내부적으로 동작하여 위 부분을 자동화
- HTTP Body안에 있는 내용을 문자로 바꿔서 HttpEntity에 저장해준다 (스프링)
- 스프링은 다음 파라미터 지원
- HttpEntity
- HTTP Header, Body 정보 간단히 조회
- 메시지 바디 정보 직접 조회
- 요청 파라미터를 조회하는 기능과 관계 없음, @RequestParam x, @ModelAttribute x
- HttpEntity는 응답에도 사용 가능
- 메시지 바디 직접 반환
- 헤더 정보 포함 가능
- view 조회 x
- HttpEntity를 상속받은 객체도 같은 기능 제공
- RequestEntity<T>
- HttpMethod, url 정보가 추가, 요청에서 사용
- ResponseEntity<T>
- HTTP 상태 코드 설정 가능, 응답에서 사용
- return new ResponseEntity<String>(”Hello World”, responseHeaders, HttpStatus.CREATED);
- RequestEntity<T>
- HttpEntity
스프링 MVC 내부 → HttpMessageConverter 사용 → HTTP message body 파싱 → 문자 or 객체로 변환
@RequestBody, @ResponseBody 사용 - V4
/**
* @RequestBody, @ResponseBody 사용
*/
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
log.info("message = {}", messageBody);
return "ok";
}
- @RequestBody
- HTTP message body를 편리하게 조회 가능
- 참고로 헤더 정보가 필요하면 @RequestHeader를 사용하면 됨
- 이렇게 바디를 직접 조회하는 기능은 @RequestParam, @ModelAttribute와 상관이 없음
- @ResponseBody
- 응답 결과를 HTTP message body에 담아 전달
- 물론 이 경우에도 view를 사용하지 않음
- 자동으로 해준다는 의미?
- InputStream, Writer… 등등
- 위와 같은 부분은 HttpMessageConverter를 통해 객체, 문자로 자동 변환
04-2. 요청 파라미터 vs HTTP 메시지 바디
- 요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute
- HTTP 메시지 바디 조회 : @RequestBody
05. HTTP 요청 메시지 - JSON
- 이전에는 HTTP 요청 메시지 Body - TEXT 형태 조회
- 이번에는 HTTP 요청 메시지 Body - JSON 형태 조회
05-1. RequestBodyJsonController
HttpServletRequest, HttpServletResponse - V1
@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 = mapper.readValue(messageBody, HelloData.class); // json to object -> 역직렬화
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
response.getWriter().write("ok");
}
- 이전과 달라지는 부분은 Request(요청)으로 JSON 데이터가 들어옴
- JSON 데이터 파싱을 위해 ObjectMapper 객체 사용
@RequestBody - String - V2
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
log.info("messageBody = {}", messageBody);
HelloData helloData = mapper.readValue(messageBody, HelloData.class); // json to object -> 역직렬화
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
return "ok";
}
- @RequestBody를 통해 Body 데이터 출력
- 이전과 달라지는 부분은 없음
@RequestBody - HelloData DTO - V3
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
return "ok";
}
- @RequestBody에 HelloData 객체를 매개변수로 받는다
- 어떻게 이렇게 했는데 값이 셋팅되는 걸까??
- @RequestBody HelloData data
- @RequestBody에 직접 만든 객체 지정 가능
- 이렇게 변환되는 이유는 아래와 같다
- HttpEntity, @RequestBody를 사용하면 HTTP Message Converter가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체로 자동 변환시킨다
- MappingJackson2HttpMessageConverter
- HTTP Message Converter는 JSON 객체도 변환
- V2에서 했던 내용을 대신 처리
- HelloData data = mapper.readValue(data, HelloData.class); → **역직렬화**
- @RequestBody는 메서드의 매개변수 영역에서 생략하면 @ModelAttribute로 처리한다
- 스프링은 @ModelAttribute, @RequestParam 생략시 다음 규칙 적용
- String, int, Integer 같은 단순 타입은 @RequestParam로 치환한다
- 나머지는 @ModelAttribute로 치환된다
- 스프링은 @ModelAttribute, @RequestParam 생략시 다음 규칙 적용
HTTP 요청 시 content-type이 application/json인지 확인해야함 확인하지않으면 MessageConverter에 의해 변환이 되지 않음.
HttpEntity - V4
@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의 사용방법은 이전과 동일
@RequestBody / @ResponseBody Message Converter - V5
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username = {}, age = {}", data.getUsername(), data.getAge());
return data;
}
- @RequestBody가 있으면 들어올때도 HttpMessageConverter 적용
- JSON 요청 → HTTP Message Converter → 객체
- @ResponseBody가 있으면 나갈때도 HttpMessageConverter 적용
- 객체 → HTTP Message Converter → JSON
- 직렬화, 역직렬화 자동 수행
'Spring MVC > Spring MVC - 백엔드 웹 개발 기술 2탄' 카테고리의 다른 글
[스프링 MVC - 백엔드 웹 개발 기술] HTTP 메시지 컨버터(Message Converter) (2) | 2024.01.05 |
---|---|
[스프링 MVC - 백엔드 웹 개발 기술] HTTP 응답 - 정적 리소스와 뷰 템플릿 반환 및 응답 방법 (1) | 2024.01.05 |
[스프링 MVC - 백엔드 웹 개발 기술] 컨트롤러의 요청 매핑 방식과 HTTP 요청 기본 헤더 값 조회 방법 (1) | 2024.01.05 |
[스프링 MVC - 백엔드 웹 개발 기술] 프로젝트 생성과 로깅 간단히 알아보기 (2) | 2024.01.05 |
[스프링 MVC - 백엔드 웹 개발 기술] 스프링 MVC 구조의 이해 (0) | 2023.10.01 |
블로그의 정보
기록하고 정리하는 공간
ymkim