🧑🏫 목표
- HTTP 요청 및 응답 데이터의 처리 방식을 학습한다.
- 실습을 통해 CRUD 기능을 포함한 프로젝트를 개발하며, 실제 애플리케이션에서 HTTP 통신과 데이터 처리를 적용하는 방법을 익힌다.
URL의 쿼리 파라미터를 사용하여 데이터 전달하는 방법
http://localhost:8080/request-params?key1=value1&key2=value2
@Slf4j
@Controller
public class RequestParamController {
@GetMapping("/request-params")
public void params( HttpServletRequest request,
HttpServletResponse response ) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
// 직접적으로 응답 데이터에 접근해서 응답 데이터를 만들어낼 수 있다. @Controller지만 데이터로 응답
response.getWriter().write("success");
}
}
POST /form-data
content-type: application/x-www-form-urlencoded
key1=value1&key2=value2
@PostMapping("/form-data")
public void requestBody(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
response.getWriter().write("success");
}
데이터의 생김새가 똑같기 때문에 getParameter로 값을 꺼내올 수 있다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Board {
private String title;
private String content;
}
@Slf4j
@Controller
public class RequestBodyController {
// JSON을 객체로 변환해주는 Jackson 라이브러리
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body")
public void requestBody(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
Board board = objectMapper.readValue(messageBody, Board.class);
log.info("board.getTitle()={}, board.getContent()={}", board.getTitle(), board.getContent());
response.getWriter().write("success");
}
}
request.getInputStream()을 통해서 문자열로 그 JSON 데이터를 꺼낸다.💡 JSON을 Java 객체로 변환하려면 Jackson과 같은 라이브러리를 사용해야 한다. Spring Boot는 기본적으로 Jackson 라이브러리의 ObjectMapper를 제공하며, starter-web에 포함되어 있습니다.
@annotation 기반의 Spring에서는 사실 HttpServletRequest 보다 편리하게 사용하는 방법이 있다.
URL에서 파라미터 값과 이름을 함께 전달하는 방식으로 주로 HTTP 통신 Method 중 GET 방식의 통신을 할 때 많이 사용한다. @Requestparam을 사용하면 요청 파라미터 값에 아주 쉽고 간편하게 접근(Parameter Binding)할 수 있다.
request.getParameter("key1");
request.getInputStream();
⬇️ 이렇게 요청 파라미터 값을 받아올 수 있다.
@Requestparam("key1") String key1;
@Slf4j
@Controller
public class RequestParamControllerV2 {
@ResponseBody
@GetMapping("/v1/request-param")
public String requestParamV1 (
@RequestParam("name") String userName,
@RequestParam("age") int userAge
) {
// logic
log.info("name={}", userName);
log.info("age={}", userAge);
return "success";
}
}
@RequestParam@RequestParam(”속성값”) @RequestParam(required=false) 필수 여부 속성이 default로 설정된다.어노테이션 생략 방식은 권장하지 않습니다. 명시적으로 표시되어있지 않으면 팀의 협의가 있지 않는 경우 다른 개발자들에게 혼동을 주게 됩니다. 최소 @RequestParam String name 속성 값 생략 형태를 쓰면 됩니다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
@RequestParam(required = true) String name, // 필수
@RequestParam(required = false) int age // 필수가 아님
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
400 BadRequest(클라이언트 에러)
🤨 requestParam에 required가 false인 값만 빼고 요청했을때 500 Internal Server Error(서버 에러)?

값을 빼고 요청한다는 것은 해당 키의 value가 null 인것과 같다.
→ 기본타입인 int는 null을 받아들일 수 없기 때문에
IllegalStateException이 발생
➕ param에 키값만 있고 value가 없는 경우
이름(name)에 value 없이 요청

속성으로 required=true 제한하였지만 빈문자열 ""은 null과 다르기 때문에 통과가 되어버린다.
@ResponseBody
@GetMapping("/v5/request-param")
public String requestParamV5(
@RequestParam(required = true, defaultValue = "sparta") String name,
@RequestParam(required = false, defaultValue = "1") int age
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
클라이언트 요청 파라미터에 값이 없다면 서버에서 미리 정의한 default 값이 들어간다.
✏️ MultiValueMap: 하나의 key에 여러개의 value가 존재
요청 파라미터를 받아 필요한 Object로 바인딩 해준다. 주로 HTML 폼에서 전송된 데이터를 바인딩하고 HTTP Method POST인 경우 사용된다.
POST /v1/tutor
content-type: application/x-www-form-urlencoded
name=tutor&age=100
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Tutor {
private String name;
private int age;
}
@Data 는 테스트 용도로만 사용한다.
@Controller
public class ModelAttributeController {
@ResponseBody
@PostMapping("/v1/tutor")
public String requestParamV1(
@RequestParam String name,
@RequestParam int age
) {
Tutor tutor = new Tutor();
tutor.setName(name);
tutor.setAge(age);
return "tutor name = " + name + " age = " + age;
}
}
@ModelAttribute 는 해당 과정을 자동화 한다.▶︎ ModelAttribute 적용
@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
@ModelAttribute Tutor tutor
) {
String name = tutor.getName();
int age = tutor.getAge();
return "tutor name = " + name + " age = " + age;
}
@ModelAttirubte 동작 순서
name 이면 setName(value); 메서드를 호출한다.@Data의 Setter가 없다면?
객체 필드에 값이 set되지 않는다.
파라미터의 타입이 다른 경우
age의 값을 숫자가 아닌 문자열로 전달

@ModelAttirubte 생략
@ModelAttribute와 @RequestParam은 모두 생략이 가능하다.
String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다.✏️ @RequestParam, @ModelAttribute는 GET + Query Parameter와, POST HTML Form Data를 바인딩하는 방법

