728x90
💡 Java + Spring Boot을 이용하여 클라이언트와 http 통신을 할 때 전달받는 http 요청 메시지나, 전달하는 응답 메시지는 json 형식의 데이터로 이루어지는 경우가 많다. 이때 자바 객체(dto)에서 json 데이터로 어떻게 변환이 되는지를 잘 모르고 있는 것 같아 한 번 정리해보려고 한다.
우선 HTTP 통신에 대해 기본적인 내용을 훑고 넘어가자.
- HTTP 통신 중 가장 많이 사용하는 HTTP/1.1 기반의 통신 방식에 대해 알아보겠다.
- HTTP/1.1 기반의 통신 방식은 Request-Response 방식으로 동작한다. 클라이언트가 서버에 요청을 하면, 서버는 클라이언트의 요청에 대해 응답한다.
- 이때, 클라이언트는 HTTP 메서드(GET, POST, PUT, DELETE 등)를 이용하여 요청을 보내며, 서버는 요청에 따라 처리된 결과를 HTTP 상태 코드(200 OK, 404 Not Found 등)와 함께 응답한다.
요청과 응답의 구조는 다음과 같다.
[Request]
HTTP Method (GET, POST, PUT, DELETE 등)
URI (Uniform Resource Identifier)
HTTP Version
[Headers]
Key: Value
Key: Value
...
[Body]
(Optional)
---------------------------------------
[Response]
HTTP Version
Status Code
Status Message
[Headers]
Key: Value
Key: Value
...
[Body]
(Optional)
메시지에서 [Body] 안에는 어떤 내용이 들어갈까?
- HTTP 프로토콜에서 메시지 바디(body)는 요청(request)이나 응답(response)에 대한 본문 데이터를 담고 있는 부분이다.
- 이 데이터의 형식은 요청/응답의 목적에 따라 달라질 수 있다.
- 대표적인 데이터 형식으로는 JSON, XML, HTML, 텍스트 등이 있으며, 이러한 데이터 형식으로 주고받은 데이터는 개발자가 분석 가능한 형태로 파싱 되어 처리된다.
- 예를 들어, 클라이언트에서 서버로 POST 요청을 하면 요청 바디에는 클라이언트에서 서버로 전송하고자 하는 데이터가 들어있을 것이다.
- 같은 방식으로 서버에서 클라이언트로 응답을 보낼 때도 응답 바디에 데이터가 들어있을 것이다.
- 이 데이터를 분석하여 클라이언트와 서버는 데이터를 서로 주고받고 원하는 대로 처리할 수 있다.
Request 관련 애너테이션
@ModelAttribute
- HTTP 요청의 다수의 매개변수(쿼리 스트링, 쿼리 파라미터)들을 일대일로 자바 객체에 바인딩하는 데 사용된다.
- 컨트롤러의 메서드 파라미터에 적용할 수 있다.
- 클라이언트가 전송한 요청 데이터를 자동으로 도메인 객체나 DTO(Data Transfer Object)로 변환하여 메서드에 전달할 수 있다.
- 변환이 아닌 바인딩을 사용하기 때문에 Setter 메서드가 없으면 저장되지 않는다.
- 스프링 MVC에서는 컨트롤러 메서드의 파라미터가 복합 타입(예: DTO 클래스)인 경우 암묵적으로 @ModelAttribute를 적용한다.
- @ModelAttribute 애너테이션을 생략해도 된다는 말이다.
- 따라서 코드 예제에서와 같이 @ModelAttribute 애너테이션을 생략해도 요청 매개변수가 DTO 객체에 자동으로 바인딩된다.
예시 코드
@GetMapping("/sample")
public ResponseEntity<SampleDto> getSampleData(@ModelAttribute SampleDto sampleDto) {
// 로직 구현
return ResponseEntity.ok(sampleDto);
}
@RequestParam
- HTTP 요청에서 단일 파라미터를 컨트롤러의 메서드 파라미터와 바인딩하는 데 사용된다.
- RequestParam 역시 생략 가능하다.
- URL 경로 예시 : /members?id=1
예시 코드
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
...
@GetMapping
public ResponseEntity getMemberByParam(@RequestParam("id") Long id) {
Optional<Member> optionalMember = memberRepository.findById(id);
MemberDto.Response response = MemberDto.Response.builder()
.id(optionalMember.get().getId())
.name(optionalMember.get().getName())
.age(optionalMember.get().getAge())
.build();
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
- URL 경로에서 ? 뒤에 key-value 쌍을 전달하는 방식이다.
@PathVariable
- URL 경로에 있는 변수를 컨트롤러의 메서드 매개변수로 바인딩한다.
- {}(중괄호) 안에 변수 이름을 작성하며 이름을 지정하지 않으면 메서드의 매개변수 이름으로 자동 지정된다.
- URL 경로 예시 : /members/{id}이다.
예시 코드
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
...
@GetMapping("/{id}")
public ResponseEntity getMemberByPathVariable(@PathVariable Long id) {
Optional<Member> optionalMember = memberRepository.findById(id);
MemberDto.Response response = MemberDto.Response.builder()
.id(optionalMember.get().getId())
.name(optionalMember.get().getName())
.age(optionalMember.get().getAge())
.build();
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
- URL에 member의 id를 입력하고 get 요청을 보내면 db에 id=1인 member의 id, age, name을 찾는 쿼리가 날아간다.
@PathVarialble VS @RequestParam
- @PathVariable는 경로 상에서 매개변수 정보를 전달받고,
- @RequestParam은 HTTP Request URL의 Query Parameter에서 매개변수 정보를 전달받는다.
@RequestBody
- HTTP Request Body에 포함된 데이터를 자바 객체로 변환하여 매핑한다.
- 보통 JSON이나 XML 형식의 데이터가 HTTP 요청 바디에 실려 컨트롤러 단에 오게 되면 이를 자바 객체로 변환하여 자바 언어로 비즈니스 로직을 수행하도록 돕는다.
- HttpMessageConverter 인터페이스를 구현한 객체에 의해 수행된다.
@RequestBody를 메서드 파라미터에 붙이지 않을 경우
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@PostMapping("/dtos")
public MemberDto.Response createMemberToDto(MemberDto.Post post) {
Member member = Member.builder()
.name(post.getName())
.age(post.getAge())
.build();
Member savedMember = memberRepository.save(member);
MemberDto.Response response = MemberDto.Response.builder()
.id(savedMember.getId())
.name(savedMember.getName())
.age(savedMember.getAge())
.build();
return response;
}
}
- @RequestBody를 붙이지 않아 HTTP 요청 바디에 들어있는 JSON 데이터가 자바 객체로 변환되지 않아 dto 필드의 기본값인 null과 0이 데이터베이스에 그대로 저장되었다.
@RequestBody를 메서드 파라미터에 붙인 경우
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@PostMapping("/dtos")
public MemberDto.Response createMemberToDto(@RequestBody MemberDto.Post post) {
...
}
- @RequestBody를 붙이고 실행하면 정상적으로 JSON 데이터 -> 자바 객체로 변환이 되어 HTTP 요청 바디에 있는 데이터를 정상적으로 자바 언어로 다룰 수 있다.
Response 관련 애너테이션
@ResponseBody
- HTTP 응답의 결과를 가진 자바 객체를 JSON 데이터로 변환한다.
- 클래스 단, 메서드 단에 붙일 수 있다.
- HttpMessageConverter 인터페이스를 구현한 객체에 의해 수행된다.
@RestController
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
- @Controller + @ResponseBody로 이루어져 있으며 스프링에게 컨트롤러 클래스임을 알림(빈으로 등록)과 동시에 클래스단에 @ResponseBody 애너테이션을 적용하여 따로 자바 객체를 JSON 응답 데이터로 변환하는 작업을 할 필요 없게 도와준다.
ResponseEntity 클래스
- HTTP 응답 데이터를 보다 개발자 친화적으로 보낼 수 있도록 도와주는 클래스이다.
- 자바 객체를 JSON 응답 데이터로 변환한다.
- HttpMessageConverter 인터페이스를 구현한 객체에 의해 수행된다.
- 이에 더해 응답 데이터에 상태 코드, 헤더 등을 개발자가 원하는 대로 추가할 수 있다.
예시 코드
@Controller // 컨트롤러 애너테이션
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@PostMapping("/dtos")
@ResponseBody // ResponseBody 애너테이션
public MemberDto.Response createMemberToDto(@RequestBody MemberDto.Post post) {
Member member = Member.builder()
.name(post.getName())
.age(post.getAge())
.build();
Member savedMember = memberRepository.save(member);
MemberDto.Response response = MemberDto.Response.builder()
.id(savedMember.getId())
.name(savedMember.getName())
.age(savedMember.getAge())
.build();
return response;
}
@PostMapping("/entities")
public Member createMemberToEntity(@RequestBody MemberDto.Post post) {
Member member = Member.builder()
.name(post.getName())
.age(post.getAge())
.build();
Member savedMember = memberRepository.save(member);
MemberDto.Response response = MemberDto.Response.builder()
.id(savedMember.getId())
.name(savedMember.getName())
.age(savedMember.getAge())
.build();
return savedMember;
}
@PostMapping("/responseentities")
public ResponseEntity createMemberToResponseEntity(@RequestBody MemberDto.Post post) {
Member member = Member.builder()
.name(post.getName())
.age(post.getAge())
.build();
Member savedMember = memberRepository.save(member);
MemberDto.Response response = MemberDto.Response.builder()
.id(savedMember.getId())
.name(savedMember.getName())
.age(savedMember.getAge())
.build();
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
}
- createMemberToDto 메서드의 경우 @ResponseBody 애너테이션이 있어 HTTP 응답 데이터로 JSON 데이터가 정상적으로 변환되었음을 확인할 수 있다.
- @ResponseBody를 제거하고 재실행하면 위와 같은 에러가 발생한다.
- 404 Not Found가 발생한다.
- createMemberToEntity 역시 @ResponseBody가 없으면 위와 같은 에러가 발생한다.
- createMemberToResponseEntity의 경우 반환 타입을 ResponseEntity로 하여 JSON 데이터로 변환하고 상태코드까지 개발자가 설정한 201 Created로 응답하는 것을 확인할 수 있다.
결론
- Spring MVC Restful 웹 서비스를 개발할 때 자바 객체와 JSON 데이터 간 변환을 도와주는 여러 애너테이션이 있다.
- 클라이언트로부터 받은 HTTP 요청은 경로(@PathVariable), 쿼리 스트링(@RequestParam), 요청 바디(@RequestBody) 등의 데이터를 자바 객체로 변환해 주는 애너테이션을 이용하면 된다.
- HTTP 응답의 경우에는 따로 구분할 필요 없이 @RestController를 사용하자.
- 그럼 메서드 단에 따로 @ResponseBody를 적용할 필요없이 모든 컨트롤러 메서드에서 자바 객체 -> JSON 데이터로 HTTP 응답 데이터를 자동 변환해 준다.
- 만약 좀 더 커스터마이징이 필요하다면 ResponseEntity를 사용하여 상태 코드 및 헤더 등을 개발자가 원하는 방향으로 설정할 수 있다.
- 개발팀에서 응답 데이터에 대한 규약을 만들어 통일하고자 한다면 ResponseEntity를 상속받는 클래스를 만들어 새로운 응답 데이터 형식을 커스텀하면 된다.
참고
뤼튼
728x90
'[Spring] > Spring MVC' 카테고리의 다른 글
스프링 MVC의 구조 (0) | 2023.03.23 |
---|---|
스프링 MVC에서의 프런트 컨트롤러(Front Controller) & 어댑터(Adapter) 패턴 (0) | 2023.03.22 |
자바에서의 MVC 패턴(Servlet -> JSP -> 스프링 MVC까지) (0) | 2023.03.16 |
HttpServletRequest & HttpServletResponse 정리 (0) | 2023.03.15 |
트랜잭션 전파(Transaction Propaganda) & 트랜잭션 격리 레벨(Isolation Level) 1분 요약 정리 (0) | 2022.12.04 |