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