HTTP Request Body에 Data가 전송되는 경우 HttpMessageConverter를 통해 바인딩된다.
💡 현대에는 Restful API를 주로 사용하고 있어서 대부분의 경우 JSON 형식으로 통신한다.
@Controller
public class RequestBodyStringController {
@PostMapping("/v1/request-body-text")
public void requestBodyTextV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
response.getWriter().write("response = " + bodyText);
}
}
inputStream으로 객체(Object)로 만든 텍스트는
StringUtils.copyToString(inputStream, StandardCharsets.UTF_8); 로 UTF_8로 인코딩된 형태의 문자열로 반환한다.

@PostMapping("/v2/request-body-text")
public void requestBodyTextV2(
InputStream inputStream,
Writer responseWriter
) throws IOException {
String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
responseWriter.write("response = " + bodyText);
}
HttpEntity를 사용하면 HttpMessageConverter를 사용한다.@PostMapping("/v3/request-body-text")
public HttpEntity<String> requestBodyTextV3(HttpEntity<String> httpEntity) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String body = httpEntity.getBody();
return new HttpEntity<>("response = " + body); // 매개변수 = Body Message
}

💡 Converter는 어떤 뭔가를 다른 뭔가로 바꿔주는(Convert) 장치를 말한다.
🌱 Spring 에서 Converter의 역할은 json, text와 같은 데이터를 Object와 같은 객체로 바꿔주는 역할을 하게 된다.
HttpEntity는 HTTP Header, Body 정보를 편리하게 조회할 수 있도록 만들어준다.
HttpEntity 역할
HttpEntity를 상속받은 객체
RequestEntity<>ResponseEntity<> @PostMapping("/v4/request-body-text")
public HttpEntity<String> requestBodyTextV4(RequestEntity<String> httpEntity) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String body = httpEntity.getBody();
// url, method 사용 가능
return new ResponseEntity<>("response = " + body, HttpStatus.CREATED); // Body Data, 상태코드
}

201 CREATED로 반환되었다.entity.getData()) 사용해야 한다.Spring에서 @RequestBody, @ResponseBody 어노테이션을 사용하면 각각 Request, Response 객체의 Body에 편하게 접근하여 사용할 수 있다.
@ResponseBody
@PostMapping("/v5/request-body-text")
public String requestBodyTextV5(
@RequestBody String body,
@RequestHeader HttpHeaders headers
) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String bodyMessage = body;
return "request header = " + headers + " response body = " + bodyMessage;
}
@RequestBody
@RequestHeader
@ResponseBody
요약
Json은 @RestController 에서 가장 많이 사용되는 데이터 형식이다. 현재 대부분의 API는 Request, Response 모두 JSON 형태로 통신한다.
@RestController
public class JsonController {
// jackson 라이브러리
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/v1/request-body-json")
public void requestBodyJsonV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
// request body message를 Read
ServletInputStream inputStream = request.getInputStream();
// UTF-8 형식의 String으로 변환한다.
String requestBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
// String requestBody를 ObjectMapper를 사용하여 변환 "{\"name\":\"wonuk\", \"age\":10}"
Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
response.getWriter().write("tutor" + tutor);
}
}
요청받은 JSON 을 문자열로 받는다.
@PostMapping("/v2/request-body-json")
public String requesBodyJsonV2(@RequestBody String requestBody) throws IOException {
Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
}
🤨 ObjectMapper가 계속 반복되는데 @ModelAttribute처럼 객체로 바로 반환은 안되나요?
🧑🎓 Spring은 개발에 필요한 대부분의 기능이 구현되어 있습니다.
@PostMapping("/v3/request-body-json")
public String requestBodyJsonV3(@RequestBody Tutor tutor) {
Tutor requestBodyTutor = tutor;
return "tutor = " + requestBodyTutor;
}
객체로 직접 json 데이터의 값을 매핑하여 사용
👉 위 Controller가 동작하는 이유는 무엇인가요?
@RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.MappingJackson2HttpMessageConverter 의 역할
MappingJackson2HttpMessageConverter
생략하게 되면 메서드는 해당 인자를 (기본 타입이 아니기 때문에) @ModelAttribute로 인식한다.
@PostMapping("/v5/request-body-json")
public String requestBodyJsonV5(
HttpEntity<Tutor> httpEntity
) {
// 값을 꺼내서 사용해야한다!
Tutor tutor = httpEntity.getBody();
return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
}
번거롭게 httpEntity.getBody();로 값을 꺼내서 사용해야 한다.
@Controller
public class JsonController {
@ResponseBody // @RestController = @Controller + @ResponseBody
@PostMapping("/v6/request-body-json")
public Tutor requestJson(@RequestBody Tutor tutor) {
return tutor;
}
}
Tutor라는 객체를 반환했는데 Json 형태로 변환되어 클라이언트에 응답하였다.

HttpMessageConverter가 동작한다.MappingJackson2HttpMessageConverter 적용@RequestBody를 사용해서 바인딩 하면 된다.@RequestBody 는 생략이 불가능하다.@ModelAttribute가 적용되기 때문HttpMessageConverter 가 요청·응답 데이터를 모두 변환할 수 있다.MappingJackson2HttpMessageConverter 를 사용한다.Spring Framework에서 HTTP 요청과 응답을 변환하는 인터페이스이다. 클라이언트와 서버 간에 데이터를 주고받을 때, 요청 데이터를 자바 객체로 변환하거나 자바 객체를 응답 본문으로 변환하는 역할을 수행한다.

@RequestBody@ResponseBody