loop-study
개발 공부할래?
loop-study
전체 방문자
오늘
어제
  • 분류 전체보기 (186)
    • 목표 및 회고 (25)
    • 세미나 & 워크샵 (1)
    • 교육 및 인강 (67)
      • TDD, Clean Code with Java (5)
      • ATDD, 클린 코드 with Spring (6)
      • DDD 세레나데 (3)
      • 인프라 공방 (6)
      • 이규원의 현실 세상의 TDD (19)
      • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (18)
      • 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (0)
      • 모든 개발자를 위한 HTTP 웹 기본 지식 - 김영한 (8)
      • 코딩으로 학습하는 GoF의 디자인 패턴 (1)
      • 스프링 시큐리티 완전정복 6.x (1)
    • 서적 (62)
      • 객체지향의 사실과 오해 (1)
      • 객체지향과 디자인패턴 (7)
      • 만들면서 배우는 클린 아키텍처 (3)
      • 테스트 주도 개발로 배우는 객체 지향 설계와 실천 (1)
      • 오브젝트: 코드로 이해하는 객체지향 설계 (17)
      • 리팩토링 : 코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기 (0)
      • 토비의 스프링 (3)
      • 엔터프라이즈 애플리케이션 아키텍처 패턴 (9)
      • 개발자의 글쓰기 (1)
      • 소프트웨어 장인 (17)
      • Real MySQL 8.0 (2)
      • JVM 밑바닥까지 파헤치기 (0)
    • 개발 & 방법론 (29)
      • Java (13)
      • TDD (5)
      • ATDD (3)
      • DDD (6)
      • 인프라 (2)
      • SQL (0)
    • 개인이야기 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 백엔드 로드맵

인기 글

태그

  • nextstep
  • fastcampus
  • 김영한
  • 장인정신
  • 이규원
  • ATDD
  • java
  • 객체지향
  • 백기선
  • Patterns of Enterprise Application Architecture
  • DDD 세레나데
  • 스프링
  • 인프런
  • 마틴 파울러
  • Test Driven Development
  • 넥스트스탭
  • 소프트웨어 장인
  • study
  • 인프라공방
  • 테스트 주도 개발
  • 추상화
  • TDD
  • 모든 개발자를 위한 HTTP 웹 기본 지식
  • 현실세상의 TDD
  • JUnit
  • 오브젝트
  • 조영호
  • 엔터프라이즈 애플리케이션 아키텍처 패턴
  • 자바
  • 스터디

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
loop-study

개발 공부할래?

스프링 MVC 이해 - 전체 구조, 핸들러 매핑과 핸들러 어댑터, 뷰 리졸버
교육 및 인강/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

스프링 MVC 이해 - 전체 구조, 핸들러 매핑과 핸들러 어댑터, 뷰 리졸버

2021. 7. 13. 20:38

인프런 김영한 님의 스프링 강의이며, 섹션 5 - 스프링 MVC 구조 이해를 정리한다.

자세한 설명이 궁금하면 수강을 권장한다.

 

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

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

www.inflearn.com


스프링 MVC 전체 구조

이전까지 만들었던 프레임워크 구조는 아래와 같다.

 

스프링 MVC 구조는 다음과 같다.

지금까지 만들고 개선했던 게 스프링 MVC 구조와 거의 흡사하다.

직접 만든 프레임워크 스프링 MVC
FrontController DispatcherServlet
handlerMappingMap HandlerMapping
MyHandlerAdapter HandlerAdapter
ModelVIew ModelAndView
viewResolver ViewResolver
MyView View

 

DispatcherServlet 구조 알아보기

org.springframework.web.servlet.DispatcherServlet; 를 찾아가면 엄청난 코드들이 존재한다.

코드가 1500줄이 있다.

간단히 요약하자면 스프링 MVC도 프론트 컨트롤러 패턴 구조로 되어있다.

스프링 MVC에서 프론트컨트롤러가 디스패쳐서블릿(DispatcherServlet)이고, 이게 바로 스프링MVC 핵심이다.

디스패쳐서블릿도 HttpServlet을 상속받아 사용하고 서블릿으로 동작한다.

    - DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet

스프링부트는 디스패쳐서블릿을 자동으로 등록하면 urlPattern="/" 매핑한다. 자세한 경로에 우선순위가 높아 기존 서블릿도 함께 동작한다.

 

요청 흐름

서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.

스프링 MVC는 디스패쳐의 부모인 FrameworkServlet에서 service()를 오버라이드 했다. 

FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch()가 호출된다.

1020 라인에 있다.

코드를 보면 내용이 어렵지만 핵심로직을 구분하면 다음과 같다.

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);

그리고 마지막에 실행되는 processDispatchResult의 로직이다.

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. 뷰 리졸버를 통해서 뷰 찾기, 6. View 반환
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    // 8. 뷰 렌더링
    view.render(mv.getModelInternal(), request, response); 
}

위의 로직으로 스프링 MVC의 동작 순서를 정리한 이미지 (맨 처음 언급한 이미지다)

 

 

직접 살펴본 스프링 MVC의 구조는 코드도 많고 복잡해서 파악하기 힘들지만

핵심 동작방식을 알아두면 문제를 발생했을 때 쉽게 파악하고 해결할 수 있다.

또한 추가확장이 필요할 때 어떤 부분을 확장할지 감을 잡을 수 있다.

 


핸들러 매핑과 핸들러 어댑터

현재는 사용하지 않지만 과거에 사용했던 핸드러 매핑과 핸들러 어댑터를 알아보자. (어노테이션 기반이 나오면서 세대교체됨)

@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;
    }
}

이 옛날 방식의 컨트롤러가 호출되려면 2가지가 필요하다

