일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 2024 정보처리기사 수제비 실기
- 스프링 mvc1 - 서블릿
- @Aspect
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch6
- 2024 정보처리기사 시나공 필기
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch3
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch11
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch7
- 스프링 db1 - 스프링과 문제 해결
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch1
- 스프링 고급 - 스프링 aop
- 스프링 mvc1 - 스프링 mvc
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch9
- 게시글 목록 api
- 자바의 정석 기초편 ch12
- 스프링 입문(무료)
- 스프링 mvc2 - 타임리프
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch14
- Today
- Total
나구리의 개발공부기록
스프링 MVC - 구조이해, 스프링 MVC 전체 구조, 핸들러 매핑과 핸들러 어댑터, 뷰 리졸버 본문
스프링 MVC - 구조이해, 스프링 MVC 전체 구조, 핸들러 매핑과 핸들러 어댑터, 뷰 리졸버
소소한나구리 2024. 2. 29. 18:50 출처 : 인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 스프링 MVC 전체 구조
1) 스프링 MVC
(1) 직접 작성한 MVC 프레임워크 구조와 스프링 MVC 구조
- 구조 둘의 구조가 동일함
(2) 직접만든 MVC 프레임워크 -> 스프링 MVC 1:1 비교
- FrontController -> DispatcherServlet
- handlerMappingMap -> HandlerMapping(인터페이스): 핸들러 매핑
- MyHandlerAdapter -> HandlerAdapter(인터페이스): 핸들러 어댑터
- ModelView -> ModelAndView
- viewResolver -> ViewResolver(인터페이스): 뷰 리졸버
- MyView -> View(인터페이스): 뷰
- 굵은 표시는 주요 인터페이스 목록
(2) 동작 순서 설명
- 핸들러 조회 : 핸들러 매핑을 통해 URL에 매핑된 핸들러(컨트롤러)를 조회
- 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회
- 핸들러 어댑터 실행 : 핸들러 어댑터를 실행
- 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행
- ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
- viewResolver 호출 : 뷰 리졸버를 찾고 실행, JSP의 경우 InternalResourceViewResolver가 자동 등록되고 사용 됨
- View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고 렌더링 역할을 담당하는 뷰 객체를 반환
JSP의 경우 InternalResourceView(JstlView)를 반환 -> 내부에 forward()로직이 있음 - 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링
(3) 인터페이스 살펴보기
- 스프링 MVC의 큰 강점은 DispatcherServlet 코드의 변경 없이 원하는 기능을 변경하거나 확장이 가능하도록 인터페이스들을 제공함
- 해당 인터페이스들을 구현해서 DispatcherServlet에 등록하면 자신만의 컨트롤러를 만들 수도 있음
2) DispatcherServlet 구조 살펴보기
- 스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어있으며 스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿(DispatcherServlet)임
- 이 디스패처 서블릿이 스프링 MVC의 핵심
(1) DispatcherServlet 등록
- 부모클래스에서 HttpServlet을 상속받아서 사용하며 서블릿으로 동작
- DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
- 스프링 부트는 DispatcherServlet을 서블릿으로 자동으로 등록하면서 모든경로(urlPatterns = "/")에 대해서 매핑함
- 참고로 더 자세한 경로가 우선순위가 높으므로 기존에 등록한 서블릿도 함께 동작함
(2) 요청 흐름
- 서블릿 호출 -> HttpServlet이 제공하는 service()가 호출 됨
- 스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service를 오버라이딩 해두었음
- FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch()가 호출 됨
(3) DispatcherServlet의 핵심 메서드인 doDispatch() 코드 분석
- 너무 구현 코드가 많으므로 예외처리, 인터셉터 기능은 제외하고 분석
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 핸들러 조회
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 기타 파라미터들...) throws Exception{
// 뷰 렌더링 호출
this.render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
(4) 정리
- 스프링 MVC는 코드 분량도 매우 많고 복잡해서 내부구조를 파악하는 것이 쉽지 않음
- 이미 전세계의 수많은 개발자들이 요구사항에 맞추어 기능을 확장해왔기 때문에 대부분의 기능은 이미 구현이 되어있어서 기능을 직접 확장하거나 나만의 컨트롤러를 만드는일은 없으므로 만들어진 기능을 잘 활용하는 것에 중점을 두면 됨
- 핵심 동작방식을 알아두는 이유는 향후 문제가 발생했을 때 어떤 부분에서 문제가 발생했는지 파악하고 해결할 때 쉽게 접근이 가능하고 만약 확장포인트가 필요하다면 어떤 부분을 확장해야 할지 감을 잡기 수월하기 때문임
2. 핸들러 매핑과 핸들러 어댑터
1) 핸들러 매핑과 해들러 어댑터 이해
(1) Controller 인터페이스 - 과거버전 스프링 컨트롤러
- 지금은 전혀 사용하지 않지만 과거에는 주로 사용하였으며 스프링도 처음에는 이런 딱딱한 형식의 컨트롤러를 제공하였음
- 참고로 @Controller(애노테이션)과는 전혀 다름
package org.springframework.web.servlet.mvc;
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
(2) OldController
- web패키지 하위에 springmvc.old 패키지를 생성하여 Controller 인터페이스를 구현하는 클래스를 작성
- @Component: 이 컨트롤러는 /springmvc/old-controller라는 이름의 스프링 빈으로 등록되며 빈의 이름으로 URL을 매핑함
- 작성 후 애플리케이션을 실행하여 localhost:8080/springmvc/old-controller에 접속하여 로그에 해당 출력문이 출력되면 성공임
package hello.servlet.web.springmvc.old;
@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;
}
}
(3) 해당 컨트롤러가 호출될 때 필요한 항목
- HandlerMapping(핸들러 매핑) : 핸들러 매핑에서 해당 컨트롤러를 찾을 수 있어야 함
예를 들어 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요함 - HandlerAdapter(핸들러 어댑터) : 핸들러 매핑을 통해 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요함
예를 들어 Controller 인터페이스를 실행 할 수 있는 핸들러 어댑터를 찾고 실행 해야 함
(4-1) 스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터(실제로는 더 많지만 중요한 부분만 작성)
- 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터 대부분을 구현해두었기 때문에 개발자가 직접 만드는 일은 거의 없음
- 실제로는 더 많지만 중요한 부분 위주로 설명하기위해 일부 생략
- 핸들러 매핑, 핸들러 어댑터 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어감
(4-2) HandlerMapping
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾음
(4-3) HandlerAdapter
0 = RequestMappingHandlerAdapter: 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
(5-1) 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서 핸들러를 찾음
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아 주는 BeanNameUrlHandlerMapping이 실행되고 핸들러인 OldController를 반환
(5-2) 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출
- SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 됨
(5-3) 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨줌
- SimpleControllerHandlerAdapter는 핸들러인 OldController를 내부에서 실행하고 그 결과를 반환
(6) 정리 - OldController 매핑, 어댑터
- OldController를 실행하며 사용 된 객체는 아래와 같음
HandlerMapping = BeanNameUrlHandlerMapping
HandlerAdapter = SimpleControllerHandlerAdapter
2) HttpRequestHandler
(1) HttpRequestHandler 인터페이스
- Controller인터페이스가 아닌 다른 핸들러이며 서블릿과 가장 유사한 형태의 핸들러임(컨트롤러)
package org.springframework.web;
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
(2) MyHttpRequestHandler구현
- 작성 후 localhost:8080/springmvc/request-handler로 접속하여 출력문이 나오면 정상임
- OldController(Controller 인터페이스)와 동일하지만 빈 이름으로 핸들러를 찾으면 MyHttpRequestHandler가 반환됨
package hello.servlet.web.springmvc.old;
@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");
}
}
(3-1) 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서 핸들러를 찾음
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아 주는 BeanNameUrlHandlerMapping이 실행되고 핸들러인 MyHttpRequestHandler를 반환
(3-2) 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출함
- HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스를 지원하므로 대상이 되어 실행이 됨
(3-3) 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨줌
- 핸들러인 MyHttpRequestHandler를 내부에서 실행하고 그 결과를 반환함
(4) 정리 - MyHttpRequestHandler 핸들러 매핑, 어댑터
- MyHttpRequestHandler를 실행하며 사용 된 객체
HandlerMapping = BeanNameUrlHandlerMapping
HandlerAdapter = HttpRequestHandlerAdapter
(5) @RequestMapping
- 뒤에서 더 설명이 나오지만 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping과 RequestMappingHandlerAdapter임
- @RequestMapping의 앞글자를 따져 만들어졌는데 이것이 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터이며 실무에서 99.9% 이 방식의 컨트롤러를 사용함 (MVC 시작하기에서 자세히 다룸)
3. 뷰 리졸버
1) 뷰 리졸버 이해
(1) OldController 수정 - View 조회를 할 수 있도록 변경
- View를 사용할 수 있도록 return new ModelAndView("/new-form")을 추가
- 애플리케이션을 실행 후 localhost:8080/springmvc/old-controller에 접속해보면 웹브라우저는 에러페이지가 출력되지만 콘솔에는 정상적으로 출력문이 확인됨
- 즉, 컨트롤러는 정상 호출되지만 Whitelabel Error Page 오류가 발생하는데, 뷰 리졸버 기능이 필요함
package hello.servlet.web.springmvc.old;
@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"); // 추가
}
}
(2) application.properties 수정
- 아래의 설정 추가 후 다시 애플리케이션을 실행하고 해당 경로로 들어가보면 등록 폼이 정상적으로 출력되는 것을 확인할 수 있음
- 물론 저장 기능을 개발하지 않았으므로 현재는 폼만 출력되고 더 진행하면 오류가 발생함
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
(3) 뷰 리졸버 - InternalResourceViewResolver
- 스프링 부트는 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록함
- 이때 application.properties에 등록한 spring.mvc.view.prefix와 spring.mvc.view.suffix 설정 정보를 사용해서 등록함
** 참고
- 권장하지는 않지만 설정 없이 OldController의 반환값인 ModelAndView에 전체경로를 주어도 동작하기는 함
- return new ModelAndView("/WEB-INF/views/new-form.jsp");
2) 뷰 리졸버 동작 방식
(1) 스프링 부트가 자동으로 등록하는 뷰 리졸버(실제로는 더 많음)
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
(2) 핸들러 어댑터 호출
- 핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름을 획득
(3) View Resolver 호출
- 획득한 논리뷰 이름으로 viewResolver를 순서대로 호출
- BeanNameViewResolver는 new-form이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야하는데 없음
- InternalResourceViewResolver가 호출됨
(4) InternalResourceViewResolver
- InternalResourceView를 반환
(5) 뷰 - InternalResourceView
- InternalResourceView는 JSP처럼 forward()를 호출해서 처리할 수 있는 경우에 사용함
(6) view.render()
- view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행
** 참고
- 만약 JSTL 라이브러리가 있으면 InternalResourceView를 상속받은 JstlView를 반환하는데 JstlView는 JSTL태그 사용시 약간의 부가 기능이 추가 된 것임
- 다른 뷰 템플릿은 실제 뷰를 렌더링(forward() 과정 없이 바로 렌더링) 하지만 JSP의 경우 forward()를 통해서 해당 JSP로 이동(실행)해야 렌더링이 됨
- Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver를 등록해야하는데 최근에는 라이브러리만 추가하면 스프링 부트가 모두 자동으로 작업해줌
'인프런 - 스프링 완전정복 코스 로드맵 > 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
스프링 MVC - 기본기능, 프로젝트 생성, 로깅(간단히알아보기), 요청매핑, 요청매핑 - API 예시 (0) | 2024.03.01 |
---|---|
스프링 MVC - 구조이해, 스프링 MVC (시작하기/컨트롤러 통합하기/실용적인 방식) (0) | 2024.02.29 |
MVC 프레임워크 만들기, 단순하고 실용적인 컨트롤러 - v4, 유연한 컨트롤러 - v5 (1) | 2024.02.28 |
MVC 프레임워크 만들기, 프론트 컨트롤러 패턴 소개, 프론트 컨트롤러 도입 - v1, View 분리 - v2, Model 추가 - v3 (1) | 2024.02.24 |
서블릿,JSP,MVC패턴, MVC패턴(개요/적용/한계) (1) | 2024.02.24 |