🍃 Spring MVC 아키텍처
웹 애플리케이션의 요청-응답 흐름을 시각적으로 이해해보세요!
🎨 색상 구분 가이드
🟧 개발자 구현 영역
Controller, Service, DAO 등 직접 코드를 작성해야 하는 부분입니다.
🟩 Spring 제공 영역
DispatcherServlet, HandlerMapping 등 Spring이 자동으로 처리해주는 부분입니다.
🟨 커스터마이징 가능
View, ViewResolver 등 Spring 기본 제공 + 개발자 수정 가능한 부분입니다.
💡 핵심 포인트 3가지
DispatcherServlet
모든 요청의 중심! 받고, 분배하고, 응답하는 총괄 매니저
관심사의 분리
Controller, Service, DAO, View가 각자 역할에만 집중!
설정의 중요성
XML 또는 어노테이션으로 URL-Controller 매핑 설정
Spring MVC란 무엇인가?
Spring MVC는 Spring Framework에서 제공하는 웹 애플리케이션 개발을 위한 MVC(Model-View-Controller) 패턴 구현체입니다. HTTP 요청을 받아 적절한 컨트롤러로 전달하고, 처리 결과를 뷰로 반환하는 전체 흐름을 프레임워크가 자동으로 관리합니다.
핵심 컴포넌트 설명
DispatcherServlet — 총괄 매니저
모든 HTTP 요청의 진입점입니다. 요청을 받아 HandlerMapping에 어떤 컨트롤러가 처리할지 물어보고, 처리 결과를 ViewResolver에 넘겨 최종 응답을 만들어냅니다. 개발자가 직접 구현하지 않고 Spring이 자동으로 제공합니다.
HandlerMapping — 요청 라우터
URL과 컨트롤러를 연결해주는 역할입니다. @RequestMapping, @GetMapping 등의 어노테이션을 분석해서 어떤 메서드가 해당 요청을 처리해야 하는지 찾아줍니다.
Controller — 요청 처리자
개발자가 직접 작성하는 핵심 영역입니다. 요청 파라미터를 받아 Service를 호출하고, 처리 결과를 Model에 담아 View 이름을 반환합니다.
Service — 비즈니스 로직
실제 업무 로직이 담기는 레이어입니다. 트랜잭션 처리, 데이터 가공, 여러 DAO를 조합한 복합 로직을 담당합니다.
DAO / Repository — 데이터 접근
데이터베이스와 직접 통신하는 레이어입니다. MyBatis의 Mapper 또는 JPA의 Repository가 이 역할을 합니다.
ViewResolver — 뷰 결정자
Controller가 반환한 뷰 이름(예: "list")을 실제 파일 경로(예: /WEB-INF/views/list.jsp)로 변환해줍니다.
요청 처리 흐름 요약
- 클라이언트가 HTTP 요청 전송
- DispatcherServlet이 요청 수신
- HandlerMapping에서 처리할 Controller 탐색
- Controller 메서드 실행 → Service 호출
- Service에서 DAO를 통해 DB 처리
- Controller가 Model에 데이터 담고 View 이름 반환
- ViewResolver가 실제 View 파일 결정
- View 렌더링 후 HTML 응답 반환
@Controller vs @RestController: 무엇이 다를까?
전통적인 Spring MVC 컨트롤러와 현대적인 REST API 컨트롤러의 가장 큰 차이점은 **‘응답 데이터의 처리 방식’**에 있습니다.
@Controller
@Controller는 주로 HTML 뷰를 반환하는 전통적인 웹 애플리케이션에서 사용됩니다. 메서드가 문자열을 반환하면 ViewResolver가 해당 이름의 뷰 파일을 찾아 렌더링합니다. 만약 이 컨트롤러에서 JSON 데이터를 반환하고 싶다면 메서드나 클래스에 별도로 @ResponseBody 어노테이션을 붙여야 합니다.
@Controller
public class WebController {
// HTML 뷰를 반환하는 경우
@GetMapping("/home")
public String homePage() {
return "index"; // index.html 또는 index.jsp 렌더링
}
// JSON 데이터를 직접 반환하는 경우 (@ResponseBody 필요)
@GetMapping("/api/status")
@ResponseBody
public String status() {
return "OK";
}
}
@RestController
@RestController는 @Controller와 @ResponseBody를 합친 것입니다. 모든 메서드에 자동으로 @ResponseBody가 적용되어, 객체를 반환하면 Spring이 이를 JSON 또는 XML로 자동 변환하여 HTTP 응답 본문에 직접 작성합니다. 최근의 프론트엔드-백엔드 분리 구조나 마이크로서비스 아키텍처에서 표준으로 사용됩니다.
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
// 객체 반환 시 JSON으로 자동 변환됨
return userService.findById(id);
}
}
Spring MVC 실전: 간단한 게시판 컨트롤러 작성하기
컴포넌트들의 유기적인 작동을 이해하기 위해, 실제 프로젝트에서 가장 흔하게 쓰이는 게시판 컨트롤러의 구현 방식을 살펴보겠습니다.
@Controller
@RequestMapping("/board")
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
// 게시글 목록 조회
@GetMapping("/list")
public String listBoards(Model model) {
List<Board> boards = boardService.findAll();
// Model 객체에 데이터를 담아 뷰로 전달
model.addAttribute("boardList", boards);
return "board/list"; // board/list.html 렌더링
}
// 게시글 작성 처리
@PostMapping("/write")
public String saveBoard(@ModelAttribute Board board) {
boardService.save(board);
// 저장 후 목록 페이지로 리다이렉트
return "redirect:/board/list";
}
}
위 예시에서 @GetMapping은 데이터를 조회할 때, @PostMapping은 데이터를 생성/변경할 때 사용합니다. 특히 Model 객체는 컨트롤러에서 처리한 결과 데이터를 뷰(Thymeleaf, JSP 등)로 전달하는 브릿지 역할을 수행하며, redirect: 접두사를 통해 처리 완료 후 특정 URL로 사용자를 이동시킬 수 있습니다.
자주 하는 실수 TOP 3
Spring MVC를 처음 접하는 개발자들이 자주 겪는 시행착오와 해결 방법입니다.
-
@RequestMapping vs @GetMapping/@PostMapping
모든 요청에 범용적인@RequestMapping을 사용하는 것보다, HTTP 메서드가 명시된 전용 어노테이션(@GetMapping,@PostMapping등)을 사용하는 것이 좋습니다. 이는 코드의 의도를 명확히 하고 보안상 안전한 매핑을 보장합니다. -
Model과 ModelAndView의 혼용
과거에는ModelAndView객체를 생성해 뷰와 데이터를 한꺼번에 담아 반환했지만, 최근에는 파라미터로Model을 받아 데이터를 담고 메서드 리턴값으로는String(뷰 이름)만 전달하는 패턴이 더 선호됩니다. 코드가 훨씬 간결해지기 때문입니다. -
ViewResolver 경로 설정 오류
컨트롤러에서"board/list"를 리턴했는데404 Not Found가 발생한다면,application.properties나 설정 클래스의prefix,suffix설정을 확인하세요. 실제 파일이/WEB-INF/views/board/list.jsp에 있다면 prefix는/WEB-INF/views/, suffix는.jsp여야 합니다.
자주 묻는 질문 (FAQ)
Q: DispatcherServlet은 여러 개를 만들어야 할까요?
A: 일반적으로 하나의 웹 애플리케이션당 하나의 DispatcherServlet을 두는 Front Controller 패턴을 따릅니다. 복잡한 앱에서도 계층별로 Servlet Context를 나누는 경우는 있지만, 대부분은 1개로 모든 요청을 통합 관리합니다.
Q: @Controller를 선언하면 모든 요청이 자동으로 처리되나요?
A: 아닙니다. 먼저 해당 클래스가 @ComponentScan 범위에 포함되어 Spring Bean으로 등록되어야 하며, 각 메서드에는 반드시 @RequestMapping 계열의 매핑 정보가 있어야 합니다.
Q: 컨트롤러에서 모든 비즈니스 로직을 처리해도 되나요?
A: 가능은 하지만 권장되지 않습니다. 컨트롤러는 요청의 흐름을 제어하는 역할만 수행해야 합니다. 실제 계산이나 DB 처리는 Service 계층으로 분리해야 코드의 재사용성이 높아지고 단위 테스트 작성이 쉬워집니다.
관련 글
- Spring MVC 요청 처리 흐름 가이드
- Spring 데이터베이스 연결, 집에 가는 것처럼 쉽게 이해하기
- Tailwind CSS v4 마이그레이션 완벽 가이드: config 파일 없이 CSS만으로 설정하기