Spring-MVC (1) 기본 구조
Spring MVC의 기본 구조
참고:
Inflearn - 김영한님_강의
Spring MVC 동작 순서
- 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(Controller) 를 조회한다.
- 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
- 핸들러 어댑터 실행: 핸들러 어댑터를 실행
- 핸들러 실행: 핸들러 어댑터로 인해 실제 핸들러가 실행된다.
- ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView 로 변환하여 반환.
- viewResolver 호출: 뷰 리졸버를 찾아 실행한다.
- View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 변환 후 Rendering 역할을 담당하는 뷰 객체를 반환한다.
- 뷰 렌더링: 뷰를 통해 뷰를 렌더링 한다.
Spring MVC의 기본 구조에 대해 알아보자.
- Spring MVC도 프론트 컨트롤러 패턴으로 구현되어 있다.
- Spring MVC에서 프론트 컨트롤러 역할은 DispatcherServlet이 맡고 있으며, 이는 곧 Spring MVC의 핵심이다.
DispatcherServlet
Dispatcher Servlet?
(1) DispatcherServlet의 서블릿 등록
- DispatcherServlet의 상위 클래스를 따라가다 보면 HttpServlet을 상속받아 서블릿으로 동작하고 있는 것을 알 수 있다.
- 스프링 부트가 내장 WAS (내장 톰캣 서버 등) 를 띄우는 동시에 DispatcherServlet을 서블릿으로 자동 등록한다.
- 그 과정에서 모든 경로(‘urlpatterns=”/”‘)에 대하여 매핑해준다.
- 하지만 위와 같이 모든 경로에 대하여 매핑이 되면 해당 작업의 우선 순위가 가장 낮아져 더욱 자세한 하위 경로에 대한 매핑도 잘 동작한다.
(2) doDispatch
- 서블릿이 호출된 후 HttpServlet이 제공하는 Service()가 호출된다. (DispatcherServlet이 상속하고 있는 FrameworkServlet에서 service()를 override 해둠.)
- 최종적으로 제일 중요한
DispatcherServlet.doDispatch()
가 호출된다. -
protected void doDispatch(HttpServletRequest request, HttpServletResponse response){ ... ModelAndView mv = null; // modelAndView 사용 ... // 1. 핸들러 조회 mappedHandler = getHandler(processedRequest); // 핸들러를 찾는다. if (mappedHandler == null) { noHandlerFound(processedRequest, response); // 핸들러가 없으면 SC_NOT_FOUND : 404 오류를 발생시킨다. return; } // 2. 핸들러 어댑터 조회 - getHandler()하여 찾은 핸들러를 처리할 수 있는 핸들러 어댑터 조회 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // getHandler()하여 찾은 핸들러를 가지고 핸들러 어댑터를 찾는다. ... // 3. 핸들러 어댑터 실행 (ha) -> 4. 핸들러 어댑터를 통해 핸들러 실행 (ha.handle()) -> 5. ModelAndView 반환 (mv) mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 찾은 실제 핸들러 어댑터를 ModelAndView 로 받는다. ... // render()를 반환하는 processDispatchResult 가 실행된다. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException){ ... // 뷰 렌더링 호출 render(mv, request, response) { View view; String viewName = mv.getViewName(); ... // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); // 8. 뷰 렌더링 (JSP Forward logic이 포함되어 있다) view.render(mv.getModelInternal(), request, response); } } }
주요 인터페이스
- 핸들러 매핑: org.springframework.web.servlet.HandlerMapping
- 핸들러 어댑터: org.springframework.web.servlet.HandlerAdapter
- 뷰 리졸버: org.springframework.web.servlet.ViewResolver
- Spring 용, Thymeleaf 용 뷰 리졸버가 각각 따로 제공된다.
- 뷰: org.springframework.web.servlet.View
- JSP용 뷰, Thymeleaf용 뷰가 따로 존재한다.
핸들러 매핑과 핸들러 어댑터
컨트롤러의 호출 방법 및 과정
컨트롤러를 호출 하는것에 있어서 2가지의 전제 조건이 성립해야 한다.
- HandlerMapping(핸들러 매핑)
- 핸들러 매핑에서 해당 컨트롤러를 찾을 수 있어야 한다.
- HandlerAdapter(핸들러 어댑터)
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
- ex. Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
Spring Boot 가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
// HandlerMapping
0 = RequestMappingHandlerMapping // 어노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용 (대부분 이 방법을 사용)
1 = BeanNameUrlHandlerMapping // 스프링 빈의 이름으로 핸들러를 찾는다.
// HandlerAdapter
0 = RequestMappingHandlerAdapter // 애노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용.
1 = HttpRequestHandlerAdapter // HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter // Controller 인터페이스 처리 (Annotation 이 아닌, 과거에 사용하던 Controller)
- 핸들러 매핑과 핸들러 어댑터를 위 순서대로 찾아 실행하며, 만약 없다면 다음 순서로 넘어가 찾는다.
핸들러 매핑 및 핸들러 어댑터의 조회 및 실행 과정
- 핸들러 매핑을 통한 핸들러 조회
- HandlerMapping 을 위 순서대로 실행하여 핸들러를 찾는다.
- 핸들러 어댑터 조회
- HandlerAdapter 의 supports()를 순서대로 호출한다.
- 핸들러 어댑터 실행
- DispatcherServlet 이 조회한 핸들러 어댑터를 실행하면서 핸들러 정보도 함께 넘겨준다.
- 해당 핸들러 어댑터는 넘겨받은 핸들러(정보)를 내부에서 실행하고, 그 결과(ModelAndView(“논리 뷰 이름 == new-form”))를 반환.
@RequestMapping
가장 우선 순위가 높은 핸들러 매핑과 어댑터는
RequestMappingHandlerMapping (핸들러 매핑)
RequestMappingHandlerAdapter (핸들러 어댑터)
@RequestMapping 의 앞글자를 따서 만든 이름이며,
위 두가지가 바로 현재 스프링에서 주로 사용하는 Annotation 기반의 컨트롤러를 지원하는 매핑과 어댑터이다.
실무에서는 99.9% 이 방식의 컨트롤러를 사용한다고 한다.
뷰 리졸버 (InternalResourceViewResolver)
뷰 리졸버 - InternalResourceViewResolver
- 스프링 부트는
InternalResourceViewResolver
라는 뷰 리졸버를 자동으로 등록해준다.- 이때 application.properties 에 등록한
spring.mvc.view.prefix
/spring.mvc.view.suffix
설정 정보를 사용해서 등록한다.
spring.mvc.view.prefix=/WEB-INF/views/ // prefix 이후는 예시 spring.mvc.view.suffix=.jsp // suffix 이후는 예시
뷰 리졸버의 동작 방식
스프링 부트가 자동 등록하는 뷰 리졸버
1 = BeanNameViewResolver // 빈 이름으로 뷰를 찾아서 반환한다. 2 = InternalResourceViewResolver // JSP를 처리 할 수 있는 뷰를 반환한다.
동작 과정
- 핸들러 어댑터 호출
- 핸들러 -> 핸들러 어댑터가 반환하는
ModelAndView("new-form")
을 통해new-form(예시)
이라는 논리 뷰 이름을 획득
- 핸들러 -> 핸들러 어댑터가 반환하는
- ViewResolver 호출
new-form
이라는 뷰 이름으로 viewResolver 를 순서대로 호출한다.BeanNameViewResolver
는new-form
이라는 이름의 스프링 빈으로 등록된 뷰를 찾는다.- 만약 해당 사항이 없다면 다음 호출 순서인 InternalResourceViewResolver가 호출된다.
- InternalResourceViewResolver
InternalResourceView
를 반환한다.
- 뷰 - InternalResourceView
InternalResourceView
는 JSP 처럼forward()
를 호출해서 처리할 수 있는 경우에 사용.
- view.render()
view.render()
가 호출되어InternalResourceView
는forward()
를 사용하여 JSP를 실행.
참고
- 여타 뷰는 실제 뷰를 렌더링. JSP의 경우
forward()
를 통해서 해당 JSP로 이동(실행)해야만 렌더링 가능.
- JSP를 제외한 나머지 View(Thymeleaf … etc)템플릿들은
forward()
과정없이 바로 렌더링이 된다.- Thymeleaf 뷰 템플릿을 사용하면
ThymeleafViewResolver
를 등록해야 사용이 가능하다.
- 이는 라이브러리만 추가하면 스프링 부트가 모두 자동화 해준다.
최대한의 설명을 코드 블럭 내의 주석으로 달아 놓았습니다.
혹시 이해가 안가거나 추가적인 설명이 필요한 부분, 오류 등의 피드백은 언제든지 환영합니다!
긴 글 읽어주셔서 감사합니다. 포스팅을 마칩니다.
Task Lists
- Spring MVC 동작 순서
- DispatcherServlet
- 주요 인터페이스
- 핸들러 매핑과 핸들러 어댑터
- Spring Boot 가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
- 핸들러 매핑 및 핸들러 어댑터의 조회 및 실행 과정
- 뷰 리졸버 (InternalResourceViewResolver)
- 뷰 리졸버의 동작 방식
Comments