스프링MVC, DispatcherServlet, ArgumentResolver, HttpMessageConverter

2024. 8. 15. 07:44Backend 취업준비/Spring

서블릿 이란?

  • 위와 같은 HTTP요청, 응답 흐름에 필요한 일련의 작업들에서 의미있는 비지니스 로직을 제외한 모든 작업들을 자동화 해주는 객체
  • HTTP 요청 정보를 편리하게 사용할 수 있는 HttpServletRequest 객체 제공
  • HTTP 응답 정보를 편리하게 제공할 수 있는 HttpServletResponse 객체 제공

 

 

서블릿 객체의 작동 방식

  1. 스프링 부트가 실행하면서 내장 통켓 서버가 스프링 부트가 내장 톰켓 서버를띄워줌. 톰켓 서버 (WAS)는 내부에 Servlet Container 기능을 가지고 있음
  2. WAS의 Servlet Container가 servlet 객체를 생성
  3. 클라이언트가 해당 servlet을 사용하는 http 요청을 하면, Servlet Container에서 request,response 객체 생성
  4. 이때, 쓰레드가 Servlet 객체 호출하고 request,response 객체를 Servlet 객체에 넘겨줌. 
  5. request 객체를 활용해 Servlet의 비즈니스 로직 실행. 
  6. 응답 결과를 response 객체에 담은 후, Servlet Container에 전달
  7. Servlet Container가 http 응답 메시지 생성 후 클라이언트에게 전달

 

 

서블릿 + 단순 html => jsp 뷰 템플릿 사용 => mvc패턴 등장

  • 서블릿으로 개발할 때는 뷰(View)화면을 위한 HTML을 만드는 작업이 자바 코드에 섞여서 지저분하고 복잡했다.
  • JSP를 사용한 덕분에 뷰를 생성하는 HTML 작업을 깔끔하게 가져가고, 중간중간 동적으로 변경이 필요한 부분에만 자
    바 코드를 적용했다. 그래도 부족함
  • 비즈니스 로직은 서블릿 처럼 다른곳에서 처리하고, JSP는 목적에 맞게 HTML로 화면(View)을 그리는 일에 집중하도록 함.

 

여전히 많이  남은 개선사항들

 

포워드 중복

  • 문제점: 서블릿에서 비즈니스 로직을 처리한 후, 결과를 특정 뷰로 전달하기 위해 RequestDispatcher의 forward() 메서드를 사용. 하지만, 이 포워드 작업은 매번 모든 컨트롤러에서 반복해서 호출되어야 한다. 예를 들어, 다음과 같은 코드가 모든 서블릿에 중복되어 나타남.이 중복 코드는 유지보수성을 떨어뜨리고, 실수로 잘못된 뷰를 지정할 가능성을 높임.
     
  • 개선 방안: 이 문제는 MVC 패턴에서 프론트 컨트롤러(Front Controller)를 도입함으로써 해결. 프론트 컨트롤러는 모든 요청을 한 곳에서 처리하고, 비즈니스 로직 처리 후 적절한 뷰로 포워드하는 역할. 이로 인해 개별 서블릿에서는 더 이상 포워드 코드를 작성할 필요가 없어지고, 중복 코드가 제거
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/views/someView.jsp"); 
dispatcher.forward(request, response);

 

ViewPath 중복

  • 문제점: JSP 파일의 경로는 물리적 경로와 논리적 경로로 구분. 물리적 경로는 파일 시스템에서의 실제 위치를 의미하고, 논리적 경로는 사용자가 접근하는 URL을 의미함. JSP를 사용하면서, 개발자는 논리적 경로와 물리적 경로를 모두 명시적으로 설정해야 했습니다. 이로 인해 다음과 같은 코드가 서블릿에 중복되곤 했음. 오타 유발
  • 개선 방안: 이 문제는 뷰 리졸버(View Resolver)라는 개념을 도입하여 해결. 뷰 리졸버는 논리적 뷰 이름을 전달받아, 이를 물리적 경로로 자동으로 매핑. 예를 들어, 논리적 경로를 "home"으로 전달하면, 뷰 리졸버는 이를 "/WEB-INF/views/home.jsp"와 같은 실제 경로로 변환. 이를 통해 물리적 경로를 명시할 필요가 없어지고, 코드의 가독성과 유지보수성이 크게 향상.

