Spring의 @Controller와 @RestController 어노테이션의 차이점과 사용 시나리오를 실제 예제와 함께 알아봅니다
Spring Framework에서 웹 요청을 처리하는 컨트롤러를 정의할 때 @Controller와 @RestController 어노테이션을 사용할 수 있습니다. 이 두 어노테이션의 주요 차이점은 HTTP 응답을 어떻게 처리하는가에 있습니다.
이 포스트에서는 두 어노테이션의 차이점과 각각의 사용 시나리오를 실제 예제와 함께 알아보겠습니다.
@Controller는 전통적인 Spring MVC 컨트롤러를 정의할 때 사용됩니다.
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserController {
@GetMapping("/users")
public String listUsers(Model model) {
// 데이터를 모델에 추가
model.addAttribute("users", userService.findAll());
// "users" 라는 뷰 이름을 반환
// ViewResolver가 "users.html" 또는 "users.jsp" 템플릿을 찾아서 렌더링
return "users";
}
@GetMapping("/user/{id}")
public String getUserDetail(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
return "user-detail";
}
}
@Controller에서도 JSON 응답을 할 수 있지만, 각 메서드마다 @ResponseBody를 추가해야 합니다.
@Controller
public class ApiController {
@GetMapping("/api/users")
@ResponseBody // 이 어노테이션이 있어야 JSON으로 변환됨
public List<User> getUsers() {
return userService.findAll();
}
@GetMapping("/api/user/{id}")
@ResponseBody // 매번 붙여줘야 함
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
@RestController는 RESTful 웹 서비스를 만들 때 사용하는 어노테이션입니다.
@ResponseBody가 자동으로 적용됩니다실제로 @RestController는 다음과 같이 정의되어 있습니다:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody // 핵심: @Controller + @ResponseBody의 조합
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
즉, @RestController = @Controller + @ResponseBody 입니다.
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class UserRestController {
@GetMapping("/users")
public List<User> listUsers() {
// 반환값이 자동으로 JSON으로 변환됨
return userService.findAll();
}
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/users/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
위 코드의 /api/users 엔드포인트를 호출하면 다음과 같은 JSON 응답이 자동으로 생성됩니다:
[
{
"id": 1,
"name": "홍길동",
"email": "hong@example.com"
},
{
"id": 2,
"name": "김철수",
"email": "kim@example.com"
}
]
| 특징 | @Controller | @RestController |
|---|---|---|
| 주 용도 | 웹 페이지 (View) 반환 | RESTful API (데이터) 반환 |
| 반환 타입 | View 이름 (String) | 객체 (JSON/XML로 자동 변환) |
| @ResponseBody | 각 메서드마다 필요 | 자동 적용 (생략 가능) |
| Template Engine | JSP, Thymeleaf 등과 연동 | 사용하지 않음 |
| 응답 형식 | HTML | JSON, XML |
| 사용 시나리오 | 서버 사이드 렌더링 웹 앱 | SPA, 모바일 앱 백엔드 API |
@Controller를 사용해야 하는 경우@Controller
public class WebController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "Welcome!");
return "index"; // templates/index.html 렌더링
}
@GetMapping("/login")
public String loginPage() {
return "login"; // templates/login.html 렌더링
}
}
@RestController
@RequestMapping("/api/v1")
public class ProductApiController {
@GetMapping("/products")
public List<Product> getProducts() {
return productService.findAll();
}
@PostMapping("/products")
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product saved = productService.save(product);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
}
실제 프로젝트에서는 두 어노테이션을 함께 사용하는 경우가 많습니다.
// 웹 페이지 컨트롤러
@Controller
public class WebController {
@GetMapping("/dashboard")
public String dashboard() {
return "dashboard"; // 대시보드 HTML 페이지 반환
}
}
// API 컨트롤러
@RestController
@RequestMapping("/api")
public class DashboardApiController {
@GetMapping("/dashboard/stats")
public DashboardStats getStats() {
return dashboardService.getStatistics(); // JSON 데이터 반환
}
}
위 구조에서:
/dashboard → HTML 페이지 렌더링/api/dashboard/stats → 대시보드에서 사용할 통계 데이터를 JSON으로 제공@RestController에서 HTTP 상태 코드와 헤더를 더 세밀하게 제어하려면 ResponseEntity를 사용합니다.
@RestController
@RequestMapping("/api/users")
public class UserApiController {
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity
.status(HttpStatus.CREATED)
.header("Location", "/api/users/" + savedUser.getId())
.body(savedUser);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(user -> ResponseEntity.ok(user))
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
@Controller → View Name → ViewResolver → HTML 응답
@RestController → Object → MessageConverter → JSON/XML 응답
Spring Boot를 사용하여 간단한 REST API를 만드는 방법을 단계별로 알아봅니다.
Apache Kafka의 핵심 개념을 이해하고 Spring Boot와 함께 사용하는 방법을 알아봅니다.
overflow-x: hidden으로 덮어둔 문제가 터지기까지. 테이블 오버플로부터 Radix Sheet 스크롤 잠금까지, 모바일 가로 스크롤 문제를 근본적으로 해결한 과정을 정리합니다.
백준 2526번 싸이클 문제를 풀며 나머지 연산의 순환 특성을 이해하고 최적화하는 과정