Spring MVC Controller

Spring MVC는 DispatcherServlet을 Front Controller로 두는 Servlet 기반 웹 프레임워크다. DispatcherServlet이 모든 HTTP 요청을 먼저 받고, 적절한 Controller 메서드로 분배한 뒤 파라미터 바인딩과 View 변환을 자동화한다. Controller에서 Service를 호출할 때 트랜잭션 경계가 Spring Transaction의 AOP 프록시로 열린다.

요청 처리 흐름

flowchart LR
    Client[Client] --> DS[DispatcherServlet]
    DS --> HM[HandlerMapping
URL → Controller] HM --> DS DS --> HA[HandlerAdapter
파라미터 바인딩 + 호출] HA --> Controller[Controller Method] Controller --> HA HA --> DS DS --> VR[ViewResolver
논리 이름 → View] VR --> View[View render] View --> Client
  1. Client → HTTP 요청
  2. DispatcherServlet — 모든 요청을 가장 먼저 받음(Front Controller)[1]
  3. HandlerMapping — 이 URL을 처리할 Controller 메서드 검색[2]
  4. DispatcherServlet — 찾은 handler를 HandlerAdapter로 위임
  5. HandlerAdapter — 파라미터 바인딩 후 Controller 메서드 호출[2:1]
  6. Controller — Service/DAO 호출, Model에 데이터 담음, 논리 View 이름 반환
  7. ViewResolver — "board/detail" → /WEB-INF/views/board/detail.jsp로 변환[2:2]
  8. View — Model 데이터로 렌더링, HTML 생성
  9. DispatcherServlet → HTTP 응답 → Client

DispatcherServlet 자체는 비즈니스 로직을 처리하지 않는다. 흐름만 잡고 각 컴포넌트에게 위임한다.[1:1]

기존 Servlet MVC는 하나의 Servlet 안에서 action 파라미터로 분기하고, getParameter/parseInt를 직접 호출하고, setAttribute/forward를 반복하며, Service를 new로 직접 생성해 강결합됐다. Spring MVC는 이 한계를 얇은 추상화 계층과 DI로 해결한다.[3] Spring Boot는 spring-boot-starter-web 의존성만으로 DispatcherServlet, HandlerMapping, HandlerAdapter, ViewResolver 기본 구성을 자동 등록한다.[4]

Controller 구조와 어노테이션

@Controller
@RequestMapping("/board")
@RequiredArgsConstructor
public class BoardController {
    private final BoardService boardService;  // final + 생성자 주입

    @GetMapping("/list")
    public String list(Model model) {
        model.addAttribute("boards", boardService.getBoards());
        return "board/list";  // 논리 View 이름
    }
}

@Controller는 @Component의 변형이다. Component Scan 대상이 되어 Bean으로 등록되며, HandlerMapping이 이 클래스의 메서드를 매핑 대상으로 검토한다.[5]

Annotation 계층 의미
@Controller Presentation 웹 요청 처리
@Service Business 비즈니스 로직
@Repository Persistence DB 접근, 예외 변환

URL Mapping 단축 어노테이션으로 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping이 있다. REST 스타일에서는 같은 URL이라도 HTTP 메서드로 다른 작업을 구분한다.[6]

파라미터 바인딩

Spring MVC의 강점은 HTTP 요청 값을 Controller 메서드 파라미터로 자동 바인딩하는 것이다.

@RequestParam vs @PathVariable

구분 @RequestParam @PathVariable
위치 Query String, form data URL 경로 일부
예시 URL /board?no=42 /board/42
의미 부가 조건, 검색어, 페이지 번호 리소스 식별자
선택 기준 부가 정보인가? 리소스를 특정하는가?
@GetMapping("/{no}")
public String detail(@PathVariable int no, Model model) {
    model.addAttribute("board", boardService.getBoard(no));
    return "board/detail";
}

파라미터가 많으면 DTO/Command 객체로 묶는다. 요청 파라미터 이름과 객체 setter property 이름을 맞추고, 기본 생성자로 객체를 생성한 뒤 setter로 채운다. 타입 변환은 자동 수행된다.[7]

@Data
public class SearchCondition {
    private int page = 1;
    private int size = 10;
    private String keyword;
}

@GetMapping("/list")
public String list(SearchCondition condition, Model model) {
    model.addAttribute("boards", boardService.search(condition));
    return "board/list";
}

기본 타입은 대부분 자동 형 변환된다("42"→int, "true"→boolean, "2026-05-05"→LocalDate 등). 복잡한 타입(LocalDateTime 등)은 추가 설정이 필요할 수 있다.[8]

Model과 View 반환

forward vs redirect

return "forward:/board/list";   // 서버 내부 전달, URL 변경 안 됨, 요청 1회
return "redirect:/board/list";  // 새로운 요청, URL 변경됨, 요청 2회
구분 forward redirect
요청 횟수 1회 2회
URL 변경 변경 안 됨 변경됨
데이터 유지 request 데이터 유지 request 데이터 사라짐
주 용도 일반 화면 표시 POST 처리 후 페이지 이동

