@RequestParam
- URL / Form 에서 오는 값을 읽을 때 사용한다.
ex) ?name=jenson&age=20 이라는 쿼리 파라미터가 있고, 해당 URL을 통해 값을 가져오고 싶다면?
@GetMapping
public void read(
@RequestParam("name") String name,
@RequestParam("age") int age
) { ... }
위와 같이 가져오려는 모든 매개변수에 @RequestParam 어노테이션을 붙여줘야 한다. 단, 파라미터 이름이 변수 이름과 같다면, @RequestParam 값은 생략 가능하다.
@GetMapping
public void read(
@RequestParam String name,
@RequestParam int age
) { ... }
@RequestBody
- HTTP 요청 바디를 읽는다.
- RequestParam과 같이 값을 읽어올 때 사용되지만, Json 처럼 요청 Body 전체를 자바 객체로 바꿀 때 사용한다.
@GetMapping
public void read(@RequestBody Person person) {
String name = person.getName();
int age = person.getAge();
}
궁금했던 점
1) Json은 위와 같은 형식으로 데이터를 제공해줄텐데 String, int 값을 누가 Person 객체로 만들어주는걸까?
{
name : "jenson",
age : 20
}
- @RequestBody를 사용하면 Json -> 객체로 변경해준다. 이를 역직렬화라고 한다. (+ 반대로 객체 -> Json으로 변경은 직렬화라고 한다).
주의사항
- HTTP 요청은 Body를 한 번만 읽을 수 있다. 하지만 Form 데이터는 request.getParameter()를 통해 읽게 된다 (이를 통해 body를 자동으로 읽어버림). 이후에 @RequstBody로 body를 다시 읽으려고 할 때 에러가 날 수 있다.
즉, Form데이터를 사용할 때는 @RequestParam을 사용하자.
(+ 쿼리파라미터를 통해 URL를 읽는 방법에는 애초에 HTTP 요청 바디가 없다. 즉 이 경우에는 @RequestParam을 사용해야 한다.)
@PathVariable
- URL 자체에 포함된 값을 가져온다.
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
@RequestParam과의 차이점
- owners?ownerId=42 -> @RequestParam
- onwers/42 -> @PathVariable
경로에 값이 들어있다면 @PathVariable / 쿼리스트링에 들어있다면 @RequestParam
@PathVariable은 누구(what)를, @RequestParam은 어떻게(how)를 표현할 때 사용한다.
예시 코드
@GetMapping("/users/{userId}/posts")
public List<Post> getPosts(
@PathVariable Long userId,
@RequestParam int page,
@RequestParam int size
)
- userId → 어떤 유저의 글인지 (자원 식별)
- page, size → 어떻게 가져올지 (옵션/필터)
@ResponseBody
주로 Json값을 반환할 때 사용한다. 해당 어노테이션을 붙인 후 객체를 반환하면, Spring의 Jackson 이 직렬화를 해 Json으로 반환해준다. 메서드가 성공한다면, 기본적으로 상태코드를 200으로 반환한다.
예시코드
class Member {
private final String name;
private final int age;
}
@ResponseBody
public Member findMember() {
Member member = getMember();
return member;
}
member 객체를 반환하지만 @ResponseBody를 사용한다면
{
name : "Jenson",
age : 25
}
위와 같은 Json 값이 반환된다.
@ResponseEntity
@ResponseBody와 같지만, 명시적으로 Http의 Header와 상태 코드를 지정할 수 있다.
class Member {
private final String name;
private final int age;
}
public ResponseEntity<Member> findMember() {
Member member = getMember();
return ResponseEntity.ok(member);
}
ok()는 상태코드 200을 반환하지만, 보다 유연하게 관리할 수 있다.
noContent(), notFound(), accepted() ...
ResponseEntity.status(400) 과 같이 직접 상태 코드를 관리할 수도 있다.
하지만, @ResponseBody 또한 @ResponseStatus 를 통해 상태 코드를 관리할 수도 있다.
class Member {
private final String name;
private final int age;
}
@ResponseBody
@ResponseStatus(HttpStatus.OK) // 해당 어노테이션을 통해 상태 코드 조작 가능
public Member findMember() {
Member member = getMember();
return member;
}
그러면 둘 다 똑같아 보이는데... 어떤 차이점이 있을까?
아래의 예시 코드를 통해 이해해보자.
class Member {
private final String name;
private final int age;
}
public ResponseEntity<Member> findMember() {
Member member = getMember();
if (member == null) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.ok(member);
}
member가 존재하지 않는다면 상태코드 400을 반환하고, 존재한다면 200을 반환하려고 한다.
@ResponseEntity 를 사용한다면 동적으로 상태코드를 반환할 수 있지만, @ResponseStatus를 사용했다면 정적으로 반환할 수 밖에 없다.
결론: @ResponseEntity를 사용하면 보다 유연하게 반환값을 관리할 수 있다. 다만, @ResponseBody를 사용할 때 보다 코드의 양이 조금 늘어날 수 있다.
추가 공부
Controller vs RestController
@RestController의 경우 어노테이션 내부를 확인해보면 Controller + @ResponseBody 인 것을 확인할 수 있다.
방탈출 미션에서의 코드를 보며 어떤 문제가 생기는 지 알아보자.
@RestController
public class RoomescapeController {
@GetMapping("/admin")
public String admin() {
return "admin/index";
}
내가 의도한 코드는 admin/index.html이 렌더링 되어 /admin 페이지로 반환이다. 예상대로 동작할까?
이상한 결과값이 나온다.
이상한 결과값이 나온다. 이유가 뭘까?
@RestController 내부에 @ResponseBody 어노테이션이 존재하기 때문이다. 해당 어노테이션이 붙어있기에 반환할 때 html 페이지가 렌더링 되지 않고, 문자열인 "admin/index"가 반환되는 것이다.
정리하자면 HTML View를 리턴하고 싶다면 @ResponseBody 어노테이션이 없는 @Controller를 사용해야 한다.
어떤 값은 HTML View를 반환해야 하고 어떤 값은 Json을 반환해야 한다면, Controller를 사용하며, Json 값이 필요한 메서드에 @ResponseBody를 일일히 붙여주자.
만약, HTML View를 반환해야 할 일이 없다면 그냥 @RestController를 쓰자.
'Spring' 카테고리의 다른 글
Spring HTTP 처리 과정 (0) | 2025.06.29 |
---|---|
Spring Core 꼬리 질문 해보기 (0) | 2025.04.28 |