Spring Framework 시리즈 13화 – @Controller와 @RestController 내부 구조와 처리 흐름
스프링에서 자주 사용하는 @Controller와 @RestController, 그리고 @RequestMapping 애노테이션이 DispatcherServlet과 어떻게 연결되고 처리되는지 흐름 중심으로 설명합니다.
@Controller와 @RestController는 뭐가 다른가요?
가장 간단히 정리하면:
애노테이션반환 방식용도
| @Controller | 뷰 이름 | 웹 페이지 응답 (HTML) |
| @RestController | HTTP 본문(JSON 등) | API 응답 |
즉, @RestController는 사실상 아래 두 가지의 합성입니다.
@RestController
== @Controller + @ResponseBody
DispatcherServlet과 어떻게 연결될까?
Spring MVC가 내부적으로 요청을 처리할 때, DispatcherServlet은 먼저 HandlerMapping을 통해 어떤 메서드가 이 요청을 처리할지 찾습니다.
그때 @RequestMapping, @GetMapping, @PostMapping 같은 애노테이션이 붙은 메서드를 RequestMappingHandlerMapping이 인식해서 매핑 테이블을 구성합니다.
예제
@RestController
public class HelloApi {
@GetMapping("/api/hello")
public String hello() {
return "hello world";
}
}
이 코드는 Spring 부팅 시 다음처럼 매핑됩니다.
GET /api/hello → HelloApi.hello()
매핑 구조를 관리하는 핵심 클래스
컴포넌트역할
| RequestMappingHandlerMapping | 애노테이션 기반 메서드 매핑 수집 |
| RequestMappingHandlerAdapter | 매핑된 메서드를 실행하고 결과를 반환 |
| HandlerMethod | 메서드 정보를 캡슐화한 객체 |
| HandlerInterceptor | (옵션) 요청 전후 로직 삽입 |
그럼 우리가 만든 컨트롤러는 실제로 어떤 객체로 바뀌는가?
Spring 부팅 시, @Controller나 @RestController가 붙은 클래스들은 Bean으로 등록된 후,
RequestMappingHandlerMapping이 해당 클래스의 public 메서드를 모두 스캔해서 RequestMappingInfo로 정리해둡니다.
@RequestMapping(path = "/users", method = GET)
→ HandlerMethod: UserController.list()
이렇게 매핑 테이블이 만들어지고, 요청이 들어오면 그 경로와 method를 기준으로 적절한 HandlerMethod가 호출되는 구조입니다.
응답 방식의 차이: 뷰 vs 바디
// HTML View 반환
@Controller
public class WebController {
@GetMapping("/home")
public String home(Model model) {
model.addAttribute("msg", "Hello Spring!");
return "home"; // → home.html
}
}
// JSON 반환
@RestController
public class ApiController {
@GetMapping("/api/data")
public Map<String, String> data() {
return Map.of("message", "Hello JSON");
}
}
- @Controller는 ViewResolver를 통해 .html 등을 렌더링
- @RestController는 Jackson 등을 이용해 객체를 JSON 등으로 변환해서 본문에 직접 반환
동작 흐름 요약
요청 → DispatcherServlet
→ RequestMappingHandlerMapping (어떤 메서드?)
→ HandlerAdapter (어떻게 실행?)
→ @Controller or @RestController → 메서드 실행
→ 결과 반환 (뷰 이름 or 객체)
→ ViewResolver (필요 시)
→ 응답 전송
실무 팁 – RestController에도 ViewResolver가 동작할까?
결론부터 말하자면, @RestController는 ViewResolver를 사용하지 않습니다.
이유는 @ResponseBody가 자동 적용되어 있기 때문입니다.
하지만, 실수로 @RestController에서 문자열을 반환하면 다음과 같은 일이 벌어질 수 있습니다:
@RestController
public class WrongController {
@GetMapping("/test")
public String test() {
return "test"; // 텍스트 응답으로 "test"가 감
}
}
→ 이걸 @Controller로 바꾸면 "test.html"을 찾으려고 하겠죠?
우리가 매일 쓰는 애노테이션의 이면
스프링이 편한 이유 중 하나는 복잡한 구조를 간단한 애노테이션 하나로 추상화해줬기 때문입니다.
하지만 내부 구조를 이해하면, 디버깅이 쉬워지고 확장도 훨씬 자유로워집니다.
@Controller, @RestController, @RequestMapping의 핵심은 결국 요청을 적절한 메서드로 연결해주는 일입니다.
그리고 이 모든 것은 DispatcherServlet이 뒤에서 조용히 처리하고 있죠.