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- Client → HTTP 요청
DispatcherServlet— 모든 요청을 가장 먼저 받음(Front Controller)[1]HandlerMapping— 이 URL을 처리할 Controller 메서드 검색[2]DispatcherServlet— 찾은 handler를HandlerAdapter로 위임HandlerAdapter— 파라미터 바인딩 후 Controller 메서드 호출[2:1]- Controller — Service/DAO 호출,
Model에 데이터 담음, 논리 View 이름 반환 ViewResolver—"board/detail"→/WEB-INF/views/board/detail.jsp로 변환[2:2]- View —
Model데이터로 렌더링, HTML 생성 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 반환
- Model: Controller → View 데이터 전달 통로. Servlet의
request.setAttribute("name", value)와 같은 역할이다.[9] - String 반환(논리 View 이름):
return "board/list"→ViewResolver가prefix + view name + suffix로 실제 View 경로를 생성한다. View 파일 위치를 바꿔도 Controller 코드는 바뀌지 않는다.spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp @ResponseBody/@RestController: JSON 중심 REST API. Jackson이 객체를 JSON으로 직렬화해 응답 본문으로 전송한다.String반환 시 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]
실패 모드
DispatcherServlet이 직접 비즈니스 로직을 다루도록 설계하는 경우(Front Controller 역할 위배).@RequestParam과@PathVariable을 리소스 식별 기준 없이 섞어 쓰는 경우.- Controller가 Service 로직을 직접 구현하거나 DB에 접근하는 경우(Controller는 얇게, 비즈니스는 Service 위임).
- View 이름과 View 파일 경로를 혼동해
ViewResolverprefix/suffix를 이중 설정하는 경우. - POST 처리 후
redirect:없이 forward만 해 새로고침 중복 제출을 유발하는 경우. @Controller와@RestController의도를 섞어 쓰는 경우 — View 반환인지 JSON 응답인지 명확하지 않음.
관련
- Spring Transaction — Controller에서 Service를 호출할 때 트랜잭션 경계가 AOP 프록시로 열린다.
- Spring AOP —
@TransactionalService 진입 시 AOP 프록시가 트랜잭션 경계를 여는 메커니즘.
출처
테스트 질문
DispatcherServlet의 핵심 역할은 무엇이며, 왜 비즈니스 로직을 직접 처리하지 않는가?HandlerMapping과HandlerAdapter의 역할 차이는 무엇인가?@RequestParam과@PathVariable중 어느 것을 선택하는 기준은 무엇인가?return "board/list"와return "redirect:/board/list"의 차이는 무엇인가?- POST 처리 후
redirect:없이 forward하면 어떤 문제가 생기는가? - Spring Boot에서
DispatcherServlet이 자동 등록되는 조건은 무엇인가?
Spring MVC.md — "DispatcherServlet은 Spring MVC의 Front Controller다. 클라이언트의 HTTP 요청을 가장 먼저 받고, 각 단계를 담당하는 컴포넌트에게 일을 위임한다.", "DispatcherServlet 자체가 직접 비즈니스 로직을 처리하지 않는다는 점이다. 흐름만 잡고 실제 작업은 각 컴포넌트에게 위임한다." ↩︎ ↩︎
Spring MVC.md — 핵심 출력 컴포넌트 표: "HandlerMapping | 이 URL을 처리할 Controller 메서드가 누구인지 찾는다.", "HandlerAdapter | 찾은 핸들러 메서드를 실제로 호출한다. 이때 파라미터 바인딩도 같이 처리된다.", "ViewResolver | Controller가 반환한 논리 View 이름을 실제 View 파일 또는 View 객체로 변환한다." ↩︎ ↩︎ ↩︎
Spring MVC.md — "하나의 Servlet 안에서 여러 액션이 if/else 또는 switch로 분기되어 Controller가 금방 비대해진다.", "매번 request.getParameter("name")로 값을 꺼내고 직접 Integer.parseInt() 같은 형 변환을 해야 한다.", "Servlet API를 직접 다루는 코드를 얇게 감추고, 개발자는 Controller 메서드에 집중한다." ↩︎
Spring MVC.md — "spring-boot-starter-web 의존성을 추가하면 자동 설정이 다음을 수행한다. DispatcherServlet Bean 자동 등록... HandlerMapping, HandlerAdapter, ViewResolver 기본 구성 자동 등록" ↩︎ ↩︎
Spring MVC.md — "내부적으로는 @Component의 한 종류라서 Component Scan 대상이 되고, Spring Bean으로 등록된다.", "HandlerMapping이 이 클래스의 메서드를 매핑 대상으로 검토한다." ↩︎
Spring MVC.md — "REST 스타일 설계에서는 같은 URL이라도 HTTP 메서드로 서로 다른 작업을 구분한다." ↩︎
Spring MVC.md — "요청 파라미터 이름과 객체의 setter property 이름을 맞춘다.", "기본 생성자로 객체를 생성한 뒤 setter로 값을 설정한다.", "타입 변환은 자동으로 수행된다." ↩︎
Spring MVC.md — 자동 형 변환 표(
"42"→int,"true"→boolean,"2026-05-05"→LocalDate), "복잡한 타입(LocalDateTime 등)은 추가 설정이 필요할 수 있지만, 기본 타입은 대부분 자동으로 처리된다." ↩︎Spring MVC.md — "Model은 Controller에서 View로 데이터를 전달하는 객체다. Servlet의 request.setAttribute("name", value)와 비슷한 역할을 한다." ↩︎
Spring MVC.md — "addAttribute()는 URL Query String으로 전달한다.", "addFlashAttribute()는 Redirect 이후 한 번만 쓰는 임시 데이터에 적합하다.", "게시글 등록 같은 데이터 변경 작업 후에는 새로고침 중복 제출을 막기 위해 PRG를 적용한다." ↩︎
Spring MVC.md — "WebMvcConfigurer는 인터페이스다.", "필요한 메서드만 골라 구현하면 된다.", "자동 설정을 완전히 잃지 않으면서 커스터마이징할 수 있다." ↩︎