사용하지 않는 코드 (HttpServletResponse 객체)

  • 문제점: JSP 페이지에서 HTML을 렌더링하는 동안 서블릿의 HttpServletResponse 객체를 직접 사용하지 않는 경우가 많음. 그러나 서블릿 코드는 HttpServletRequest, HttpServletResponse 객체를 항상 받아야 하므로, 불필요한 코드가 많이 생긴다. 이는 코드가 불필요하게 길어지고, 사용하지 않는 객체로 인해 코드의 명확성 저하
  • 개선 방안: MVC 패턴에서는 JSP와 같은 뷰 템플릿이 직접 HttpServletRequest, HttpServletResponse 객체에 의존하지 않도록 함. 대신, 데이터는 모델 객체로 전달되고, 뷰는 모델 데이터를 활용하여 화면을 렌더링. 이는 서블릿 코드에서 불필요한 객체 참조를 줄이고, 뷰 템플릿은 오직 화면 표시만을 책임지도록 분리

 

 

FrontController패턴

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
  • 공통 처리 => DispatcherServlet이 공통 처리해주는 기능 참고
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨
  • 스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음

 

MVC프레임워크 발전 과정

 

1.  프론트 컨트롤러를 도입
기존 구조를 최대한 유지하면서 프론트 컨트롤러를 도입
2.  View 분류
단순 반복 되는 뷰 로직 분리 (View)
3. Model 추가
컨트롤러에 서블릿 종속성 제거 (ModelAndView), 뷰 이름 중복 제거 (ViewResolver)

4. 3 거의 비슷
구현 입장에서 ModelAndView를 직접 생성해서 반환하지 않는 형태
5: 유연한 컨트롤러
어댑터 도입. 어댑터를 통해서 다양한 형태의 컨트롤러를 한번에 처리  (3, 4 형태 혹은 더 확장 가능) 할 수 있게 됨. 

 

 

DispatcherServlet이 공통 처리해주는 기능

  • 요청 수신 및 핸들러 매핑 (모든 경로, "/")
    • 클라이언트로부터 HTTP 요청을 받아들이고, 해당 요청을 처리할 수 있는 핸들러(컨트롤러 메서드)를 찾음. 이 과정에 HandlerMapping 인터페이스를 사용하여 수행.
    • RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
  • 핸들러 어댑터 선택
    • 매핑된 핸들러를 실행하기 위해 적절한 HandlerAdapter를 선택. HandlerAdapter는 핸들러가 실제로 호출되기 전에 필요한 모든 전처리를 수행. => 다음 단락 참고
  • 핸들러 실행
    • 선택된 핸들러(컨트롤러 메서드)를 호출하여 실제로 요청을 처리하고, 그 결과를 ModelAndView 객체로 반환 받음.
  • 뷰 이름 해석 및 ViewResolver 적용
    • 핸들러가 반환한 ModelAndView 객체에 정의된 뷰 이름을 기반으로 ViewResolver를 사용하여 적절한 뷰를 결정
  • 뷰 렌더링
    • 최종적으로 결정된 뷰를 사용하여 클라이언트에게 응답을 렌더링. 이 과정에서 모델 데이터가 뷰에 전달되고, 뷰는 이를 사용해 HTML, JSON, XML 등의 형태로 응답을 생성.

 

 

 

 

HandlerAdapter는 핸들러가 실제로 호출되기 전에 필요한 모든 전처리를 수행

- ArgumetResolver, HttpMessageConverter

  • ArgumentResolver
    • 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있다. HttpServletRequest , Model 은 물론이고, @RequestParam , @ModelAttribute 같은 애노테이션 그리고 @RequestBody , HttpEntity 같은 HTTP 메시지를 처리하는 부분까지 매우 큰 유연함을 가진다.
    • 이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다.
    • 애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. 그리고 이렇게 파리미터의 값이 모
      두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
  • HttpMessageConverter
    • HTTP의 BODY에 문자 내용이나 Json형태로 보내기 위해 HttpMessageConverter가 사용된다.
    • HTML 파일의 경우, Spring에서 주로 View나 ViewResolver를 통해 처리, 직접 HTML파일 자체로 처리하고 싶으면  StringHttpMessageConverter 가 사용될 수 있긴 하다.
    • 요청의 경우 @RequestBody 를 처리하는 ArgumentResolver 가 있고, HttpEntity 를 처리하는 ArgumentResolver 가 있다. 이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성
      하는 것이다
    • 응답의 경우 @ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있다. 그리고 여기에서
      HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.

 

 

출처

김영한의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1

 

코드 참고

https://github.com/aammddkkzxc/spring-mvc1-1