공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
직접 만든 MVC 프레임워크와 스프링 MVC를 비교해보자.
스프링 MVC 전체 구조
1. 직접 만든 프레임 워크와 스프링 MVC 비교
우리가 만든 프레임워크는 기본적으로 FrontController 패턴을 사용하여 요청을 중앙에서 관리하고, 적절한 컨트롤러를 호출하는 구조였다. 스프링 MVC의 DispatcherServlet도 같은 역할을 수행한다.
직접 만든 MVC 프레임워크 구조
2. 스프링 MVC의 요청 처리 흐름
스프링 MVC의 요청 처리 과정은 우리가 만든 프레임워크와 유사하지만, 더 확장성이 뛰어난 구조를 가지고 있다. DispatcherServlet이 중심이 되어 요청을 처리하며, 어댑터와 핸들러 매핑 등을 활용해 유연한 구조를 제공한다.
SpringMVC 구조
동작 순서
- 핸들러 조회: HandlerMapping을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
- 핸들러 어댑터 조회: 조회된 핸들러를 실행할 수 있는 적절한 HandlerAdapter를 찾는다.
- 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.
- 핸들러 실행: 핸들러 어댑터가 실제 컨트롤러를 실행한다.
- ModelAndView 반환: 컨트롤러가 처리한 결과를 ModelAndView 형태로 반환한다.
- viewResolver 호출: 뷰 리졸버를 호출하여 논리적인 뷰 이름을 실제 뷰 객체로 변환한다.
- View 반환: 뷰 객체를 반환한다.
- 뷰 렌더링: 뷰를 통해 최종적으로 HTML을 응답으로 생성한다.
JSP의 경우: InternalResourceViewResolver가 자동으로 등록되며, InternalResourceView(JstlView)를 사용하여 내부적으로 forward()를 수행한다.
3. DispatcherServlet 구조 살펴보기
DispatcherServlet의 역할
스프링 MVC도 프론트 컨트롤러 패턴을 기반으로 구현되어 있다. 이 핵심적인 역할을 담당하는 것이 DispatcherServlet이며, 모든 HTTP 요청을 중앙에서 처리한다.
DispatcherServlet 서블릿 등록
DispatcherServlet은 HttpServlet을 상속받아 동작하는 서블릿이다. 스프링 부트는 이를 자동으로 서블릿으로 등록하며, 기본적으로 모든 요청(urlPatterns="/")을 처리하도록 설정한다.
참고: 더 구체적인 경로가 존재할 경우 해당 경로가 우선 처리된다. 따라서 기존 서블릿과 함께 동작할 수 있다.
요청 흐름
- 클라이언트의 요청이 들어오면 HttpServlet의 service() 메서드가 호출된다.
- 스프링 MVC는 DispatcherServlet의 부모 클래스인 FrameworkServlet에서 service()를 오버라이드하여 여러 내부 메서드를 호출한 후 doDispatch()를 실행한다.
- FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.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);
}
뷰 렌더링 과정
doDispatch() 실행 이후, processDispatchResult() 메서드를 통해 뷰를 찾아 렌더링한다.
뷰를 찾고 렌더링하는 과정은 render() 메서드에서 이루어진다. 이 과정에서 viewResolver를 호출하여 논리적인 뷰 이름을 물리적인 뷰로 변환한 후, view.render()를 실행하여 최종 응답을 생성한다.
4. 주요 인터페이스 분석
스프링 MVC는 DispatcherServlet을 수정하지 않고도 확장 가능한 구조를 제공한다. 이를 위해 주요 기능을 인터페이스로 분리하여 필요에 따라 원하는 기능을 쉽게 확장할 수 있다.
기능 | 인터페이스 |
핸들러 매핑 | org.springframework.web.servlet.HandlerMapping |
핸들러 어댑터 | org.springframework.web.servlet.HandlerAdapter |
뷰 리졸버 | org.springframework.web.servlet.ViewResolver |
뷰 | org.springframework.web.servlet.View |
이러한 인터페이스를 직접 구현하여 확장하면, 우리가 만든 프레임워크에서처럼 새로운 방식의 컨트롤러를 추가하거나, 다른 뷰 렌더링 방식을 적용할 수 있다.
5. 정리
스프링 MVC는 코드가 방대하고 복잡하지만, 대부분의 기능이 이미 구현되어 있어 직접 확장하거나 커스텀 컨트롤러를 만들 필요는 거의 없다. 하지만 핵심 동작 방식을 이해하면 문제 발생 시 원인을 쉽게 파악하고, 확장 포인트를 찾는 데 도움이 된다. 전체적인 구조를 익혀두면 이후 기능을 확장하거나 다른 컴포넌트를 활용할 때 유용하다.
핸들러 매핑과 핸들러 어댑터
스프링 MVC에서 요청을 처리하기 위해서는 핸들러 매핑(HandlerMapping) 과 핸들러 어댑터(HandlerAdapter) 가 필요하다. 이 두 가지 개념이 어떻게 동작하는지, 그리고 과거의 스프링 컨트롤러 방식과 현재의 방식이 어떻게 다른지 살펴보자.
1. 핸들러 매핑과 핸들러 어댑터란?
- 핸들러 매핑(HandlerMapping): 클라이언트의 요청 URL을 기반으로 적절한 핸들러(컨트롤러) 를 찾는다.
- 핸들러 어댑터(HandlerAdapter): 조회된 핸들러를 실행할 수 있도록 도와주는 역할을 한다.
스프링은 기본적으로 여러 종류의 핸들러 매핑과 어댑터를 제공하며, 개발자가 이를 직접 구현할 필요는 거의 없다.
2. 과거 스프링 컨트롤러 - Controller 인터페이스
2.1. Controller 인터페이스
과거 스프링에서는 org.springframework.web.servlet.mvc.Controller 인터페이스를 구현해야 했다.
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
이 인터페이스를 구현한 컨트롤러는 handleRequest() 메서드를 오버라이드하여 요청을 처리해야 했다.
2.2. OldController 구현
@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;
}
}
- @Component("/springmvc/old-controller"): 스프링 빈으로 등록되며, 요청 URL과 매핑된다.
- 실행하면 /springmvc/old-controller 요청이 들어올 때 OldController.handleRequest() 가 호출된다.
실행 테스트
- URL: http://localhost:8080/springmvc/old-controller
- 기대 결과: 콘솔에 OldController.handleRequest 출력
이제 이 컨트롤러가 실행되려면 핸들러 매핑과 핸들러 어댑터 가 필요하다.
3. OldController 실행 과정
3.1. 핸들러 매핑 (HandlerMapping)
핸들러 매핑은 요청 URL에 따라 핸들러(컨트롤러)를 찾는다. 과거에는 스프링 빈의 이름을 기반으로 핸들러를 찾는 방식을 사용했다.
BeanNameUrlHandlerMapping → 빈의 이름을 URL과 매핑하여 OldController를 찾는다.
3.2. 핸들러 어댑터 (HandlerAdapter)
찾은 핸들러를 실행하려면 해당 핸들러를 처리할 수 있는 핸들러 어댑터 가 필요하다.
SimpleControllerHandlerAdapter → Controller 인터페이스를 실행할 수 있도록 지원
3.3. 실행 과정 정리
1. 핸들러 매핑으로 핸들러 조회
- 1. HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.
- 2. 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping가 실행에 성공하고 핸들러인 OldController 를 반환한다.
2. 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출한다.
- SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 된다.
3. 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.
- SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.
정리 - OldController 핸들러매핑, 어댑터
OldController를 실행하면서 사용된 객체는 다음과 같다.
- HandlerMapping = BeanNameUrlHandlerMapping
- HandlerAdapter = SimpleControllerHandlerAdapter
4. HttpRequestHandler 인터페이스
Controller 인터페이스 외에도, 서블릿과 유사한 방식으로 동작하는 컨트롤러도 존재했다. 그중 하나가 HttpRequestHandler 인터페이스이다.
4.1. HttpRequestHandler 인터페이스
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
이 인터페이스를 구현하면, 서블릿과 비슷하게 직접 요청을 처리할 수 있다.
4.2. MyHttpRequestHandler 구현
@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");
}
}
실행 테스트
- URL: http://localhost:8080/springmvc/request-handler
- 기대 결과: 콘솔에 MyHttpRequestHandler.handleRequest 출력
4.3. 실행 과정 정리
1. 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping가 실행에 성공하고 핸들러인 MyHttpRequestHandler를 반환한다.
2. 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출한다.
- HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
3. 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.
- HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고, 그 결과를 반환한다.
정리 - MyHttpRequestHandler 핸들러매핑, 어댑터
MyHttpRequestHandler를 실행하면서 사용된 객체는 다음과 같다.
- HandlerMapping = BeanNameUrlHandlerMapping
- HandlerAdapter = HttpRequestHandlerAdapter
5. 스프링 부트에서 자동 등록되는 핸들러 매핑과 어댑터
5.1. 핸들러 매핑 (HandlerMapping)
순서 | 핸들러 매핑 종류 | 설명 |
0 | RequestMappingHandlerMapping | @RequestMapping 기반 매핑 (현재 사용) |
1 | BeanNameUrlHandlerMapping | 스프링 빈의 이름을 URL로 매핑 (과거 방식) |
5.2. 핸들러 어댑터 (HandlerAdapter)
순서 | 핸들러 어댑터 종류 | 설명 |
0 | RequestMappingHandlerAdapter | @RequestMapping 컨트롤러 실행 |
1 | HttpRequestHandlerAdapter | HttpRequestHandler 처리 |
2 | SimpleControllerHandlerAdapter | Controller 인터페이스 처리 (과거 방식) |
핸들러 매핑과 어댑터는 순서대로 실행되며, 해당 핸들러를 처리할 수 있는 첫 번째 매핑 및 어댑터가 사용된다.
6. 정리
- 스프링 MVC는 다양한 핸들러 매핑과 핸들러 어댑터를 제공하여 요청을 유연하게 처리한다.
- 과거에는 Controller 인터페이스와 HttpRequestHandler 를 사용했지만, 현재는 @RequestMapping 기반의 컨트롤러가 주로 사용된다.
- 스프링 부트는 RequestMappingHandlerMapping 과 RequestMappingHandlerAdapter 를 기본적으로 사용하며, 옛 방식도 지원하지만 잘 사용하지 않는다.
결론: 실무에서는 @RequestMapping 기반의 컨트롤러를 사용하고, 옛 방식은 잘 사용하지 않는다. 하지만 구조를 이해하면 스프링 MVC의 동작 원리를 더 깊이 이해할 수 있다.
'Spring MVC' 카테고리의 다른 글
[MVC] 스프링 MVC - 구조 이해(3) (1) | 2025.02.22 |
---|---|
[MVC] 스프링 MVC - 구조 이해(2) (0) | 2025.02.20 |
[MVC] MVC 프레임워크 만들기(4) (0) | 2025.02.17 |
[MVC] MVC 프레임워크 만들기(3) (1) | 2025.02.12 |
[MVC] MVC 프레임워크 만들기(2) (1) | 2024.12.08 |