인프런 김영한 님의 스프링 강의이며, 섹션 5 - 스프링 MVC 구조 이해를 정리한다.
자세한 설명이 궁금하면 수강을 권장한다.
스프링 MVC 전체 구조
이전까지 만들었던 프레임워크 구조는 아래와 같다.
스프링 MVC 구조는 다음과 같다.
지금까지 만들고 개선했던 게 스프링 MVC 구조와 거의 흡사하다.
직접 만든 프레임워크 | 스프링 MVC |
FrontController | DispatcherServlet |
handlerMappingMap | HandlerMapping |
MyHandlerAdapter | HandlerAdapter |
ModelVIew | ModelAndView |
viewResolver | ViewResolver |
MyView | View |
DispatcherServlet 구조 알아보기
org.springframework.web.servlet.DispatcherServlet; 를 찾아가면 엄청난 코드들이 존재한다.
간단히 요약하자면 스프링 MVC도 프론트 컨트롤러 패턴 구조로 되어있다.
스프링 MVC에서 프론트컨트롤러가 디스패쳐서블릿(DispatcherServlet)이고, 이게 바로 스프링MVC 핵심이다.
디스패쳐서블릿도 HttpServlet을 상속받아 사용하고 서블릿으로 동작한다.
- DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
스프링부트는 디스패쳐서블릿을 자동으로 등록하면 urlPattern="/" 매핑한다. 자세한 경로에 우선순위가 높아 기존 서블릿도 함께 동작한다.
요청 흐름
서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
스프링 MVC는 디스패쳐의 부모인 FrameworkServlet에서 service()를 오버라이드 했다.
FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch()가 호출된다.
코드를 보면 내용이 어렵지만 핵심로직을 구분하면 다음과 같다.
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);
그리고 마지막에 실행되는 processDispatchResult의 로직이다.
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. 뷰 리졸버를 통해서 뷰 찾기, 6. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
위의 로직으로 스프링 MVC의 동작 순서를 정리한 이미지 (맨 처음 언급한 이미지다)
직접 살펴본 스프링 MVC의 구조는 코드도 많고 복잡해서 파악하기 힘들지만
핵심 동작방식을 알아두면 문제를 발생했을 때 쉽게 파악하고 해결할 수 있다.
또한 추가확장이 필요할 때 어떤 부분을 확장할지 감을 잡을 수 있다.
핸들러 매핑과 핸들러 어댑터
현재는 사용하지 않지만 과거에 사용했던 핸드러 매핑과 핸들러 어댑터를 알아보자. (어노테이션 기반이 나오면서 세대교체됨)
@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;
}
}
이 옛날 방식의 컨트롤러가 호출되려면 2가지가 필요하다
1. HandlerMapping(핸들러 매핑)
- 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야한다
- 스프링 빈의 이름으로 핸들러를 찾아야한다
2. HandlerAdapter(핸들러 어댑터)
- 찾은 핸들러를 실행할 핸들러 어댑터가 필요
- Controller 인터페이스를 실행할 핸들러 어댑터를 찾고 실행해야 한다
스프링은 이미 대부분 구현되어있어서 개발자가 직접 만드는 일이 없다.
스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
등록된 핸들러 매핑
...
0 = RequestMappingHandlerMapping : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾음
...
등록된 핸들러 어댑터
...
0 = RequestMappingHandlerAdapter : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpReqeustHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : 과거방식의 Controller 인터페이스 처리
...
작동방법은 이전에 만들었던 프레임워크와 동일하다.
1. 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서 찾는다.
- 이 경우 빈 이름으로 핸들러를 찾는 BeanNameUrlHandlerMapping가 성공하여 OldController를 반환한다.
2. 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 실행한다.
- SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하니 대상이 된다.
3. 핸들러 어댑터 실행
- 디스패쳐 서블릿이 조회한 어댑터를 실행하면서 핸들러 정보도 넘겨준다
- 어댑터가 핸들러를 실행하고 그 결과를 반환한다.
여기서 사용된 객체는 다음과 같다.
HandlerMapping = BeanNameUrlHandlerMapping
HandlerAdapter = SimpleControllerHandlerAdapter
다른 방법인 HttpRequestHandler로 한번 더 해보자.
@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
HandlerAdapter = HttpReqeustHandlerAdapter
@RequestMapping
가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping, RequestMappingHandlerAdapter 이다.
@RequestMapping 앞글자를 따서 만든 이름으로, 현재 사용하는 어노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터다. 대부분 실무에서 사용한다.
뷰 리졸버
OldController에 ModelAndView 객체를 반환하도록 수정해보자.
@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 new ModelAndView("new-form");
}
}
new-form으로 이동하려고 한다.
http://localhost:8080/springmvc/old-controller 경로로 이동하자
로그를 보면 정상적으로 실행이 되었는데, 에러 페이지가 발생한다.
이유는 뷰를 못 찾기 때문으로 뷰 리졸버를 만들어야 한다.
하지만 스프링 부트이기 때문에 application.properties에 가서 설정을 추가한다.
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
재실행하고 새로고침을 하면
정상적으로 접근이 된다.
뷰 리졸버 - InternalResourceViewResolver
스프링 부트는 InternalResourceViewResolver를 자동적으로 등록하기 때문에 application.properties 설정 정보를 사용해서 등록한다.
직접 선언해도 되지만, 스프링 부트의 편리한 기능을 애용하자.
@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class ServletApplication {
...
// 직접 선언해도 되지만, 편안하게 application.properties 설정을 이용하자.
@Bean
ViewResolver internalResourceViewResolver() {
return new InternalResourceViewResolver("/WEB-INF/view/", ".jsp");
}
}
뷰 리졸버 동작 방식
스프링 부트가 자동으로 등록하는 뷰 리졸버
...
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환 (ex: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환
// 많지만 생략
...
1. 핸들러 어댑터 호출
- 핸들러 어댑터를 통해 new-form 논리 뷰 이름을 획득
2. ViewResolver 호출
- new-form 뷰 이름으로 viewResolver를 순서대로 호출
- BeanNameViewResolver는 찾아보지만 없다
- InternalResourceViewResolver에서 찾아 호출된다
3. InternalResourceViewResolver
이 뷰 리졸버는 InternalResourceView를 반환한다
4. 뷰 - InternalResourceView
JSP처럼 forward()를 호출해서 처리할 수 있는 경우에 사용된다.
5. view.render()
view.render()가 실행되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다.
'교육 및 인강 > 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
스프링 MVC 기본 기능 - 로깅 알아보기, 요청 매핑, API 예시 (0) | 2021.07.19 |
---|---|
스프링 MVC 이해 - 시작하기, 컨트롤러 통합, 실용적인 방식 (0) | 2021.07.19 |
MVC 프레임워크 만들기 - 실용적인 컨트롤러 (v4), 유연한 컨트롤러 (v5) (0) | 2021.07.13 |
MVC 프레임워크 만들기 - View 분리(v2), Model 추가(v3) (0) | 2021.07.05 |
MVC 프레임워크 만들기 - 프론트 컨트롤러 패턴 소개, 도입 (0) | 2021.07.05 |