간단히 말해 클라이언트는 웹브라우저처럼 서버에 정보를 요청하는 쪽이고, 서버는 이 요청을 받아서 처리한 후 응답을 보내주는 쪽이이다. 클라이언트가 서버에게 보내는 메시지를 요청(Request)이라고 하고, 반대로 서버가 클라이언트에게 보내는 메시지를 응답(Response)이라고 부른다.
우리가 사용하는 많은 웹사이트는 페이지를 새로 고치지 않고도 데이터를 주고받으며 자연스럽게 동작한다.
이런 방식을 바로`비동기 통신`라고 한다.
비동기 통신을 하려면 클라이언트는 서버에게 요청을 보낼 때 데이터를 'Body'라는 부분에 담아서 보내야 한다.
서버도 클라이언트에게 응답할 때 마찬가지로 'Body'에 데이터를 담아서 보내준다.
이렇게 Body에 담아서 보내는 데이터를 각각 요청 Body, 응답 Body이라고 한다. 'Body'에 담길 수 있는 데이터 형식은 다양하지만, 가장 널리 사용되는 것은 JSON이라는 형식이다.
즉, 비동기 방식에서는 클라이언트와 서버가 주로 JSON 형식의 데이터를 주고받는다.
스프링을 사용할 때도 이 방식이 자주 사용된다.
클라이언트가 JSON이나 XML 등의 데이터를 보내면, 스프링이 자동으로 이 데이터를 자바 객체로 변환해서 처리할 수 있도록 도와준다.
여기서 중요한 역할을 하는 것이 바로 `@RequestBody`와 `@ResponseBody`라는 어노테이션이다.
`@RequestBody`는 클라이언트에서 보낸 HTTP 요청 Body(JSON 등)을 자바 객체로 바꿔준다.
`@ResponseBody`는 자바 객체를 다시 HTTP 응답 Body(JSON 등)으로 바꿔서 클라이언트에 보내준다.
이렇게 스프링 MVC는 비동기 통신을 더욱 쉽게 할 수 있도록 많은 편의 기능을 제공한다.
자 이제 대략적인 흐름을 알았으니 본격적으로 `@RequestBody`에 대해서 알아보자.
우리는 여기서 4번째 과정인 "핸들러 어댑터가 컨트롤러로 요청을 위임함"에만 집중하면 된다.
source : 망나니 개발자
바로 여기서 `@RequestBody`가 드디어 일을 시작한다.
쉽게 말해 클라이언트에서 보낸 요청에 있는 body를 자바 객체로 바꿔주는 일을 바로 여기서한다.
`@RequsetBody`는 HTTP 요청으로 같이 넘어오는 Header의 Content-type을 보고 어떤 `Converter`를 사용할지 정하기에 Content-type을 반드시 명시해야 한다.
(Content-type은 따로 default 값이 없다. 그래서 프론트엔드와 협업할 때 종종 요청에 Content-Type을 명시하지 않아 에러가 발생하기도 한다)
📌 Content-Type이란? HTTP 요청이나 응답이 주고받는 데이터가 어떤 형식인지 명시하는 헤더이다.
naver 메인화면 📚 자주 사용하는 Content-type 종류 - application/json : JSON 데이터 전송 - application/x-www-form-urlencoded : 폼 데이터 전송 (HTML 폼 형식) - application/xml : XML 데이터 전송 - text/plain : 단순 문자열 데이터를 보낼 때 사용 - multipart/form-data : 파일 전송 (spring에서는 주로 @RequestPart와 함꼐 사용) - text/html : HTML 전송
자 이제 Content-type을 대략적으로 알았으니 이제 좀 전에 말한 "Converter"에 대해서 알아보자.
일단 자세히 알아보기 전에 요약하자면 스프링에서는 위에서 말한 작업을 HttpMessageConverter라는 친구가 자동으로 처리해준다. 기본적으로는 Jackson이라는 라이브러리를 사용해서 JSON -> 객체(역직렬화) / 객체 -> JSON(직렬화)로 변환을 해준다.
`objectMapper.readValue`를 호출해 JSON을 `ReservationRequest` 같은 자바타입으로 변환한다.
이렇게 변환할 때 해당 객체는 반드시 기본 생성자(매개변수가 없는 "비어있는" 생성자)를 가지고 있어야한다.
물론 자바가 아무 생성자도 가지고 있지 않다면 컴파일러가 자동으로 기본 생성자를 만들어주기 때문에 신경쓸 필요가 없지만 매개변수가 있는 생성자를 만들면 명시적으로 기본 생성자를 만들어줘야한다.
그렇다면 왜 기본생성자를 요구할까?
Jackson 라이브러리는 객체를 역직렬화(deserialization)할 때 내부적으로 객체를 먼저 생성하고 그 다음에 리플렉션으로 필드를 채워 넣기 때문이다.
🔎 Jackson의 역직렬화 과정
1. 객체 생성 Jackson은 대상 클래스의 기본 생성자를 호출하여 객체를 생선한다. 기본 생성자가 없을 경우, @JsonCreator와 @JsonProperty를 사용하여 매개변수 있는 생성자를 통해 객체를 생성할 수 있다.
public class Person {
private final String name;
private final int age;
@JsonCreator
public Person(@JsonProperty("name") String name,
@JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
// Getter methods...
}
2. 필드 값 주입 생성된 객체에 JSON의 각 필드 값을 주입한다. 이때, Jackson은 다음과 같은 방법을 사용한다. 1. Setter 메서드 : 해당 필드에 대한 setter 메서드가 존재하면 이를 호출하여 값을 설정한다. 2. 직접 필드 접근 : setter 메서드가 없을 경우, 리플렉션을 통해 필드에 직접 접근하여 값을 설정한다.
🔎 Jackson의 직렬화 과정
직렬화 할 때는 이미 만들어진 객체에서 Jackson 내부적으로 getter 메서드를 호출해서 값을 읽고 JSON 문자열로 변환한다. 따라서 역질렬화 과정과 다르게 기본 생성자는 필요없고 getter만 있으면 된다.
만약 getter이 없다면 Jackson은 아무 필드도 읽지 못해서 빈 JSON이 나올 수 있다. 하지만 `@JsonProperty`나 `@JsonAutoDetect`등의 어노테이션을 쓰면 리플렉션으로 필드 값을 읽을 수 있도록 강제할 수 있다.
public class Person {
@JsonProperty
private String name;
@JsonProperty
private int age;
}
➡ 이런 경우엔 getter 없어도 직렬화 가능!
위와 같은 과정을 거치면 요청 Body에 있는 JSON이 객체로 변환되고 그것을 통해 내부 비즈니스 로직이 동작한다.
기본 생성자가 없는 경우도 있는데 이는 왜 역직렬화가 되는거죠?
Jackson은 객체에 생성자가 하나만 있으면 그 생성자를 묵시적(@JsonCreator 없이)으로 ‘Creator’로 간주해서 디폴트 생성자 없이도 역직렬화해 준다.
다만 이게 잘 동작하려면 다음 조건이 충족되어야 한다.
생성자가 오직 하나여야 한다
다른 파라미터 생성자가 하나라도 더 있으면 Jackson은 어느 쪽을 쓸지 몰라서 에러를 낸다.
파라미터 이름이 JSON 필드 이름과 일치해야 한다
<작성 예정>
Controller에서 받을때 어노테이션 생략시 @ModelAttribute가 기본값이므로 @RequestBody를 사용하고자 하는 경우에는 반드시 기술해야 한다.