김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 : 스프링 MVC 구조 이해

개발할래·2024년 11월 6일
0

개발

목록 보기
21/21

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 인터페이스(과거에 사용한 스프링 컨트롤러)

  • org.springframework.web.servlet.mvc.Controller

    • 스프링도 처음에는 이런 딱딱한 형식의 컨트롤러를 제공
    • Controller 인터페이스는 @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
      • HttpRequestHandler 처리
    • 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 구조
    • 스프링 부트가 자동 등록하는 뷰 리졸버
        1. BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환
        1. 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

  • ViewName 직접 반환

    • 뷰의 논리 이름을 반환
    • 애노테이션 기반의 컨트롤러는 인터페이스로 고정되어 있지 않고, 유연하게 설계되어 있기 때문에
      ModelAndView나 문자형으로 반환해도 무관 (viewName으로 인지 처리)
  • @RequestParam 사용

    • 요청파라미터를 처리하는 애노테이션
    • 애노테이션 기반의 컨트롤러는, 파라미터로 HttpServletRequest, HttpServletResponse, @RequestParam, Model 등을 받을수 있음
    • @RequestParam("test")와 request.getParameter("test") 같다고 인지해도 무방
  • Model 파라미터

    • save(), members()를 보면 Model을 파라미터로 받는 것을 확인, 스프링 MVC도 해당 편의 기능을 제공
  • @RequestMapping(@GetMapping, @PostMapping)

    • @RequestMapping은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분
      예를 들어 URL /new-form이고, HTTP Method가 GET인 경우를 모두 만족하는 매핑의 경우는 아래와 같이 처리
  • 이것을 @GetMapping, @PostMapping으로 더 편리하게 사용
profile
내 인생부터 개발

0개의 댓글