본문 바로가기
Develop/Spring+JPA

[스프링 MVC] 스프링 MVC 구조, DispatcherServlet

by 연로그 2021. 5. 24.
반응형

목차

1. 기존 프로젝트의 구조

2. 스프링 MVC 구조

3. DispatcherServlet


 


1. 기존 프로젝트 구조

여태껏 만든 프로젝트의 구조를 한번 정리해보자.

( 프로젝트는 https://github.com/yeon-06/inflearnSpring/tree/master/mvc1 참조)

기존 프로젝트 구조

  1. 핸들러 조회: Handler 매핑을 통해 요청 url에 매핑된 핸들러(컨트롤러) 조회
  2. 핸들러 어댑터 조회: 핸들러를 처리할 수 있는 핸들러 어댑터 조회
  3. 핸들러 어댑터 실행: handler(handler)
  4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러 실행
  5. ModelView 반환: 핸들러 어댑터가 핸들러의 반환 정보를 ModelView 형태로 변환해서 반환
  6. viewResolver 호출
  7. MyView 반환: viewResolver가 view의 논리 이름 -> 물리 이름으로 변환 후 렌더링 역할을 하는 MyView 객체 반환
  8. MyView 렌더링: render(model)

 

코드와 함께 자세히 보기 ▼

더보기

1. 핸들러 조회

(예시 데이터: /front-controller/v5/v4/members/save )

@WebServlet(name = "frontControllerServletV5", urlPatterns="/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet { ... }

- urlPatterns으로 /front-controller/v5/* 요청들은 FrontControllerServletV5로 들어오게 됨

 

2. 핸들러를 처리할 수 있는 핸들러 어댑터 조회

@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
	Object handler = getHandler(req);
		
	if(handler == null) {
		res.setStatus(HttpServletResponse.SC_NOT_FOUND);	//404
		return;
	}
		
	MyHandlerAdapter adapter = getHandlerAdapter(handler);
	ModelView mv = adapter.handle(req, res, handler);
		
	String viewName = mv.getViewName();
	MyView view = viewResolver(viewName);
	view.render(mv.getModel(), req, res);
}

private Object getHandler(HttpServletRequest req) {
	String reqURI = req.getRequestURI();
	return handlerMappingMap.get(reqURI);
}

- FrontControllerServletV5의 service에서 getHandler(req) 실행

- handlerMappingMap에 있는 {"/front-controller/v5/v4/members/save", new MemberSaveControllerV4()} 데이터를 통해 MemberSaveControllerV4 가져옴

 

3. handler(handler)

@Override
public ModelView handle(HttpServletRequest req, HttpServletResponse res, Object handler) throws ServletException, IOException {
	ControllerV4 controller = (ControllerV4) handler;
		
	Map<String, String> paramMap = createParamMap(req);
	Map<String, Object> model = new HashMap<>();
		
	String viewName = controller.process(paramMap, model);
	ModelView mv = new ModelView(viewName);
	mv.setModel(model);
	
	return mv;
}

- 2에서 찾은 핸들러를 ControllerV4HandlerAdapter의 handler() 함수에 파라미터로 넣고 실행

- ControllerV4 controller에는 MemberSaveControllerV4가 들어가게 됨


4. 핸들러 호출

@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
	String username = paramMap.get("username");
	int age = Integer.parseInt(paramMap.get("age"));
		
	Member member = new Member(username, age);
	memberRepository.save(member);
	model.put("member",member);
		
	return "save-result";
}

- 3의 ControllerV4 controller 이용

- controller에 저장된 MemberListControllerV4의 process 함수 실행


5. ModelView 반환

@Override
public ModelView handle(HttpServletRequest req, HttpServletResponse res, Object handler) throws ServletException, IOException {
	...	
	String viewName = controller.process(paramMap, model);
	ModelView mv = new ModelView(viewName);
	mv.setModel(model);
	
	return mv;
}

- ControllerV4HandlerAdapter에서 3의 결과를 통해 viewName을 받아옴  -> "save-result"

- 이를 이용해 ModelView 생성하고 반환


6. viewResolver 호출

@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
	...
	ModelView mv = adapter.handle(req, res, handler);
		
	String viewName = mv.getViewName();
	MyView view = viewResolver(viewName);
	view.render(mv.getModel(), req, res);
}

private MyView viewResolver(String viewName) {
	return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}

- FrontControllerServletV5에 ModelView가 저장됨

- ModelView에서 "save-result"(viewName)을 가져오고 이를 이용해 viewResolver를 호출


7. MyView 반환

- viewResolver에서 경로를 새로 정의해 MyView를 반환 -> viewName이 "/WEB-INF/view/save-result.jsp"인 객체


8. render(model) 호출

public void render(Map<String, Object> model, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
	modelToRequestAttribute(model, req);
	RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
	dispatcher.forward(req, res);
}

private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest req) {
	model.forEach((key,value)-> req.setAttribute(key, value));
}

- MyView의 render() 실행

- model의 key, value를 HttpServletRequest의 key, value에 저장

- RequestDispatcher 객체 생성 시 이동할 페이지 지정 -> "/WEB-INF/view/save-result.jsp"

- forward()를 통해 req, res를 넘겨주며 페이지 이동

 


2. 스프링 MVC 구조

스프링 MVC 구조

  1. 핸들러 조회: Handler 매핑을 통해 요청 url에 매핑된 핸들러(컨트롤러) 조회
  2. 핸들러 어댑터 조회: 핸들러를 처리할 수 있는 핸들러 어댑터 조회
  3. 핸들러 어댑터 실행
  4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러 실행
  5. ModelAndView 반환: 핸들러 어댑터가 핸들러의 반환 정보를 ModelAndView 형태로 변환해서 반환
  6. viewResolver 호출: jsp의 경우 InternalResourceviewResolver가 자동 등록 및 사용됨
  7. View 반환: viewResolver가 view의 논리 이름 -> 물리 이름으로 변환 후 렌더링 역할을 하는 View 객체 반환
  8. View 렌더링: View를 이용해 렌더링

 

거의 동일하다!!!

약간의 용어들만이 바뀌었을 뿐이다

직접 만든 MVC   스프링 MVC
FrontController DispatcherServlet
handlerMappingMap HandlerMapping
MyHandlerAdapter HandlerAdapter
ModelVeiw ModelAndView
viewResolver ViewResolver
MyView View

 

이제 Front Controller인 DispatcherServlet에 대해 자세히 살펴보자

 


DispatcherServlet

 

  • 요청을 처리하고 적절한 응답을 렌더링하기 위해 Special Bean
  • DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet 상속받고 있음
  • Spring boot에서는 DispatcherServlet을 자동으로 urlPattern="/"으로 등록

 

urlPattern="/"으로 등록해도 문제되지 않는 이유 ▼

더보기

urlPattern을 전체로 등록하면 이런 의문이 생길수도 있다.

어떤 경로로 접근해도 DispatcherServlet으로 들어가는건 아닐까?

하지만 경로에도 우선 순위가 존재하기 때문에 괜찮다.

더 자세한 경로일수록 우선 순위가 더 높다.

 

A 서블릿을 urlPattern="/hello/*"

B 서블릿을 urlPattern="/hello/test/*"라고 등록했다고 가정하자.

만약 "/hello/test/test2" 경로로 접근한다면 B 서블릿의 경로가 더 구체적이니 B 서블릿으로 접근하게 된다.

 

요청의 흐름

1. 서블릿 호출 시 HttpServlet이 제공하는 service() 호출

 

2. FrameworkServlet에서 오버라이드한 service() 호출

  @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
	doDispatch(request, response);
        ...
    }

 

3. 2가 실행되며 DispatcherServlet.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);
}

 


본 게시글은 김영한 님의 '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 강의를 구매 후 정리하기 위한 포스팅입니다.

내용을 임의로 추가, 수정, 삭제한 부분이 많으며 정확한 이해를 위해서 강의를 구매하시는 것을 추천 드립니다.

 

inf.run/B756

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com

https://docs.spring.io/spring-framework/docs/5.1.9.RELEASE/spring-framework-reference/web.html#mvc

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more com

docs.spring.io

 

반응형