📋 Spring MVC - 5. 스프링 MVC
2021-05-03글
스프링 MVC 전체 구조
지금껏 만들었던 프레임 워크 <-> 스프링 MVC
- FrontController - DispatcherServlet
- HandlerMappingMap - HandlerMapping
- MyHandlerAdapter - HandlerAdapter
- ModelView - ModelAndView
- ViewResolver - ViewResolver
- MyView - View
DispatcherServlet
DispacherServlet 도 부모 클래스에서 HttpServlet 을 상속 받아서 사용하고, 서블릿으로 동작한다.
스프링 부트는 DispatcherServlet를 자동으로 등록하면서 모든 경로에 대해서 매핑한다.
요청의 흐름
DispatcherServlet이 호출되면 service()
가 호출된다.
최종적으로는 DispatcherServlet.doDispatch()` 가 호출된다.
doDispatch()
DispatcherServlet의 doDispatch()의 주요 부분들을 살펴보자!
- ModelAndView
- getHandler() : 핸들러를 가져온다.
- 핸들러가 없으면 404로 셋팅을 한다.
- getHandlerAdapter() : 핸들러 어댑터를 가져온다.
- 핸들러 어댑터로 핸들러 호출하고 ModelAndView 반환
- view 렌더링 호출
- ViewResolver를 통해서 view를 찾아 반환
- View 렌더링
스프링 MVC 구조
- 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러 조회
- 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터 조회
- 핸들러 어댑터 실행 : 핸들러 어댑터 실행
- 핸들러 실행 : 핸들러 어댑터가 실제 핸들러 실행
- ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
- viewResolver 호출 : JSP의 경우
InternalResourceViewResolver
가 자동으로 등록되고 사용됨 - view 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고 렌더링 역할을 담당하는 뷰 객체 반환
- JSP의 경우 InternalResourceViewResolver(JstlView) 를 반환하고 내부에 forward() 로직이 있다.
- 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링 한다.
가장 큰 장점은 DispatcherServlet 코드의 변경 없이 원하는 기능을 변경하거나 확장할 수 있다.
대부분 확장 가능하도록 인터페이스로 제공한다.
하지만 사실 우리가 확장할 컨트롤러는 거의 없다.
핸들러 매핑과 핸들러 어댑터
과거 Controller 인터페이스
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
@Component
: ""/springmvc/old-controller: 라는 이름의 스프링 빈으로 등록- 빈의 이름으로URL을 매핑
위 컨트롤러가 호출되는 방식
- HandlerMapping(핸들러 매핑)
- 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 함
ex) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑 필요
- 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 함
- HandlerAdapter(핸들러 어댑터)
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요
ex) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 함
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요
- 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다.
스프링 부트가 자동으로 등록하는 핸들러 매핑과 핸들러 어댑터
- HandlerMapping
- RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용 - 가장 우선순위가 높음
- BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
(위 예제의 @Component)
- HandlerAdapter
- RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용 - 가장 우선순위가 높음
- HttpRequestHandlerAdapter : HttpRequestHandler 처리
- SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
1. 핸들러 매핑으로 핸들러 조회
HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
빈이름으로 핸들러를 찾아야하기 때문에 빈이름으로핸들러를찾아주는 BeanNameUrlHandlerMapping가 실행에 성공하고 핸들러인 OldController 를 반환한다.
2. 핸들러 어댑터 조회
HandlerAdapter 의 supports() 를 순서대로 호출한다. SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
3. 핸들러 어댑터 실행
DispatcherServlet이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.
HttpRequestHandler
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
- 서블릿과 가장 유사한 형태의 핸들러
1. 핸들러 매핑으로 핸들러 조회
HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
빈이름으로 핸들러를 찾아야하기 때문에 빈이름으로 핸들러를찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 MyHttpRequestHandler 를 반환한다.
2. 핸들러 어댑터 조회
HandlerAdapter 의 supports() 를 순서대로 호출한다.
HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
3. 핸들러 어댑터 실행
DispatcherServlet이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.
뷰 리졸버
application-properties
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
스프링 부트는 InternalResourceViewResolver
라는 뷰 리졸버를 자동으로 등록한다.
이때 application.properties
에 등록한 spring.mvc.view.prefix
, spring.mvc.view.suffix
설정 정보를 사용해서 등록한다.
스프링 부트가 자동으로 등록하는 뷰 리졸버
- BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환
- InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환
1. 핸들러 어댑터 호출
핸들러 어댑터를 통해 논리 뷰 이름을 획득
2. ViewResolver 호출
new-form 이라는 뷰 이름으로 viewResolver를 순서대로 호출
InternalResourceViewResolver 가 호출됨
3. InternalResourceViewResolver
내부에서 자원을 찾을 수 있음을 의미한다.
이 뷰 리졸버는 InternalResourceView 를 반환
4. 뷰 - InternalResourceView
InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용
5. view.render()
view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다
Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야 한다.
최근에는 라이브러리만 추가하면 스프링 부트가 이런 작업도 모두 자동화해준다.