1. HandlerMapping(핸들러 매핑)

- 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야한다

- 스프링 빈의 이름으로 핸들러를 찾아야한다

 

2. HandlerAdapter(핸들러 어댑터)

- 찾은 핸들러를 실행할 핸들러 어댑터가 필요

- Controller 인터페이스를 실행할 핸들러 어댑터를 찾고 실행해야 한다

 

스프링은 이미 대부분 구현되어있어서 개발자가 직접 만드는 일이 없다.

 

스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터

등록된 핸들러 매핑

...
0 = RequestMappingHandlerMapping	: 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping   	: 스프링 빈의 이름으로 핸들러를 찾음
...

등록된 핸들러 어댑터

...
0 = RequestMappingHandlerAdapter	: 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpReqeustHandlerAdapter   	: HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter	: 과거방식의 Controller 인터페이스 처리
...

작동방법은 이전에 만들었던 프레임워크와 동일하다.

 

1. 핸들러 매핑으로 핸들러 조회

- HandlerMapping을 순서대로 실행해서 찾는다.

- 이 경우 빈 이름으로 핸들러를 찾는 BeanNameUrlHandlerMapping가 성공하여 OldController를 반환한다.

 

2. 핸들러 어댑터 조회

- HandlerAdapter의 supports()를 순서대로 실행한다.

- SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하니 대상이 된다.

 

3. 핸들러 어댑터 실행

- 디스패쳐 서블릿이 조회한 어댑터를 실행하면서 핸들러 정보도 넘겨준다

- 어댑터가 핸들러를 실행하고 그 결과를 반환한다.

 

여기서 사용된 객체는 다음과 같다.

HandlerMapping = BeanNameUrlHandlerMapping

HandlerAdapter = SimpleControllerHandlerAdapter

 

다른 방법인 HttpRequestHandler로 한번 더 해보자.

@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");
    }
}

실행 순서는 위와 동일하다.

 

사용된 어댑터 객체만 다르다.

HandlerMapping = BeanNameUrlHandlerMapping

HandlerAdapter = HttpReqeustHandlerAdapter

 

@RequestMapping

가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping, RequestMappingHandlerAdapter 이다.

@RequestMapping 앞글자를 따서 만든 이름으로, 현재 사용하는 어노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터다. 대부분 실무에서 사용한다.

 


뷰 리졸버

OldController에 ModelAndView 객체를 반환하도록 수정해보자.

@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");
    }
}

new-form으로 이동하려고 한다. 

http://localhost:8080/springmvc/old-controller 경로로 이동하자

로그를 보면 정상적으로 실행이 되었는데, 에러 페이지가 발생한다.

이유는 뷰를 못 찾기 때문으로 뷰 리졸버를 만들어야 한다.

하지만 스프링 부트이기 때문에 application.properties에 가서 설정을 추가한다.

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

재실행하고 새로고침을 하면

정상적으로 접근이 된다.

 

뷰 리졸버 - InternalResourceViewResolver

스프링 부트는 InternalResourceViewResolver를 자동적으로 등록하기 때문에 application.properties 설정 정보를 사용해서 등록한다.

직접 선언해도 되지만, 스프링 부트의 편리한 기능을 애용하자.

@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class ServletApplication {
	...
    
        // 직접 선언해도 되지만, 편안하게 application.properties 설정을 이용하자.
	@Bean
	ViewResolver internalResourceViewResolver() {
		return new InternalResourceViewResolver("/WEB-INF/view/", ".jsp");
	}
}

 

뷰 리졸버 동작 방식

스프링 부트가 자동으로 등록하는 뷰 리졸버

...
1 = BeanNameViewResolver	        : 빈 이름으로 뷰를 찾아서 반환 (ex: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver   	: JSP를 처리할 수 있는 뷰를 반환
// 많지만 생략
...

 

1. 핸들러 어댑터 호출

- 핸들러 어댑터를 통해 new-form 논리 뷰 이름을 획득

 

2. ViewResolver 호출

- new-form 뷰 이름으로 viewResolver를 순서대로 호출

- BeanNameViewResolver는 찾아보지만 없다

- InternalResourceViewResolver에서 찾아 호출된다

 

3. InternalResourceViewResolver

이 뷰 리졸버는 InternalResourceView를 반환한다

 

4. 뷰 - InternalResourceView

JSP처럼 forward()를 호출해서 처리할 수 있는 경우에 사용된다.

5. view.render()

view.render()가 실행되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다.

'교육 및 인강 > 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글

스프링 MVC 기본 기능 - 로깅 알아보기, 요청 매핑, API 예시  (0) 2021.07.19
스프링 MVC 이해 - 시작하기, 컨트롤러 통합, 실용적인 방식  (0) 2021.07.19
MVC 프레임워크 만들기 - 실용적인 컨트롤러 (v4), 유연한 컨트롤러 (v5)  (0) 2021.07.13
MVC 프레임워크 만들기 - View 분리(v2), Model 추가(v3)  (0) 2021.07.05
MVC 프레임워크 만들기 - 프론트 컨트롤러 패턴 소개, 도입  (0) 2021.07.05
    '교육 및 인강/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
    • 스프링 MVC 기본 기능 - 로깅 알아보기, 요청 매핑, API 예시
    • 스프링 MVC 이해 - 시작하기, 컨트롤러 통합, 실용적인 방식
    • MVC 프레임워크 만들기 - 실용적인 컨트롤러 (v4), 유연한 컨트롤러 (v5)
    • MVC 프레임워크 만들기 - View 분리(v2), Model 추가(v3)
    loop-study
    loop-study
    오늘도 공부하자

    티스토리툴바