2025. 12. 20. 23:43ㆍSpring RoadMap/Spring MVC
백엔드 웹 개발 핵심 기술, 스프링 MVC 구조 이해
이전 섹션에서 우리는 FrontControllerServletV5를 직접 구현하며 프론트 컨트롤러 패턴과 어댑터 패턴이 웹 프레임워크에 어떻게 적용되는지 학습하였다. 이제 실제 스프링 MVC의 구조를 살펴볼 차례다.
결론부터 말하자면, 우리가 직접 만든 프레임워크와 스프링 MVC의 구조는 놀라울 정도로 유사하다.


직접 만든 프레임워크 => 스프링 MVC
- FrontController => DispatcherServlet
- handlerMappingMap => HandlerMapping
- MyHandlerAdapter => HandlerAdapter
- ModelView => ModelAndView
- viewResolver => ViewResolver
- MyView => View
1. DispatcherServlet: 스프링의 프론트 컨트롤러
스프링 MVC의 핵심은 바로 DispatcherServlet이다. 이 서블릿이 모든 HTTP 요청을 가장 먼저 받아 적절한 핸들러에게 분배하는 프론트 컨트롤러(Front Controller) 역할을 수행하는 것이다.
1-1. 상속 계층 구조
DispatcherServlet 또한 서블릿이다. 그 상속 관계를 살펴보면 다음과 같다.

- DispatcherServlet은 부모 클래스인 HttpServlet을 상속받아 동작한다.
- 스프링 부트는 내장 톰캣을 실행할 때 DispatcherServlet을 서블릿으로 자동 등록하며, 모든 경로(urlPatterns="/")에 대해 매핑한다. 따라서 애플리케이션으로 들어오는 모든 요청은 이 서블릿을 거치게 되는 것이다.
1-2. 요청 흐름
서블릿이 호출되면 컨테이너는 service() 메서드를 실행한다.
스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 오버라이드해 두었으며, 이를 시작으로 여러 메서드가 호출되다가 최종적으로 doDispatch() 메서드가 실행된다.
이 doDispatch()가 바로 우리가 구현했던 v5의 service() 로직과 동일한 역할을 수행하는 핵심 메서드다.
2. DispatcherServlet 소스 코드 분석
스프링 프레임워크의 실제 코드는 매우 복잡하게 구성되어 있으나, 예외 처리나 인터셉터 부분 등을 제외하고 핵심 로직만 간추리면 다음과 같다.
DispacherServlet.doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 뷰 렌더링 호출
render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
위 코드를 우리가 만든 FrontControllerServletV5와 비교해보면 구조가 동일함을 알 수 있다.
- getHandler()는 우리의 handlerMappingMap.get()에 해당한다.
- getHandlerAdapter()는 우리의 handlerAdapters 리스트를 순회하며 찾는 로직에 해당한다.
- ha.handle()은 어댑터의 handle() 메서드를 호출하여 ModelAndView를 반환받는 과정이다.
3. 스프링 MVC의 동작 순서
스프링 MVC의 전체적인 동작 흐름을 정리하면 다음과 같다.

