Written by
Sunwoo Han
on
on
스프링 MVC 구조 이해
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 공부하고 정리하는 포스트입니다.
스프링 MVC 구조 이해
직접 만든 MVC 프레임 워크와 비교
- 프론트 컨트롤러(Front Controller) 패턴의 MVC 프레임워크와 스프링 MVC의 내부구조는 동일
- FrontController -> DispatcherServlet
- handlerMappingMap -> HandlerMapping
- MyHandlerAdapter -> HandlerAdapter
- ModelView -> ModelAndView
- ViewResolver -> viewResolver
- MyView -> View
스프링 MVC 전체 구조
설명
- 스프링 MVC도 앞서 직접 만든 것과 동일하게 프론트 컨트롤러(Front Controller) 패턴으로 구현
- 프론트 컨트롤러 역할을 하는 것이 바로
Dispatcher Servlet
-> Dispatcher Servlet이 스프링 MVC의 핵심
DispatcherServlet
설명
- 부모 클래스를 따라가면 결국 HttpServlet을 상속받아서 사용하는 서블릿!
- DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
- 스프링 부트는 DispatcherServlet을 서블릿으로 자동 등록하면서 모든 경로에 대해 매핑
- 이렇게 모든 경로에 대해 매핑된 다음 지정하는 url 경로에 대해 매핑됨 -> 자세한 경로의 우선순위가 높음!
- 서블릿이 호출되면 HttpServlet의
service()
가 호출- FrameworkServlet에서
service()
를 Override 해두었기 때문에 이를 통해 서블릿이 시작 -> 이를 통해DispatcherServlet.doDispatch()
가 호출되며 우리의 비즈니스 로직이 시작 - 결국, 스프링 MVC의 핵심 동작은
DispatcherServlet.doDispatch()
에 모두 기술
- FrameworkServlet에서
DispatcherServlet.doDispatch()
DispatcherServlet.doDispatch()
핵심 동작 코드- 예외 처리 / 인터셉터 기능은 제외한 간단한 로직만 정리
protected void doDispatch(HttpServletRequest request, HttpServletResponseresponse) 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);
}
- 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회
- 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터 조회
- 핸들러 어댑터 실행 : 핸들러 어댑터를 실행
- 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행
- ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
- viewResolver 호출 : 뷰 리졸버를 찾고 실행
- JSP의 경우 :
InternalResourceViewResolver
가 자동 등록, 사용
- JSP의 경우 :
- View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환
- JSP의 경우 :
InternalResourceView(JstlView)
를 반환, 내부에forward()
로직이 있음
- JSP의 경우 :
- 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링
인터페이스
- 인터페이스 살펴보기
- 대부분의 기능을 확장 가능한 인터페이스로 제공
- 스프링 MVC의 강점은
DispatcherServlet
코드 변경 없이 원하는 기능을 변경하거나 확장 가능한 것 - 인터페이스만 구현해서
DispatcherServlet
에 등록하면 나만의 컨트롤러 생성 가능 (실제로는 만들 일이 거의 없음)
- 주요 인터페이스
- 핸들러 매핑 :
org.springframework.web.servlet.HandlerMapping
- 핸들러 어댑터 :
org.springframework.web.servlet.HandlerAdapter
- 뷰 리졸버 :
org.springframework.web.servlet.viewResolver
- 뷰 :
org.springframework.web.servlet.View
- 핸들러 매핑 :
정리
- 스프링 MVC는 복잡해서 내부 핵심 구조만 알고 있어도 충분
- 직접 기능을 확장하거나 나만의 컨트롤러를 만들 일은 거의 없음
- 이미 수많은 기능이 개발되어 있기 때문에 필요한 기능은 대부분 존재
- 스프링 MVC의 핵심 구조를 알아야 향후 문제가 발생했을 때 어떤 부분에서 발생했는지 파악이 가능
핸들러 매핑 / 핸들러 어댑터 / 뷰 리졸버
개요
- 스프링 MVC의 구조에서 핵심적인 역할을 하는 것은 크게 3가지
- 핸들러 매핑 : url에 해당하는 핸들러 조회
- 핸들러 어댑터 : 핸들러를 처리할 수 있는 핸들러 어댑터(실제 핸들러 호출)
- 뷰 리졸버 : 논리 이름을 파일의 실제 물리 주소로 변환
- 핸들러 매핑 / 핸들러 어댑터가 스프링 MVC에서 어떻게 동작하는지 이해하는게 중요!
- 지금은 사용하지 않는 인터페이스를 통해 내부 동작 흐름을 쉽게 이해 가능
핸들러 매핑 / 핸들러 어댑터 동작 이해 - Controller 인터페이스를 활용
- 지금은 사용하지 않는
Controller
라는 인터페이스를 상속한OldController
가 호출되는 과정을 알아보자
- 해당 핸들러(컨트롤러)가 호출되려면 2가지가 필요
- HandlerMapping(핸들러 매핑) : 해당 url 경로를 처리할 수 있는 핸들러 매핑을 찾고 해당하는 핸들러 반환
ex) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요 - HandlerAdapter(핸들러 어댑터) : 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터 필요
eX) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행
- HandlerMapping(핸들러 매핑) : 해당 url 경로를 처리할 수 있는 핸들러 매핑을 찾고 해당하는 핸들러 반환
- HandlerMapping / HandlerAdapter
- 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현.
/* HandlerMapping */
0 = RequestMappingHandlerMapping // @RequestMapping의 핸들러 매핑
1 = BeanNameUrlHandlerMapping // 스프링 빈(Bean)의 이름으로 핸들러를 매칭
/* HandlerAdapter */
0 = RequestMappingHandlerAdapter // @RequestMapping의 핸들러 어댑터
1 = HttpRequestsHandlerAdapter // HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter // Controller 인터페이스 처리
- 동작순서
- 핸들러 매핑 찾기 & 핸들러 반환
- HandlerMapping을 순서대로 실행 -> 실행할 수 있는 핸들러 매핑을 탐색
- 이 경우 빈(Bean) 이름으로 핸들러를 찾아야 하기 때문에
BeanNameUrlHandlerMapping
을 찾음 (우선순위 1에 해당) - 찾은 핸들러인
OldController
를 반환
- 핸들러 어댑터 조회
- HandlerAdapter의
supports()
를 순서대로 호출하며 처리할 수 있는 핸들러 어댑터를 탐색 - 이 경우
SimpleControllerHandlerAdapter
가 Controller 인터페이스를 지원함으로 선택됨 (우선순위 2에 해당)
- HandlerAdapter의
- 핸들러 어댑터 실행
- 위에서 찾은 핸들러 어댑터인
SimpleControllerHandlerAdapter
를 통해 실제OldController
실행 - ModelAndView 객체를 반환
- 위에서 찾은 핸들러 어댑터인
- 핸들러 매핑 찾기 & 핸들러 반환
뷰 리졸버
- 논리 뷰 이름을 통해서 실제 물리 주소로 매핑해주는 역할
-
스프링 부트가 자동 등록하는 뷰 리졸버
- 스프링 부트는 JSP 사용을 위해
InternalResourceViewResolver
라는 뷰 리졸버를 자동으로 등록 - application.properties에
prefix / suffix
설정 정보를 사용해서 등록해야 함- spring.mvc.view.prefix : /WEB-INF/views
- spring.mvc.view.suffix : .jsp
0 = BeanNameViewResolver // 빈 이름으로 뷰를 찾아서 반환
1 = InternalResourceViewResolver // JSP를 처리할 수 있는 뷰를 반환
- 동작 순서
- 핸들러 어댑터 호출
- new-form이라는 논리 뷰 이름을 호출
- ViewResolver 호출
- new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출
- 이 경우 스프링 빈이 없기 때문에
InternalResourceViewResolver
가 호출
- InternalResourceViewResolver
InternalResourceView
를 반환
- InternalResourceView
InternalResourceView
는 JSP처럼forward()
를 호출해서 처리할 수 있는 경우에 사용
- view.render()
view.render()
가 호출되고InternalResourceView
는forward()
를 사용해 JSP를 실행
- 핸들러 어댑터 호출
스프링 MVC 시작하기
- 스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작 -> 유연하고 실용적
@RequestMapping
-
매우 실용적인 컨트롤러
@RequestMapping
RequestMappingHandlerMapping
RequestMappingHandlerAdapter
- 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는
RequestMappingHandlerMapping
와RequestMappingHandlerAdapter
- 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터
- 실무에서 99.9%가 이 방식의 컨트롤러를 사용
- 클래스 레벨에
@RequestMapping
으로 공통 Path를 지정 @RequestMapping
을 편리하게 사용하기 위한@GetMapping()
/@PostMapping()
을 사용@RequestParam
을 이용해서 요청 변수를 바로 받음- ModelAndView 객체를 직접 반환하는 것이 아니라 viewName만으로 처리해서 사용성이 좋아짐
Comments
SPRING 의 다른 글
-
스프링 타입 컨터버 24 Jun 2022
-
API 예외 처리 17 Jun 2022
-
예외 처리와 오류 페이지 12 Jun 2022
-
로그인 처리 - 인터셉터 08 Jun 2022
-
로그인 처리 - 필터 06 Jun 2022
-
로그인 처리 - 쿠키, 세션 31 May 2022
-
Bean Validation 22 May 2022
-
검증 22 May 2022
-
메시지, 국제화 21 May 2022
-
타임리프 - 스프링 통합과 폼 19 May 2022
-
타임리프 - 기본 기능 10 May 2022
-
스프링 MVC 기본 기능 - 웹 페이지 만들기 02 May 2022
-
스프링 MVC 기본 기능 - HTTP 응답 30 Apr 2022
-
스프링 MVC 기본 기능 - HTTP 요청 24 Apr 2022
-
스프링 MVC 기본 기능 - 요청 매핑 19 Apr 2022
-
스프링 MVC 기본 기능 19 Apr 2022
-
스프링 MVC 구조 이해 14 Apr 2022
-
MVC 프레임워크 만들기 - V4, V5 12 Apr 2022
-
MVC 프레임워크 만들기 - V1, V2, V3 09 Apr 2022
-
서블릿, JSP, MVC 패턴 05 Apr 2022
-
서블릿 29 Mar 2022
-
웹 애플리케이션 이해 24 Mar 2022
-
스프링 웹 계층이란? 05 Nov 2021
-
스프링 시큐리티 공식문서 번역 27 Sep 2021
-
스프링 AOP 총정리 : 개념, 프록시 기반 AOP, @AOP 27 Apr 2021
-
SpEL (스프링 Expression Language) 25 Apr 2021
-
데이터 바인딩 추상화 : Converter와 Formatter 21 Apr 2021
-
데이터 바인딩 추상화 : PropertyEditor 12 Apr 2021
-
Validation 추상화 10 Apr 2021
-
Resource 추상화 08 Apr 2021
-
IoC 컨테이너 9부 07 Apr 2021
-
IoC 컨테이너 8부 06 Apr 2021
-
IoC 컨테이너 7부 02 Apr 2021
-
IoC 컨테이너 6부 29 Mar 2021
-
IoC 컨테이너 5부 27 Mar 2021
-
IoC 컨테이너 4부 23 Mar 2021
-
IoC 컨테이너 3부 20 Mar 2021
-
IoC 컨테이너 2부 18 Mar 2021
-
IoC 컨테이너 1부 12 Mar 2021
-
스프링 PSA 07 Jan 2021
-
스프링 @AOP 실습 07 Jan 2021
-
프록시 패턴 06 Jan 2021
-
스프링 AOP 04 Jan 2021
-
의존성 주입(Dependency Injection) 04 Jan 2021
-
스프링 빈(Bean) 02 Jan 2021
-
스프링 IoC 컨테이너 01 Jan 2021
-
스프링 IoC 01 Jan 2021