POST-Redirect-GET(PRG)은 게시글 작성 같은 POST 처리 후 반드시 redirect를 써서 새로고침 중복 제출을 막는 패턴이다.

@PostMapping("/write")
public String write(BoardForm form, RedirectAttributes ra) {
    Board created = boardService.create(form);
    ra.addAttribute("no", created.getNo());           // URL Query String
    ra.addFlashAttribute("message", "등록되었습니다."); // 한 번만 노출되는 임시 데이터
    return "redirect:/board/detail";                   // PRG
}

addAttribute()는 URL Query String으로 전달하고, addFlashAttribute()는 redirect 이후 한 번만 노출되는 임시 데이터다. 데이터 변경 POST 후 forward하면 새로고침 시 중복 제출 문제가 생긴다.[10]

Spring Boot 자동 설정

Spring Boot는 spring-boot-starter-web 의존성으로 DispatcherServlet Bean 자동 등록·/ URL 매핑, HandlerMapping/HandlerAdapter/ViewResolver 기본 구성 자동 등록, 정적 리소스 경로(src/main/resources/static/) 자동 서비스를 수행한다.[4:1] 추가 설정이 필요하면 WebMvcConfigurer 인터페이스를 구현해 필요한 메서드만 선택 오버라이드한다. 자동 설정을 잃지 않고 커스터마이징할 수 있다.[11]

실패 모드

관련

출처

테스트 질문


  1. Spring MVC.md — "DispatcherServlet은 Spring MVC의 Front Controller다. 클라이언트의 HTTP 요청을 가장 먼저 받고, 각 단계를 담당하는 컴포넌트에게 일을 위임한다.", "DispatcherServlet 자체가 직접 비즈니스 로직을 처리하지 않는다는 점이다. 흐름만 잡고 실제 작업은 각 컴포넌트에게 위임한다." ↩︎ ↩︎

  2. Spring MVC.md — 핵심 출력 컴포넌트 표: "HandlerMapping | 이 URL을 처리할 Controller 메서드가 누구인지 찾는다.", "HandlerAdapter | 찾은 핸들러 메서드를 실제로 호출한다. 이때 파라미터 바인딩도 같이 처리된다.", "ViewResolver | Controller가 반환한 논리 View 이름을 실제 View 파일 또는 View 객체로 변환한다." ↩︎ ↩︎ ↩︎

  3. Spring MVC.md — "하나의 Servlet 안에서 여러 액션이 if/else 또는 switch로 분기되어 Controller가 금방 비대해진다.", "매번 request.getParameter("name")로 값을 꺼내고 직접 Integer.parseInt() 같은 형 변환을 해야 한다.", "Servlet API를 직접 다루는 코드를 얇게 감추고, 개발자는 Controller 메서드에 집중한다." ↩︎

  4. Spring MVC.md — "spring-boot-starter-web 의존성을 추가하면 자동 설정이 다음을 수행한다. DispatcherServlet Bean 자동 등록... HandlerMapping, HandlerAdapter, ViewResolver 기본 구성 자동 등록" ↩︎ ↩︎

  5. Spring MVC.md — "내부적으로는 @Component의 한 종류라서 Component Scan 대상이 되고, Spring Bean으로 등록된다.", "HandlerMapping이 이 클래스의 메서드를 매핑 대상으로 검토한다." ↩︎

  6. Spring MVC.md — "REST 스타일 설계에서는 같은 URL이라도 HTTP 메서드로 서로 다른 작업을 구분한다." ↩︎

  7. Spring MVC.md — "요청 파라미터 이름과 객체의 setter property 이름을 맞춘다.", "기본 생성자로 객체를 생성한 뒤 setter로 값을 설정한다.", "타입 변환은 자동으로 수행된다." ↩︎

  8. Spring MVC.md — 자동 형 변환 표("42"→int, "true"→boolean, "2026-05-05"→LocalDate), "복잡한 타입(LocalDateTime 등)은 추가 설정이 필요할 수 있지만, 기본 타입은 대부분 자동으로 처리된다." ↩︎

  9. Spring MVC.md — "Model은 Controller에서 View로 데이터를 전달하는 객체다. Servlet의 request.setAttribute("name", value)와 비슷한 역할을 한다." ↩︎

  10. Spring MVC.md — "addAttribute()는 URL Query String으로 전달한다.", "addFlashAttribute()는 Redirect 이후 한 번만 쓰는 임시 데이터에 적합하다.", "게시글 등록 같은 데이터 변경 작업 후에는 새로고침 중복 제출을 막기 위해 PRG를 적용한다." ↩︎

  11. Spring MVC.md — "WebMvcConfigurer는 인터페이스다.", "필요한 메서드만 골라 구현하면 된다.", "자동 설정을 완전히 잃지 않으면서 커스터마이징할 수 있다." ↩︎