- 핸들러 조회: 핸들러 매핑(HandlerMapping)을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
- 핸들러 어댑터 조회: 조회한 핸들러를 실행할 수 있는 핸들러 어댑터(HandlerAdapter)를 조회한다.
- 핸들러 어댑터 실행: DispatcherServlet이 핸들러 어댑터를 실행한다.
- 핸들러 실행: 핸들러 어댑터가 실제 핸들러(컨트롤러)를 호출하여 비즈니스 로직을 수행한다.
- ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView 객체로 변환하여 반환한다. (데이터와 논리 뷰 이름이 포함된다.)
- viewResolver 호출: 뷰 리졸버(ViewResolver)를 찾고 실행한다.
- JSP의 경우 InternalResourceViewResolver가 자동으로 등록되어 사용된다.
- View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 경로로 변환하고, 렌더링 역할을 담당하는 뷰 객체(예: InternalResourceView)를 반환한다.
- 뷰 렌더링: 뷰 객체의 render() 메서드를 호출하여 HTML을 생성하고 응답을 전송한다.
4. 주요 인터페이스 목록
스프링 MVC의 가장 큰 강점은 DispatcherServlet 코드의 변경 없이, 원하는 기능을 확장하거나 변경할 수 있다는 점이다. 이는 주요 구성 요소들이 모두 인터페이스로 설계되어 있기 때문이다.
| 역할 | 우리가 만든 v5 구현체 | 스프링 MVC 인터페이스 | 설명 |
| 핸들러 매핑 | Map<String, Object> | HandlerMapping | 요청 URL을 분석하여 핸들러를 찾아주는 역할 |
| 핸들러 어댑터 | MyHandlerAdapter | HandlerAdapter | 핸들러를 실행하고 결과를 ModelAndView로 변환 |
| 뷰 리졸버 | viewResolver() 메서드 | ViewResolver | 뷰의 논리 이름을 물리 뷰 객체로 변환 |
| 뷰 | MyView | View | 실제 화면을 렌더링하는 역할 |
5. 핸들러 매핑과 핸들러 어댑터
- 과거 버전 스프링 Controller 인터페이스 -
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
- 과거 버전 스프링 Controller -
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return null;
}
}
스프링은 어떻게 과거의 컨트롤러(Controller 인터페이스)와 현대의 컨트롤러(@Controller)를 모두 지원할 수 있는 것일까?
이는 스프링 부트가 애플리케이션 로딩 시점에 다양한 핸들러 매핑과 핸들러 어댑터를 자동으로 등록하기 때문이다.
주요 구현체들의 우선순위는 다음과 같다.
HandlerMapping (핸들러 조회)
- RequestMappingHandlerMapping: 애노테이션 기반의 컨트롤러인 @RequestMapping을 처리한다. 실무에서 99.9% 사용되는 방식이다.
- BeanNameUrlHandlerMapping: 스프링 빈의 이름으로 핸들러를 찾는다. (과거 방식)
HandlerAdapter (핸들러 실행)
- RequestMappingHandlerAdapter: 애노테이션 기반의 컨트롤러를 실행한다.
- HttpRequestHandlerAdapter: HttpRequestHandler 인터페이스를 처리한다.
- SimpleControllerHandlerAdapter: 과거의 Controller 인터페이스를 처리한다.
만약 핸들러 매핑도, 핸들러 어댑터도 모두 순서대로 찾았지만 없다면 다음 순서로 넘어간다.
- 핸들러 매핑으로 핸들러 조회
- HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.
- 핸들러 어댑터 조회
- HandlerAdapter 의 supports() 를 순서대로 호출한다.
- SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
- 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
- SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.
HttpRequestHandler
서블릿과 가장 유사한 형태의 핸들러
HttpRequestHandler
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
MyHttpRequestHandler
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MyHttpRequestHandler.handleRequest");
}
}
- 핸들러 매핑으로 핸들러 조회
- HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 MyHttpRequestHandler 를 반환한다.
- 핸들러 어댑터 조회
- HandlerAdapter 의 supports() 를 순서대로 호출한다.
- HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
- 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
- HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.
정리하자면,
MyHttpRequestHandler 핸들러매핑, 어댑터
MyHttpRequestHandler 를 실행하면서 사용된 객체는 다음과 같다.
HandlerMapping = BeanNameUrlHandlerMapping HandlerAdapter = HttpRequestHandlerAdapter
@RequestMapping
조금 뒤에서 설명하겠지만, 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping , RequestMappingHandlerAdapter 이다. @RequestMapping 의 앞글자를 따서 만든 이름인데, 이것이 바로 지금 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터이다. 실무에서는 99.9% 이 방식의 컨트롤러를 사용한다.
따라서 개발자가 @Controller를 사용하든, 레거시 인터페이스를 사용하든, DispatcherServlet은 적절한 매핑과 어댑터를 찾아 실행하게 되는 것이다.
6. 뷰 리졸버 (ViewResolver)
마지막으로 뷰 리졸버의 동작 방식을 살펴보자. 핸들러가 new-form이라는 논리 뷰 이름을 반환했을 때, 스프링은 어떻게 실제 JSP 파일을 찾는 것일까?
application.properties 설정
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
스프링 부트는 위 설정을 기반으로 InternalResourceViewResolver라는 빈을 자동으로 등록한다.
동작 과정
- 핸들러 어댑터가 new-form이라는 논리 뷰 이름을 반환한다.
- InternalResourceViewResolver가 호출된다.
- 이 리졸버는 설정된 prefix와 suffix를 결합하여 /WEB-INF/views/new-form.jsp라는 물리 경로를 구성한다.
- JSP를 처리할 수 있는 InternalResourceView 객체를 생성하여 반환한다.
- 이 객체의 render()가 호출되면 RequestDispatcher.forward()가 실행되면서 JSP가 렌더링되는 것이다.
7. 스프링 MVC
스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작해서, 매우 유연하고 실용적이다. 과거에는 자바 언어에 애노테이션이 없기도 했고, 스프링도 처음부터 이런 유연한 컨트롤러를 제공한 것은 아니다.
1) RequestMappingHandlerMapping & Adapter
스프링은 매우 다양한 컨트롤러 방식을 지원하지만, 가장 우선순위가 높고 주로 사용되는 핸들러 매핑과 어댑터는 다음 두 가지다.
- HandlerMapping: RequestMappingHandlerMapping
- HandlerAdapter: RequestMappingHandlerAdapter
이름 그대로 @RequestMapping 애노테이션이 붙은 컨트롤러를 지원하는 구현체들이다. 이 둘 덕분에 우리는 복잡한 설정 없이 애노테이션 하나만으로 웹 요청을 처리할 수 있는 것이다.
2) 애노테이션 컨트롤러 시작 (v1)
가장 기본적인 형태의 애노테이션 컨트롤러를 만들어보자.
회원 등록 폼 컨트롤러
package org.example.springmvcpractice.servlet.web.springmvc.v1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
코드 분석
- @Controller:
- 내부에 @Component를 가지고 있어 컴포넌트 스캔의 대상이 되어 스프링 빈으로 자동 등록된다.
- 스프링 MVC가 이 클래스를 컨트롤러로 인식하게 한다. (RequestMappingHandlerMapping이 이 애노테이션을 보고 핸들러를 찾는다.)
- @RequestMapping:
- 요청 정보를 매핑한다. 해당 URL이 호출되면 이 메서드가 실행된다.
- 메서드 이름은 임의로 지어도 상관없다.
- ModelAndView:
- 모델과 뷰 정보를 담아서 반환한다. 우리가 만들었던 ModelView와 유사하다.
참고: 스프링 빈으로 등록할 때 반드시 @Controller일 필요는 없다. @Component와 @RequestMapping을 같이 클래스 레벨에 붙여도 되고, 직접 @Bean으로 등록해도 동작한다. 하지만 @Controller를 쓰는 것이 관례이고 편하다.
참고
RequestMappingHandlerMapping 은 스프링 빈 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다. 따라서 다음 코드도 동일하게 동작한다.
@Component //컴포넌트 스캔을 통해 스프링 빈으로 등록
@RequestMapping
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
컴포넌트 스캔 없이 다음과 같이 스프링 빈으로 직접 등록해도 동작한다.
@RequestMapping
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
ServletApplication
//스프링 빈 직접 등록
@Bean
TestController testController() {
return new TestController();
}
회원 저장 및 목록 컨트롤러
저장과 목록 조회 기능도 동일한 방식으로 만들 수 있다.
package org.example.springmvcpractice.servlet.web.springmvc.v1;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.springmvcpractice.servlet.domain.member.Member;
import org.example.springmvcpractice.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SpringMemberSaveControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/springmvc/v1/members/save")
public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member); // ModelAndView에서 Model 데이터 추가할 때 이용
return mv;
}
}
SpringMemberListControllerV1
package org.example.springmvcpractice.servlet.web.springmvc.v1;
import org.example.springmvcpractice.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.example.springmvcpractice.servlet.domain.member.Member;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
public class SpringMemberListControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/springmvc/v1/members")
public ModelAndView process() {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
3) 컨트롤러 통합 (v2)
v1의 단점은 각 기능마다 클래스를 따로 만들어야 한다는 점이다. @RequestMapping은 메서드 단위로 적용되기 때문에, 하나의 클래스 안에 연관된 기능들을 모을 수 있다.
통합 컨트롤러 예시
package org.example.springmvcpractice.servlet.web.springmvc.v2;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.springmvcpractice.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.example.springmvcpractice.servlet.domain.member.Member;
import java.util.List;
/**
* 클래스 단위 -> 메서드 단위
* @RequestMapping 클래스 레벨과 메서드 레벨 조합
*/
@Controller
@RequestMapping("/springmvc/v2/members") // 공통 경로
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/new-form") // -> /springmvc/v2/members/new-form
public ModelAndView newForm() {
return new ModelAndView("new-form");
}
@RequestMapping("/save") // -> /springmvc/v2/members/save
public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelAndView mav = new ModelAndView("save-result");
mav.addObject("member", member);
return mav;
}
@RequestMapping // -> /springmvc/v2/members
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mav = new ModelAndView("members");
mav.addObject("members", members);
return mav;
}
}
경로 조합
클래스 레벨에 @RequestMapping("/springmvc/v2/members")를 붙이면, 메서드 레벨의 경로와 조합된다. 중복 경로를 제거할 수 있어 코드가 훨씬 깔끔해진다. 이후 경로에 new-form, save 등 조합을 붙여 만들어내기만 하면 되기 때문이다.
4) 실용적인 방식 (v3) - 실무 스타일
우리가 MVC 프레임워크를 만들 때 v3, v4로 넘어가면서 개발자 편의성을 높였던 것을 기억하는가? 스프링 MVC는 이미 그 모든 편의 기능을 제공한다.
- Model 파라미터: ModelAndView를 직접 생성하지 않고, 파라미터로 받은 Model에 데이터를 담는다.
- ViewName 직접 반환: 뷰의 논리 이름(String)을 반환하면 뷰 리졸버가 실행된다.
- @RequestParam: request.getParameter() 없이 파라미터를 직접 받는다.
- @GetMapping, @PostMapping: HTTP 메서드를 구분해서 매핑한다.
개선된 컨트롤러 코드 (SpringMemberControllerV3)
package org.example.springmvcpractice.servlet.web.springmvc.v3;
import org.example.springmvcpractice.servlet.domain.member.Member;
import org.example.springmvcpractice.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* v3
* Model 도입
* ViewName 직접 반환
* @RequestParam 사용
* @RequestMapping -> @GetMapping, @PostMapping
*/
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
// GET 요청만 허용
@GetMapping("/new-form")
public String newForm() {
return "new-form"; // 뷰 이름만 반환 (String)
}
// POST 요청만 허용
@PostMapping("/save")
public String save(
@RequestParam("username") String username, // request.getParameter("username") 대체
@RequestParam("age") int age,
Model model) { // Model 객체를 파라미터로 받음
Member member = new Member(username, age);
memberRepository.save(member);
model.addAttribute("member", member); // 모델에 데이터 저장
return "save-result";
}
@GetMapping
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
상세 분석
- @RequestParam: 스프링은 HTTP 요청 파라미터를 이 애노테이션으로 매핑해준다. 타입 변환(String -> int)까지 자동으로 처리해주므로 매우 편리하다.
- return "String": 뷰의 논리 이름을 반환하면, 스프링이 알아서 뷰를 찾고 렌더링한다. 더 이상 ModelAndView 객체를 생성할 필요가 없다.
- @GetMapping, @PostMapping: @RequestMapping은 모든 HTTP 메서드(GET, POST 등)에 대해 열려있지만, 이 애노테이션을 사용하면 특정 메서드만 허용할 수 있다. URL은 같아도 메서드에 따라 다른 기능을 수행하게 하는 RESTful API 설계에 필수적이다.
참고: @GetMapping의 내부 코드를 보면 @RequestMapping(method = RequestMethod.GET)이 포함되어 있는 것을 확인할 수 있다. 이를 '컴포즈드 애노테이션(Composed Annotation)'이라고 한다.
정리
우리가 v1부터 v5까지 점진적으로 프레임워크를 발전시켰던 과정은 단순한 연습이 아니었다. 그것은 스프링 MVC의 내부 설계 원리를 그대로 답습하는 과정이었다.
- FrontController => DispatcherServlet
- handlerMappingMap => HandlerMapping
- MyHandlerAdapter => HandlerAdapter
- ModelView => ModelAndView
- viewResolver => ViewResolver
그리고 스프링 MVC의 컨트롤러 코드 사용법을 살펴보며
- @Controller: 스프링 빈 등록 + Web 컨트롤러로 인식.
- @RequestMapping: 요청 URL 매핑. 클래스와 메서드 레벨의 조합이 가능하다.
- ModelAndView vs String: 초기에는 ModelAndView를 썼지만, 지금은 String(뷰 이름)을 반환하고 Model 파라미터를 쓰는 방식이 훨씬 실용적이다.
- @RequestParam: 요청 파라미터를 아주 쉽게 받을 수 있다.
- @GetMapping / @PostMapping: URL 뿐만 아니라 HTTP 메서드까지 구분하여 매핑한다.
위 처럼 스프링 MVC의 기본 사용법을 익혔다.
이제 스프링 MVC의 전체적인 구조를 파악한 우리는 복잡한 서블릿 코드 없이, 애노테이션 몇 개만으로 강력한 웹 애플리케이션을 만들 수 있게 된 것이다.
다음 섹션에서는 스프링 MVC가 제공하는 가장 강력하고 편리한 기능인 애노테이션 기반 컨트롤러(@RequestMapping)에 대해 본격적으로 다루도록 한다.
'Spring RoadMap > Spring MVC' 카테고리의 다른 글
| 스프링 MVC 1편 (7 - 마지막), 웹 페이지 만들기 (0) | 2025.12.21 |
|---|---|
| 스프링 MVC 1편 (6), 스프링 MVC 기본 기능 (1) | 2025.12.21 |
| 스프링 MVC 1편 (4), MVC 프레임워크 만들기 (0) | 2025.12.20 |
| 스프링 MVC 1편 (3), 서블릿 · JSP · MVC 패턴 (0) | 2025.12.19 |
| 스프링 MVC 1편 (2), 서블릿 (0) | 2025.12.18 |