웹 애플리케이션을 개발하다 보면 많은 요청과 응답을 처리해야 합니다. 이런 복잡한 과정을 효율적으로 관리하기 위해 스프링 프레임워크는 MVC 패턴을 기반으로 한 모듈을 제공합니다. 스프링 MVC의 기본 구조와 동작 원리, 그리고 실제 개발에서 어떻게 활용하는지 알아보겠습니다.
스프링 MVC란 무엇인가?
스프링 MVC는 Model-View-Controller(Model2) 아키텍처를 기반으로 하는 스프링 프레임워크의 하위 모듈입니다. 이 구조는 애플리케이션의 관심사를 효과적으로 분리하여 유지보수성과 확장성을 높여줍니다.
주요 구성 요소
스프링 MVC는 크게 세 가지 영역으로 구성됩니다.
- DispatcherServlet: 스프링 MVC의 핵심 컴포넌트로, 모든 클라이언트 요청을 가장 먼저 받아 적절한 컨트롤러로 전달하는 프론트 컨트롤러 역할을 합니다.
- 스프링 MVC 인프라 컴포넌트:
- HandlerMapping: 요청 URL을 기반으로 적절한 컨트롤러를 찾아주는 역할
- HandlerAdapter: 컨트롤러의 메서드를 호출하여 요청을 처리
- ViewResolver: 컨트롤러가 반환한 뷰 이름을 실제 뷰 객체로 변환
- 개발자가 작성하는 영역:
- 모델(Model): 데이터와 비즈니스 로직을 담당
- 뷰(View): 사용자에게 보여지는 화면 담당
- 핸들러(Handler/Controller): 클라이언트의 요청을 처리하는 컴포넌트
스프링 MVC의 동작 흐름
스프링 MVC가 요청을 처리하는 과정은 다음과 같습니다.
- 클라이언트가 DispatcherServlet으로 요청을 보냅니다.
- DispatcherServlet은 HandlerMapping에게 요청을 처리할 적합한 컨트롤러가 무엇인지 문의합니다.
- HandlerMapping은 URL 패턴을 분석하여 해당 요청을 처리할 컨트롤러를 결정하고 반환합니다.
- DispatcherServlet은 선택된 컨트롤러를 실행하기 위해 HandlerAdapter에게 요청을 전달합니다.
- HandlerAdapter는 컨트롤러(@Controller)에게 요청 처리를 위임합니다.
- 컨트롤러는 비즈니스 로직 수행을 위해 서비스 계층을 호출합니다.
- 컨트롤러는 처리 결과를 모델 객체에 저장합니다.
- 컨트롤러는 뷰 이름을 반환하고, HandlerAdapter는 이를 DispatcherServlet에게 전달합니다.
- DispatcherServlet은 ViewResolver에게 뷰 이름을 전달하여 실제 뷰 객체를 요청합니다.
- ViewResolver는 뷰 이름을 분석하여 적합한 뷰 객체를 찾아 반환합니다.
- DispatcherServlet은 뷰 객체에게 렌더링을 요청합니다.
- 뷰는 모델 데이터를 사용하여 응답을 생성하고 클라이언트에게 반환합니다.
이 흐름은 마치 오케스트라의 지휘자와 같이 DispatcherServlet이 요청 처리의 전체 과정을 조율하는 구조입니다.
컨트롤러 작성하기
스프링 MVC에서 컨트롤러는 @Controller 어노테이션을 사용하여 정의합니다. 컨트롤러는 클라이언트의 요청을 받아들이고 적절한 응답을 제공하는 역할을 합니다.
@RequestMapping 어노테이션
@RequestMapping은 요청 URL과 컨트롤러 메서드를 연결하는 어노테이션입니다.
- 클래스 레벨 매핑: 컨트롤러 클래스의 모든 메서드에 공통으로 적용되는 기본 경로
- 메서드 레벨 매핑: 특정 메서드에만 적용되는 경로
- HTTP 메서드 지정: GET, POST 등의 HTTP 메서드를 지정할 수 있으며, @GetMapping, @PostMapping과 같은 축약형 어노테이션도 제공
@Controller
@RequestMapping("/simple")
@RequiredArgsConstructor
public class SimpleController {
private final SimpleService sService;
@GetMapping("/forward")
public String forward(Model model) {
String value = sService.helloMVC();
model.addAttribute("data", value);
return "mvc/simple";
}
@GetMapping("/redirect")
public String redirect() {
return "redirect:/simple/forward";
}
@GetMapping("/json")
@ResponseBody
public Map<String, Object> json() {
return Map.of("name", "hong", "age", 30);
}
}
요청 처리 메서드의 동작
컨트롤러의 요청 처리 메서드는 다음과 같은 단계로 동작합니다.
- 요청 분석: 클라이언트의 요청 데이터(파라미터, 헤더, 쿠키 등)를 분석합니다.
- 비즈니스 로직 수행: 서비스 계층을 호출하여 필요한 비즈니스 로직을 처리합니다.
- 결과 저장: 처리 결과를 Model 객체에 저장합니다.
- 뷰 이름 반환: 결과를 표시할 뷰의 논리적 이름을 반환합니다.
뷰 리졸버 (ViewResolver)
뷰 리졸버는 컨트롤러가 반환한 논리적인 뷰 이름을 실제 뷰 파일의 위치로 변환하는 역할을 합니다. 스프링 부트에서는 application.properties 파일에서 다음과 같이 설정할 수 있습니다.
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
이 설정은 컨트롤러가 "home"이라는 뷰 이름을 반환하면, "/WEB-INF/views/home.jsp" 파일을 찾아 렌더링하도록 합니다.
컨트롤러 단위 테스트
스프링 MVC 컨트롤러는 MockMvc를 사용하여 효과적으로 테스트할 수 있습니다. MockMvc는 실제 서버 환경 없이도 스프링 MVC의 동작을 시뮬레이션할 수 있습니다.
@WebMvcTest(SimpleController.class)
public class SimpleControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private SimpleService simpleService;
@Test
public void testForward() throws Exception {
// 서비스 동작 모의 설정
when(simpleService.helloMVC()).thenReturn("Hello, MVC!");
// 요청 수행 및 검증
mockMvc.perform(get("/simple/forward"))
.andExpect(status().isOk())
.andExpect(view().name("mvc/simple"))
.andExpect(model().attributeExists("data"))
.andExpect(model().attribute("data", "Hello, MVC!"));
}
}
테스트 과정은 일반적으로 다음 단계로 진행됩니다.
- 요청 만들기 (MockMvcRequestBuilders 사용)
- 실행 (perform 메서드)
- 검증 (andExpect 메서드로 상태, 뷰, 모델 등 확인)
- 결과 확인 (필요한 경우 andDo 메서드로 추가 작업 수행)
핸들러 메서드의 파라미터
스프링 MVC의 컨트롤러 메서드는 다양한 타입의 파라미터를 지원합니다. 파라미터의 순서에 관계없이 필요한 객체를 주입받을 수 있습니다.
@RequestParam
@RequestParam은 HTTP 요청의 파라미터를 메서드 파라미터로 바인딩합니다.
@GetMapping("/calc")
@ResponseBody
public Map<String, Integer> calc(
@RequestParam Integer num1,
@RequestParam Integer num2,
@RequestParam String oper
) {
int result = switch(oper) {
case "+" -> num1 + num2;
default -> num1 * num2;
};
return Map.of("result", result);
}
이 메서드는 /calc?num1=10&num2=20&oper=+와 같은 요청을 처리합니다.
@ModelAttribute
@ModelAttribute는 요청 파라미터를 객체의 프로퍼티에 바인딩합니다. 객체의 프로퍼티 이름과 요청 파라미터의 이름이 일치하면 자동으로 값이 설정됩니다.
@GetMapping("/regist")
@ResponseBody
public Map<String, Object> regist(@ModelAttribute Member member, Model model) {
log.debug("member: {}", member);
model.addAttribute("member", member);
return Map.of("result", member);
}
이 메서드는 /regist?name=hong&email=hong@example.com&password=1234와 같은 요청을 처리하여 Member 객체의 각 프로퍼티에 값을 설정합니다.
@CookieValue
@CookieValue는 HTTP 요청의 쿠키 값을 메서드 파라미터로 바인딩합니다.
@GetMapping("/welcome")
public String welcome(@CookieValue(value = "userId", required = false) String userId, Model model) {
if (userId != null) {
model.addAttribute("message", "Welcome back, " + userId);
} else {
model.addAttribute("message", "Welcome new user");
}
return "welcome";
}
리다이렉션과 플래시 스코프
스프링 MVC에서는 리다이렉션을 통해 다른 URL로 요청을 전달할 수 있습니다. 리다이렉션 시 데이터를 전달하는 방법에는 여러 가지가 있습니다.
리다이렉션 방법
@GetMapping("/redirect")
public String redirect() {
return "redirect:/simple/forward";
}
리다이렉션 시 데이터 전달
리다이렉션 시 데이터를 전달하는 방법은 크게 두 가지가 있습니다.
- URL 쿼리 파라미터: URL 뒤에 쿼리 스트링을 추가하여 데이터 전달
- 플래시 스코프: 리다이렉션이 완료될 때까지만 유지되는 일회성 세션
RedirectAttributes
RedirectAttributes는 리다이렉션 시 데이터 전달을 위한 특별한 객체입니다.
@PostMapping("/process")
public String processForm(RedirectAttributes redirectAttributes) {
// 처리 로직
redirectAttributes.addAttribute("status", "success"); // URL 쿼리 파라미터로 추가
redirectAttributes.addFlashAttribute("message", "처리가 완료되었습니다."); // 플래시 속성으로 추가
return "redirect:/result";
}
플래시 스코프는 요청 스코프보다는 오래 지속되지만 세션 스코프보다는 짧게 유지되며, 리다이렉션 완료 후 자동으로 제거됩니다.
마무리
스프링 MVC는 웹 애플리케이션 개발에 필요한 강력한 구조와 기능을 제공합니다. DispatcherServlet을 중심으로 한 구조는 요청 처리의 흐름을 체계적으로 관리하며, 다양한 어노테이션과 컴포넌트를 통해 개발자가 핵심 비즈니스 로직에 집중할 수 있게 도와줍니다.
컨트롤러 개발부터 뷰 처리, 파라미터 바인딩, 리다이렉션 처리까지, 스프링 MVC는 웹 개발의 다양한 측면을 포괄적으로 지원합니다. 이러한 특성들로 인해 스프링 MVC는 현대 자바 웹 애플리케이션 개발의 표준으로 자리 잡았습니다.
'BackEnd > Spring Framework' 카테고리의 다른 글
| MyBatis (3) | 2025.05.03 |
|---|---|
| Interceptor | ErrorPage | FileUpload (0) | 2025.04.30 |
| Spring AOP (0) | 2025.04.26 |
| SpringBoot (3) | 2025.04.24 |
| SLF4J와 JUnit (2) | 2025.04.24 |