스프링 MVC
전체 구조
- 클라이언트로부터 HTTP 요청
- 디스패처 서블릿이 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러) 조회
- 찾은 핸들러를 처리할 수 있는 핸들러 어댑터 실행
- 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
- 뷰 리졸버를 실행하면 뷰의 논리 이름을 물리 이름으로 바꿔서 뷰를 찾고 반환
- 뷰를 통해서 뷰 렌더링
디스패처 서블릿
- 서블릿으로 동작하는 스프링 MVC의 프론트 컨트롤러로, 부모 클래스에서 HttpServlet을 상속받아서 사용한다.
- 부모 클래스인 FrameworkSerlvet에서 doService()를 오버라이딩 해두고, doServie()를 시작으로 여러 메서드들과 함께 doDispatch()가 호출된다.
- doDispatch() 메서드는 핸들러 조회, 핸들러 어댑터 조회/실행, 뷰 리졸버를 통해서 뷰 찾기, 뷰 렌더링 등의 핵심 기능을 수행한다.
더보기
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
핸들러 매핑과 핸들러 어댑터
- 컨트롤러가 호출되려면 핸들러 매핑에서 해당 컨트롤러를 찾고, 해당 컨트롤러를 실행할 수 있는 핸들러 어댑터를 찾아서 실행해야 한다.
- 스프링은 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해 놓았다.
- 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping과 RequestMappingHandlerAdapter로, 스프링에서 주로 사용하는 어노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터
자주 쓰는 핸들러 매핑 목록
RequestMappingHandlerMapping | 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용한다. |
BeanNameUrlHandlerMapping | 스프링 빈의 이름으로 컨트롤러를 찾는다. |
자주 쓰는 핸들러 어댑터 목록
RequestMappingHandlerAdapter | 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용한다. |
HttpRequestHandlerAdapter | HttpRequestHandler 처리 |
뷰 리졸버
- 핸들러 어댑터를 통해 논리 뷰 이름을 획득하면 뷰 리졸버를 호출하고, 뷰 리졸버는 뷰를 찾아서 반환한다.
- 다른 뷰는 실제 뷰를 렌더링하지만, JSP의 경우 forward()를 통해서 해당 JSP로 이동(실행)해야 렌더링된다.
- JSP를 제외한 나머지 뷰 템플릿은 forward() 과정없이 바로 렌더링
- Tymeleaf 뷰 템플릿을 사용하면 TymeleafViewResolver를 등록해야 하지만, 최근에는 라이브러리만 추가하면 스프링 부트가 해당 작업을 자동 수행해준다.
자주 쓰는 뷰 리졸버 목록
BeanNameViewResolver | 빈 이름으로 뷰를 찾아서 반환 |
InternalResourceViewResolver | JSP를 처리할 수 있는 뷰를 반환 |
뷰 템플릿
- 뷰 라이브러리들은 어떤 웹 프레임워크와도 사용 가능하도록 설계되어 스프링의 추상화 모델을 알지 못하기 때문에, 컨트롤러가 데이터를 넣는 Model 대신 서블릿 요청 속성들을 사용한다.
- 따라서 뷰에게 요청을 전달하기 앞서 스프링은 뷰 템플릿이 사용하는 요청 속성에 모델 데이터를 복사한다.
- Thymeleat 템플릿은 요청 데이터를 나타내는 요소 속성을 추가로 갖는 HTML
- 스프링 부트 애플리케이션의 정적 콘텐츠는 루트 밑에 있는 /static 디렉터리에 위치한다.
- 스프링은 유연성이 좋아서 다양한 뷰 템플릿을 지원하므로, 원하는 뷰 템플릿을 선택하고 의존성으로 추가하면 스프링 부트가 템플릿 라이브러리를 찾아서 스프링 MVC 컨트롤러의 뷰로 사용할 컴포넌트를 자동 구성한다.
FreeMarker | spring-boot-starter-freemarker |
Groovy 템플릿 | spring-boot-starter-groovy-templates |
JSP (Java Server Page) | 없음 (Tomcat이나 Jetty 서블릿 컨테이너 자체에서 제공) |
Mustache | spring-boot-starter-mustache |
Tymeleaf | spring-boot-starter-thymeleaf |
- JSP는 서블릿 컨테이너가 JSP 명세를 구현하므로, 스프링 부트의 스타터로 지정할 필요가 없다.
- 내장된 톰캣과 제티 컨테이너를 포함해서 자바 서블릿 컨테이너는 /WEB-INF 밑에서 JSP 코드를 찾는다.
- 만약 애플리케이션을 실행 가능한 JAR 파일로 생성한다면 이러한 요구사항을 충족시킬 수 없다.
- 애플리케이션을 WAR 파일로 생성하고 서블릿 컨테이너를 설치하는 경우에는 JSP를 선택해야 한다.
'Spring > Spring MVC' 카테고리의 다른 글
뷰 컨트롤러 (0) | 2022.03.27 |
---|---|
컨트롤러 요청과 응답 (0) | 2022.03.05 |
서블릿, JSP, MVC 패턴 (0) | 2022.03.03 |
@Valid 유효성 검사 (0) | 2022.01.26 |