지난 포스팅에서는 클라이언트의 요청을 메서드에 매핑하는 방법에 대해 학습했다. @RequestMapping 어노테이션을 통해서 메서드에 요청을 매핑할 것이라고 표현했고, 속성으로 URL, HTTP Method(GET, POST 등등), Request 파라미터, Header, Media Type과 같이 다양한 정보를 담아서 요청을 특정할 수 있었다. (지난 포스팅 참고!)
이번 포스팅에는 각 메서드에 매핑된 요청을 처리하는 방법에 대해서 공부할 것이다!
스프링 프레임워크에서는 @RequestMapping 어노테이션을 사용하여 작성된 핸들러 메서드가 파라미터와 반환값을 구성하는 방법을 다양하게 제공한다.
크게 두 가지에 처리를 할 수 있다.
- 메서드로 들어오는 파라미터
- 메서드로부터 나가는 반환값
이 두 가지에 어떻게 처리를 해야 할 지에 대해서 지금부터 알아보겠다!
💋 Method Arguments
요청을 처리하기 위해 파라미터에 붙이는 어노테이션에 대한 내용이다.
공식문서를 읽어보면, 다양한 어노테이션이 있다. 이것들은 메서드 파라미터에 붙이는 것들이고, Method Arguments라고 한다. 공식문서의 Method Arguments의 표에 나와있는 것들 중 일부를 가져와봤다. 천천히 공부해보자!
✔ @RequestParam
HTTP 요청에서 전달되는 파라미터 값을 컨트롤러 메서드의 파라미터로 바인딩하는 어노테이션이다.
아래와 같은 URL로 GET 요청이 들어왔다고 생각해 보자
/users?name=hello
이 URL에서 name의 값인 hello를 어떻게 받을 수 있을까?
파라미터 이름인 name과 일치하는 HTTP 요청 파라미터의 값을 컨트롤러 메서드 파라미터에 바인딩할 수 있다.
@GetMapping("/users")
public ResponseEntity<List<User>> requestParam(@RequestParam("name") String userName) {
List<User> users = Arrays.asList(
new User(userName, "email"),
new User(userName, "email")
);
return ResponseEntity.ok().body(users);
}
이렇게 내가 받고자 하는 값이 hello라고 하면 그 앞에 있는"name"을 받아오면 된다.
웹에서 서버와 클라이언트 사이에 주고받는 메세지인 요청과 응답에 대해서는 이전의 포스팅에 간단하게 설명해 놓았다!
✔ @ModelAttribute
컨트롤러 메서드의 파라미터로 전달된 객체에 HTTP 요청 파라미터를 모두 바인딩할 수 있는 어노테이션이다.
@ModelAttribute는 주로 폼 데이터를 객체에 바인딩 하거나, 모델 객체를 초기화 할 때 사용된다.
아래에 두 가지 사용에 대해 예시를 통해 소개하겠다!
예를 들어 설명해보겠다!
[폼 데이터를 객체에 바인딩]
아래와 같은 코드가 있다고 생각해보자!
@PostMapping("/user")
public String addUser(User user) {
// user 정보를 데이터베이스에 저장하는 코드
return "redirect:/users";
}
위 코드에서 User 클래스는 다음과 같이 정의되어 있다고 가정해 보자.
public class User {
private String name;
private int age;
// getter, setter 생략
}
사용자가 전송한 폼 데이터를 User 객체로 변환하기 위해 @ModelAttribute를 사용할 수 있다. 다음과 같이 코드를 변경해보자!
@PostMapping("/user")
public String addUser(@ModelAttribute("user") User user) {
// user 정보를 데이터베이스에 저장하는 코드
return "redirect:/users";
}
위 코드에서 @ModelAttribute("user") 어노테이션은 User 객체를 모델 객체의 "user" attribute로 추가하도록 지시한다. 이제 사용자가 전송한 폼 데이터는 "user" attribute를 가지는 User 객체로 변환되어 핸들러 메서드에 전달된다.
[모델 객체 초기화]
@GetMapping("/user")
public String getUser(@RequestParam("id") int id, @ModelAttribute("user") User user) {
// id를 사용하여 데이터베이스로부터 user 정보를 가져와 user 객체를 초기화하는 코드
return "user";
}
위 코드에서 @ModelAttribute("user") 어노테이션은 user 객체를 모델 객체의 "user" attribute로 추가하고, 초기화하는 데 사용된다.
[@RequestParam vs @ModelAttribute]
@RequestParam은 단일 값을 바인딩하고 @ModelAttribute는 객체를 바인딩하는 데 사용된다.
또한 @ModelAttribute는 파라미터 이름이 지정되지 않으면 객체 이름이 자동으로 사용되므로, 파라미터 이름과 일치하지 않아도 된다.
✔ @RequestHeader
해당 파라미터와 매칭되는 HTTP 요청 헤더 값을 추출할 때 사용되는 어노테이션이다. HTTP 요청 헤더 정보를 읽어와서 매개변수로 전달받아 사용할 수 있다.
예를 들어, 아래와 같이 생긴 요청의 헤더가 있다고 생각해 보자.
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
여기서 Accept-Language만 추출하려면?
@GetMapping("/greeting")
public String greeting(@RequestHeader("Accept-Language") String acceptLanguage) {
// Accept-Language 값을 사용하여 언어 설정 등을 처리하는 코드
return "greeting";
}
이외에도 @RequestHeader는 사용자 에이전트 정보를 추출하여 로그를 남기는 등의 용도로 사용될 수 있다.
✔ @RequestBody
해당 매개변수와 매칭되는 HTTP 요청 본문(body)을 추출할 때 사용된다.
예를 들어서, HTTP POST 요청으로 JSON 형태의 데이터를 전송하고, 이 데이터를 자바 객체로 변환하려 한다.
@PostMapping("/user")
public void addUser(@RequestBody User user) {
// user 정보를 데이터베이스에 저장하는 코드
}
@RequestBody 어노테이션은 HTTP 요청 본문에 포함된 JSON 데이터를 User 객체로 변환하도록 한다.
이때 HTTP Message Converter가 사용되는데, HTTP 요청과 응답의 Body를 자바 객체로 변환해주는 역할을 위해 스프링 프레임워크에서 사용되는 기능이다. 클라이언트와 서버 사이의 데이터 전송을 위해 사용되는 데이터의 형태(예. JSON, XML, HTML 등등)를 자바 객체로 변환하거나, 반대로 자바 객체를 JSON, XML 등의 데이터 형태로 변환해서 전송할 수 있도록 도와준다.
💋 Return Values
요청을 처리하기 위해 반환값에 붙이는 어노테이션에 대한 내용이다.
마찬가지로 공식문서를 참고하면 아래 표보다 훨씬 많은 어노테이션들이 있다.
✔ @ResponseBody
메소드가 반환하는 값을 HTTP 응답 본문에 직접적으로 쓰도록 지정하는 어노테이션이다.
예를 들어, 클라이언트가 HTTP 요청을 보내면 서버는 요청에 대한 응답으로 HTML, JSON, XML 등의 데이터를 반환하는데, @ResponseBody 어노테이션을 사용하면 메소드가 반환하는 데이터가 HTTP 응답 본문에 바로 쓰인다.
컨트롤러에서 반환한 객체를 HttpMessageConverter를 거쳐 HTTP 응답 본문에 직접 쓰기 때문에, View Resolver를 거치지 않고 바로 데이터를 반환할 수 있다. 결과적으로 전송되는 데이터의 크기를 줄이고, 처리 속도를 높일 수 있다.
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@ResponseBody는 클래스 레벨에서 사용하게 되면, Controller 내의 모든 메서드에 붙인 효과가 된다. @Controller에 @ResponseBody를 붙여 두 어노테이션을 조합하면 @RestController이다.
@ResponseBody에는 하지만 단점이 있다. Response의 Header나 응답의 상태까지 전달할 수 없다는 것이다. 이 단점을 보완하기 위해 @ResponseStatus와 같은 어노테이션을 사용해 응답 상태 코드를 지정할 수 있지만 번거롭고, 응답의 Header를 설정하기는 여전히 어렵다.
응답을 조금 더 세밀하게 전달하기 위해 만들어진 ResponseEntity를 아래에서 소개한다!
✔ ResponseEntity
Spring MVC에서 HTTP 응답을 보낼 때 사용하는 클래스 중 하나이다.
ResponseEntity는 @ResponseBody랑 비슷한데, 응답 본문과 함께 HTTP 상태 코드, 응답 헤더를 포함하여 전체 응답을 구성할 수 있어서, HTTP 응답을 자세하게 제어할 수 있습니다.
예를 들어, HTTP 상태 코드를 지정하지 않으면 기본적으로 200 OK 상태 코드가 반환되지만 ResponseEntity를 사용하면 상태 코드를 지정하여 200 OK 이외의 상태 코드를 반환할 수 있다.
또한, ResponseEntity를 사용하면 응답 본문의 데이터 타입을 지정할 수 있다. 예를 들어, JSON 응답을 보낼 경우에는 ResponseEntity에 JSON 데이터와 함께 MediaType.APPLICATION_JSON을 설정하여 JSON 응답을 반환할 수 있다.
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user);
} else {
return ResponseEntity.notFound().build();
}
}
ResponseEntity를 사용하여 HTTP 응답을 반환하고 있다
ok() 메소드를 사용하여 성공적인 응답을 반환하고, notFound() 메소드를 사용하여 실패한 응답을 반환하고 있다.
이렇게 ResponseEntity를 사용하면 HTTP 응답을 더욱 세밀하게 제어할 수 있다.
마지막으로 ResponseEntity를 생성하는 두 가지 방법에 대해서 설명하고 포스팅을 마무리하려고 한다!
1. ResponseEntity 객체를 직접 생성
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
ResponseEntity 객체를 직접 생성해서 반환하는 경우이다.
public ResponseEntity(@Nullable T body, HttpStatus status) {
this(body, null, status);
}
코드를 뜯어보면 생성자가 정말 많은데 그중 하나이다.
처음의 코드를 다시 보면, 첫 번째 파라미터로는 User 객체, 두 번째 파라미터로는 HttpStatus.OK를 전달하고 있다.
성공적인 응답일 경우에는 HttpStatus.OK와 함께 User 객체를 전달하고, 실패한 응답일 경우에는 HttpStatus.NOT_FOUND만 전달한다.
2. ResponseEntity의 static 메소드를 사용
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user);
} else {
return ResponseEntity.notFound().build();
}
}
ResponseEntity의 builder를 사용해 위의 코드와 같이 만들 수도 있다!