Spring MVC 아키텍처 완벽 이해하기
Spring

Spring MVC 아키텍처 완벽 이해하기


🍃 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)로 변환해줍니다.

요청 처리 흐름 요약

  1. 클라이언트가 HTTP 요청 전송
  2. DispatcherServlet이 요청 수신
  3. HandlerMapping에서 처리할 Controller 탐색
  4. Controller 메서드 실행 → Service 호출
  5. Service에서 DAO를 통해 DB 처리
  6. Controller가 Model에 데이터 담고 View 이름 반환
  7. ViewResolver가 실제 View 파일 결정
  8. 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를 처음 접하는 개발자들이 자주 겪는 시행착오와 해결 방법입니다.

  1. @RequestMapping vs @GetMapping/@PostMapping
    모든 요청에 범용적인 @RequestMapping을 사용하는 것보다, HTTP 메서드가 명시된 전용 어노테이션(@GetMapping, @PostMapping 등)을 사용하는 것이 좋습니다. 이는 코드의 의도를 명확히 하고 보안상 안전한 매핑을 보장합니다.

  2. Model과 ModelAndView의 혼용
    과거에는 ModelAndView 객체를 생성해 뷰와 데이터를 한꺼번에 담아 반환했지만, 최근에는 파라미터로 Model을 받아 데이터를 담고 메서드 리턴값으로는 String(뷰 이름)만 전달하는 패턴이 더 선호됩니다. 코드가 훨씬 간결해지기 때문입니다.

  3. 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 계층으로 분리해야 코드의 재사용성이 높아지고 단위 테스트 작성이 쉬워집니다.


관련 글


댓글 남기기

궁금한 점이나 나누고 싶은 이야기가 있다면 자유롭게 남겨주세요.
여러분의 소중한 의견이 블로그를 성장시킵니다! 🌱

💡 오늘의 명언

"꽃은 자신을 누구와 비교하지 않는다. 그저 피어날 뿐이다."

✍️ 작성하기

💬0개의 댓글

댓글을 불러오는 중입니다...