1. 스프링 MVC 구조

DispatcherServlet 구조
-
스프링 MVC도 프론트 컨트롤러 패턴으로 구현
-
스프링 MVC의 프론트 컨트롤러가 바로 디스패터 서블릿 - 스프링 MVC의 핵심
-
DispacherServlet 서블릿 등록
- DispacherServlet 도 부모 클래스에서 HttpServlet을 상속 받아 사용 및 서블릿으로 동작
- 상속 관계 : DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
- 스프링 부트는 DispacherServlet을 서블릿으로 자동으로 등록하면 모든 경로(urlPattern = "/") 매핑
- 더 자세한 경로가 우선순위가 높음, 그래서 기존에 등록한 서블릿도 함께 등록
-
요청 흐름
- 서블릿이 호출되면 HttpServlet이 제공하는 service() 호출
- 스프링 MVC는 DispatcherServlet 의 부모인 FreamworkServlet에서 service()를 오버라이드 함
- FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch() 호출
- DispacherServlet.doDispatch() 코드 - 예외처리, 인터셉터 제외
-
DispacherServlet.doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
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);
}
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. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}
-
SpringMVC 구조

- 동작 순서
- 1) 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회
- 2) 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회
- 3) 핸들러 어댑터 실행 : 핸들러 어댑터를 실행
- 4) 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행
- 5) ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
- 6) viewResolver 호출 : 뷰 리졸버를 찾고 실행
(JSP는 InternalResourceViewResolver가 자동 등록 및 사용)
- 7) View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환
(JSP의 경우 InternalResourceView(JstlView))를 반환하는데, 내부에 forward() 로직이 있음
- 8) 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 함
2. 핸들러 매핑과 핸들러 어댑터
Controller 인터페이스(과거에 사용한 스프링 컨트롤러)
구현
- OldController
src > main > java > hello > servlet > web > springmvc > old 패키지

- @Component : /springmvc/old-controller 이름의 스프링 빈 등록 (빈의 이름으로 URL 매핑)
📣 해당 컨트롤러가 호출되는 과정

- 컨트롤러가 호출 되기위한 2가지 과정
- HandlerMapping(핸들러 매핑)
- 핸들러 매핑에서 해당 컨트로럴를 찾을수 있어야함
(스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑 필요)
- HandlerAdapter(핸들러 어댑터)
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터 필요
(Controller 인터페이스를 실행 할수 있는 핸들러 어댑터를 찾고 실행)
📣 스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
- HandlerMapping
- 0 순위 = RequestMappingHandlerMapping
- 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
- @Controller, @RequestMapping이 있는 것을 인식해서 찾음
- 1 순위 = BeanNameUrlHandlerMapping
- 스프링 빈의 이름으로 핸들러를 찾음 (요청 URL 경로와 똑같은 이름(/springmvc/old-controller)의 스프링 빈을 찾음)
- HandlerAdapter(핸들러 매핑을 통해서 핸들러를 찾게되면 핸들러 어댑터가 작동)
- 0순위 = RequestMappingHandlerAdapter
- 애노테이션 기반의 컨트로럴인 @RequestMapping에서 사용
- 1순위 = HttpRequestHandlerAdapter
- 2순위 = SimpleControllerHandlerAdapter
- Controller 인터페이스 (애노테이션X, 과거 사용) 처리
✔️ HttpRequestHandler
- 핸들러 매핑과, 어댑터를 더 잘 이해하기 위해 Controller인터페이스가 아닌 다른 핸들러를 확인
HttpRequestHandler 핸들러(컨트롤러)는 서블릿과 가장 유사한 형태의 핸들러
(리턴타입이 void, 메서드 내부에서 모든 것을 처리)
💻 구현
- MyHttpRequestHandler: src > main > java > hello > servlet > web > springmvc > old


3. 뷰 리졸버
OldController에서 View 적용
- OldController

❌ 웹테스트 결과 Whitelabel Error Page 오류 발생
- application.properties 코드 추가
✔️ 뷰 리졸버 - InternalResourceViewResolver
- 스프링부트에서는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록
application.properties에 등록한 spring.mvc.view.prefix, spring.mvc.view.suffix 설정 정보를 사용해서 등록
✔️ 뷰 리졸버 동작 방식
- 스프링 MVC 구조

- 스프링 부트가 자동 등록하는 뷰 리졸버
- BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환
- InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환
1) 핸들러 어댑터 호출
- 핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름을 획득
2) ViewResolver 호출
- new-form 이라는 뷰이름으로 viewResolver 리스트를 순서대로 호출
- BeanNameViewResolver는 new-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾음 없으면!
- InternalResourceViewResolver가 로출
3) InternalResourceViewResolver
- InternalResourceView를 반환
4) view - InternalResourceView
- InternalResourceView는 JSP처럼 포워드 forward()를 호출해서 처리할 수 있는 경우에 사용
5) view.render()
- view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행
📣 참고
- InternalResourceViewResolver는 만약 JSTL 라이브러리가 있으면 InternalResourceView를 상속받은 JstlView를 반환
JstlView는 JSTL 태그 사용시 약간의 부가 기능이 추가
- 다른 뷰는 실제 뷰를 렌더링하지만, JSP의 경우 forward() 통해서 해당 JSP로 이동(실행) 해야 렌더링 가능, JSP를 제외한 나머지 뷰 템플릿은
forward() 과정 없이 바로 렌더링 가능
- Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver를 등록, 최근에는 라이브러리만 추가하면 스프링 부트가 자동화해줌
4. 스프링 MVC 시작
- handlerMapping = RequestMappingHandlerMapping
handlerAdapter = RequestMappingHandlerAdapter
- 현재 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터
@RequestMapping 기반의 스프링 MVC 컨트롤러 변경
- SpringMemberFormControllerV1
src > main > java > hello > servlet > web > springmvc > v1


- @Controller
- 스프링이 자동으로 스프링 비능로 등록(내부에 @Component 애노테이션이 있어서 컴포넌트 스캔 대상)
- @RequestMapping
- 요청 정보 매핑, 해당 URL이 호출되면 메서드가 호출
- ModelAndView
RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어있는 경우에 매핑 정보로 인식
✔️ 동작 코드
- 1) 컴포넌트 스캔으로 자동 스프링 빈으로 등록하고, 클래스 레벨에 @RequestMapping을 적용
- 클래스 레벨에 @RequestMapping이 붙어있으면, RequestMapppingHandlerMapping이 해당 클래스를 찾음
- 2) 자바 코드로 직접 스프링 빈으로 등록, 클래스 레벨에 @RequestMapping 적용
📌 참고
- 스프링 부트 3.0 이상 부터는 클래스 레벨에 @RequestMapping이 있엉도 스프링 컨트롤러로 인식 못함, @Controller가 있어야 스프링 컨트롤러로
인식,
- SpringMemberSaveControllerV2
src > main > java > hello > servlet > web > springmvc > v2

- mv.addObject("member", member) : 스프링이 제공하는 ModelAndView를 통해 Model 데이터를 추가할 때는 addObject()를 사용, 해당 데이터는 뷰를 렌더링 할 때 사용
5. 스프링 MVC - 컨트롤러 통합
- SpringMemberControllerV2
src > main > java > hello > servlet > web > springmvc > v2

6. 스프링 MVC - 실용적인 방식
- SpringMemberControllerV3
src > main > java > hello > servlet > web > springmvc > v3

- 이것을 @GetMapping, @PostMapping으로 더 편리하게 사용
