토비의 스프링 Vol.2 - 3장 스프링 웹 기술과 스프링 MVC

2020-11-04

스프링 웹 기술과 스프링 MVC

스프링의 웹 프레젠테이션 계층 기술

스프링에서 사용되는 웹 프레임워크의 종류

스프링 웹 프레임워크
  • 스프링 서블릿/스프링 MVC
  • 스프링 포틀릿
    스프링 포트폴리오 웹 프레임워크
  • Spring Web Flow
  • Spring JavaScript
  • Spring Faces
  • Spring Web Service
  • Spring BlazeDS Integration
    스프링을 기반으로 두지 않는 웹 프레임워크
  • JSP/Servlet
  • Struts1
  • Struts2
  • Tapestry 3, 4
  • JSF/Seam

    스프링 MVC와 DispatcherServlet 전략

    스프링은 특정 기술이나 방식에 억매이지 않으면서 웹 프레젠테이션 계층의 각종 기술을 조합, 확장해서 사용할 수 있는 매우 유연한 웹 애플리케이션 개발의 기본 틀을 제공해준다. 이 틀이 제공하는 다양한 전략의 확장 포인트를 이용해서 스프링 스스로 기본적인 MVC 프레임워크를 만들어뒀다.

스프링 웹 기술의 핵심이자 기반이 되는 것은 DispatcherServlet이다. 이 서블릿은 스프링의 웹 기술을 구성하는 다양한 전략을 DI로 구성해서 확장하도록 만들어진 스프링 서블릿/MVC의 엔진과 같은 역할을 한다.

DispatcherServlet과 MVC 아키텍처

스프링의 웹 기술은 MVC 아키텍처를 근간으로 하고 있다. MVC는 프레젠테이션 계층의 구성요소 정보를 담은 모델(M), 화면 출력 로직을 담은 뷰(V), 그리고 제어 로직을 담은 컨트롤러(C)로 분리하고 이 세 가지 요소가 서로 협력해서 하나의 웹 요청을 처리하고 응답을 만들어내는 구조다.

MVC 아키텍처는 보통 프론트 컨트롤러 패턴과 함께 사용된다. 프론트 컨트롤러 패턴은 중앙집중형 컨트롤러를 프레젠테이션 계층의 제일 앞에 둬서 서버로 들어오는 모든 요청을 먼저 받아서 처리하게 만든다. 프론트 컨트롤러는 클라이언트가 보낸 요청을 받아서 공통적인 작업을 먼저 수행한 후에 적절한 세부 컨트롤러로 작업을 위임해주고, 클라이언트에게 보낼 뷰를 선택해서 최종 결과를 생성하는 등의 작업을 수행한다. 예외가 발생했을 때 이를 일관된 방식으로 처리하는 것도 프론트 컨트롤러의 역할이다. 프론트 컨트롤러는 컨트롤러와 뷰, 그리고 그 사이에서 주고받는 모델, 세 가지를 이용해서 작업을 수행하는 게 일반적이다.

서버가 브라우저나 여타 HTTP 클라이언트로부터 HTTP 요청을 받기 시작해서 다시 HTTP로 결과를 응답해주기까지의 과정
  1. DispatcherServlet의 HTTP 요청 접수
  2. DispatcherServlet에서 컨트롤러로 HTTP 요청 위임
  3. 컨트롤러의 모델 생성과 정보 등록
  4. 컨트롤러의 결과 리턴: 모델과 뷰
  5. DispatcherServlet의 뷰 호출과 6. 모델 참조
  6. HTTP 응답 돌려주기
    DispatcherServlet의 DI 가능한 전략
    • HandlerMapping
    • 핸들러 매핑은 URL과 요청 정보를 기준으로 어떤 핸들러 오브젝트, 즉 컨트롤러를 사용할 것인지를 결정하는 로직을 담당한다. * HandlerAdapter
    • 핸들러 어댑터는 핸들러 매핑으로 선택한 컨트롤러/핸들러를 DispatcherServlet이 호출할 때 사용하는 어댑터다. * HandlerExceptionResolver
    • HandlerExceptionResolver 전략은 예외가 발생했을 때 이를 처리하는 로직을 갖고 있다. * ViewResolver
    • 뷰 리졸버는 컨트롤러가 리턴한 뷰 이름을 참고해서 적절한 뷰 오브젝트를 찾아주는 로직을 가진 전략 오브젝트다. * LocaleResolver
    • 지역정보를 결정해주는 전략이다. * ThemeResolver
    • 테마를 가지고 이를 변경해서 사이트를 구성할 경우 쓸 수 있는 테마 정보를 결정해주는 전략이다. * RequestToViewNameTranslator
    • 컨트롤러에서 뷰 이름이나 뷰 오브젝트를 제공해주지 않았을 경우 URL과 같은 요청정보를 참고해서 자동으로 뷰 이름을 생성해주는 전략이다.

스프링 웹 애플리케이션 환경 구성

간단한 스프링 웹 프로젝트 생성

  • 루트 웹 애플리케이션 컨텍스트 등록
  • 서블릿 웹 애플리케이션 컨텍스트 등록
  • 스프링 웹 프로젝트 검증
    • 핸들러 어댑터: SimpleControllerHandlerAdapter
    • 핸들러 매핑: BeanNameUrlHandlerMapping
    • 뷰 리졸버: InternalResourceViewResolver

      스프링 웹 학습 테스트

  • 서블릿 테스트용 목 오브젝트 이용
    • MockHttpServletRequest
    • MockHttpSession
    • MockHttpServletResponse
    • MockServletConfig, MockServletContext
  • 테스트를 위한 DispatcherServlet 확장
    • 테스트 환경에서 DispatcherServlet이 만드는 서블릿 컨텍스트를 자유롭게 설정할 수 있다.
    • XML 설정파일의 위치를 임의로 지정할 수도 있고, 빈 클래스를 직접 등록할 수도 있다.
    • 컨트롤러가 돌려주는 ModelAndView를 저장해뒀다가 이를 테스트에서 참조할 수 있게 해준다.
  • 편리한 DispatcherServlet 테스트를 위한 AfterRunService를 구현한 테스트 지원 클래스
    • 편리한 헬퍼 메소드를 이용해서 간결한 테스트 코드를 빠르게 만들 수 있다.
    • 컨텍스트 설정과 웹 요청정보를 지정한 후에 runService() 메소드를 호출하면 요청정보를 생성해서 DispatcherServlet을 실행해준다.
    • runService()의 리턴
      • String getContentAsString()
      • ModelAndView getModelAndView()
      • AfterRunService assertViewName(String viewname), AfterRunService assertModel(String name, Object value)
      • WebApplicationContext getContext()
      • <T> getBean(Class<T> beanType)

컨트롤러

컨트롤러의 종류와 핸들러 어댑터

스프링 MVC가 지원하는 컨트롤러의 종류는 네 가지다. 각 컨트롤러를 DispatcherServlet에 연결해주는 핸들러 어댑터가 하나씩 있어야 하므로, 핸들러 어댑터도 네 개다.

  • Servlet과 SimpleServletHandlerAdapter
  • HttpRequestHandler와 HttpRequestHandlerAdapter
  • Controller와 SimpleControllerHandlerAdapter
  • AnnotationMethodHandlerAdapter

    핸들러 매핑

    핸들러 매핑은 HTTP 요청정보를 이용해서 이를 처리할 핸들러 오브젝트, 즉 컨트롤러를 찾아주는 기능을 가진 DispatcherServlet의 전략이다. 스프링은 기본적으로 다섯 가지 핸들러 매핑을 제공한다.

  • BeanNameUrlHandlerMapping
  • ControllerBeanNameHandlerMapping
  • ControllerClassNameHandlerMapping
  • SimpleUrlHandlerMapping
  • DefaultAnnotationHandlerMapping

    핸들러 매핑 설정에서 공통적으로 사용되는 주요 프로퍼티

  • order
  • defaultHandler
  • alwaysUseFullPath
  • detectHandlersInAncestorContexts

    핸들러 인터셉터

    핸들러 인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터다. 핸들러 인터셉터는 HandlerInterceptor 인터페이스를 구현해서 만든다. 이 인터페이스 안에는 세 개의 메소드가 포함되어 있다.

  • boolean proHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
  • void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
  • void afterCompletion(HttpServletRequest request, HttpServletResponse response, Objedct handler, Exception ex) throws Exception

    컨트롤러 확장

  • 커스텀 컨트롤러 인터페이스와 핸들러 어댑터 개발

    스프링이 직접 제공하는 컨트롤러 타입이 사용하기에 적합하다고 판단되면 그것을 가져다 그대로 쓰면 된다. 하지만 언제든 더 나은 아이디어가 있다면 기존 컨트롤러 타입을 확장하거나 직접 컨트롤러를 설계해서 사용하는 방법을 고려하면 된다.


컨트롤러가 작업을 마친 후 뷰 정보를 ModelAndView 타입 오브젝트에 담아서 DispatcherServlet에 돌려주는 방법은 두 가지가 있다. 첫째는 View 타입의 오브젝트를 돌려주는 방법이고, 두 번째는 뷰 이름을 돌려주는 방법이다.

DispatcherServlet이 사용하는 뷰 오브젝트는 스프링의 View 인터페이스를 구현해야 한다.

  • InternalResourceView와 JstlView
    • InternalResourceView는 RequestDispatcher의 forward()나 include()를 이용하는 뷰다. 컨트롤러가 돌려준 뷰 이름을 포워딩할 JSP의 이름으로 사용하고 모델 정보를 요청 애트리뷰트에 넣어주는 작업을 InternalResourceView와 DispatcherServlet이 해준다. 뷰 리졸버를 사용한다면 컨트롤러에서는 JSP 파일의 위치를 나타내는 논리적인 뷰 이름만 넘겨주면 된다.
  • RedirectView
    • RedirectView는 HttpServletResponse의 sendRedirect()를 호출해주는 기능을 가진 뷰다. 따라서 실제 뷰가 생성되는 것이 아니라, URL만 만들어져 다른 페이지로 리다이렉트된다.
  • VelocityView, FreeMarkerView
    • VelocityView와 FreeMarkerView는 모두 벨로시티와 프리마커라는 두 개의 대표적인 자바 템플릿 엔진을 뷰로 사용하게 해준다. JSP와 마찬가지로 컨트롤러에서 직접 뷰 오브젝트를 만드는 대신 VelocityViewResolver와 FreeMarkerViewResolver를 통해 자동으로 뷰가 만들어져 사용되게 하는 편이 낫다.
  • MarshallingView
    • 스프링 3.0에서 새롭게 등장한 OXM 추상화 기능을 활용해서 application/xml 타입의 XML 콘텐트를 작성하게 해주는 편리한 뷰다. 미리 준비해둔 마샬러 빈을 지정하고 모델에서 변환에 사용할 오브젝트를 지정해주면, OXM 마샬러를 통해 모델 오브젝트를 XML로 변환해서 뷰의 결과로 사용할 수 있다.
  • AbstractExcelView, AbstractJExcelView, AbstractPdfView
    • 엑셀과 PDF 문서를 만들어주는 뷰다. 또한 Abstract가 붙어 있으니 상속을 해서 코드를 구현해야 하는 뷰이기도 하다. AbstractExcelView는 아파치 POI 라이브러리를 이용해 엑셀 뷰를 만들어준다. AbstractJExcelView는 요즘 새로 떠오르는 엑셀지원 API인 JExcelAPI를 사용해 엑셀 문서를 만들어주고, AbstractPdfView는 iText 프레임워크 API로 PDF 문서를 생성해준다.
  • AbstractAtomFeedView, AbstractRssFeedView
    • 이 두 개의 뷰는 각각 application/atom+xml과 application/rss+xml 타입의 피드 문서를 생성해주는 뷰다. 이 두개의 뷰도 상속을 통해 피드정보를 생성하는 메소드를 직접 구현해줘야 한다.
  • XsltView, TilesView, AbstractJasperReportsView
    • XsltView는 XSLT 변환을 이용해 뷰를 생성해준다. TilesView는 Tiles 1, 2를 이용해 뷰를 생성할 수 있다. AbstractJasperReportsView는 리포트 작성용 프레임워크인 JasperReports를 이용해 CSV, HTML, PDF, Excel 형태의 리포트를 작성해준다. JasperReportsCsvView, JasperReportsHtmlView, JasperReportsPdfView, JasperReportsXlsView를 사용할 수 있다.
  • MappingJacksonJsonView
    • AJAX에서 많이 사용되는 JSON 타입의 콘텐트를 작성해주는 뷰다. 기본적으로 모델의 모든 오브젝트를 JSON으로 변환해준다.

      뷰 리졸버

      뷰 리졸버는 핸들러 매핑이 URL로부터 컨트롤러를 찾아주는 것처럼, 뷰 이름으로부터 사용할 뷰 오브젝트를 찾아준다.

  • InternalResourceViewResolver
    • 뷰 리졸버를 지정하지 않았을 때 자동등록되는 디폴트 뷰 리졸버다. 주로 JSP를 뷰로 사용하고자 할 때 쓰인다.
  • VelocityViewResolver, FreeMarkerViewResolver
    • 템플릿 엔진 기반의 뷰인 VelocityView와 FreeMarkerView를 사용하게 해주는 뷰 리졸버다.
  • ResourceBundleViewResolver, XmlViewResolver, BeanNameViewResolver
    • 컨트롤러마다 뷰의 종류가 달라질 수 있다면 한 가지 뷰만을 지원하는 뷰 리졸버를 사용할 수 없다. 이런 경우에 외부 리소스 파일에 각 뷰 이름에 해당하는 뷰 클래스와 설정을 담아두고, 이를 참조하는 ResourceBundleViewResolver와 XmlViewResolver를 사용하면 된다.
    • BeanNameViewResolver는 뷰 이름과 동일한 빈 이름을 가진 빈을 찾아서 뷰로 사용하게 해준다. 서블릿 컨텍스트의 빈을 사용한다.
  • ContentNegotiatingViewResolver
    • 직접 뷰 이름으로부터 뷰 오브젝트를 찾아주지 않는다. 미디어 타입 정보를 활용해서 다른 뷰 리졸버에게 뷰를 찾도록 위임한 후에 가장 적절한 뷰를 선정해서 돌려준다.
    • ContentNegotiatingViewResolver가 뷰를 결정하는 과정
      • 미디어 타입 결정
      • 뷰 리졸버 위임을 통한 후보 뷰 선정
      • 미디어 타입 비교를 통한 최종 뷰 선정

기타 전략

핸들러 예외 리졸버

HandlerExceptionResolver는 컨트롤러의 작업 중에 발생한 예외를 어떻게 처리할지 결정하는 전략이다.

  • AnnotationMethodHandlerExceptionResolver
    • 예외가 발생한 컨트롤러 내의 메소드 중에서 @ExceptionHandler 애노테이션이 붙은 메소드를 찾아 예외처리를 맡겨주는 핸들러 예외 리졸버다.
  • ResponseStatusExceptionResolver
    • 예외를 특정 HTTP 응답 상태 코드로 전환해준다.
  • DefaultHandlerExceptionResolver
    • 스프링에서 내부적으로 발생하는 주요 예외를 처리해주는 표준 예외처리 로직을 담고 있다.
  • SimpleMappingExceptionResolver
    • 예외를 처리할 뷰를 지정할 수 있게 해준다.

      지역정보 리졸버

      LocaleResolver는 애플리케이션에서 사용하는 지역정보를 결정하는 전략이다. LocaleResolver가 결정한 지역정보는 JstlView를 사용했을 때 <fmt:> 메시지에도 반영되고, ResourceBundleViewResolver의 views.properties 파일 이름을 결정할 때도 반영된다. 또, JSP나 프리마커 등의 스프링 폼 태그를 사용했을 때 화면에 출력할 에러 메시지나 안내 메시지를 담은 리소스 번들 파일도 지역정보에 따라 선택될 것이다.

      멀티파트 리졸버

      파일 업로드와 같이 멀티파트 포맷의 요청정보를 처리하는 전략을 설정할 수 있다. HttpServletRequest를 파라미터로 받는 컨트롤러에서는 전달받은 오브젝트를 MultipartHttpServletRequest로 캐스팅한 후에 멀티파트 정보를 가진 MultipartFile 오브젝트를 가져와 사용할 수 있다.

애노테이션 방식의 유연한 컨트롤러 메소드를 이용하면 처음부터 MultipartFile 타입의 파라미터로 전달받거나 변환기를 이용해서 아예 바이트 배열이나 파일 정보를 담은 오브젝트로 가져올 수도 있다.

  • RequestToViewNameTranslator
    • 컨트롤러에서 뷰 이름이나 뷰 오브젝트를 돌려주지 않았을 경우 HTTP 요청정보를 참고해서 뷰 이름을 생성해주는 로직을 담고 있다.

스프링 3.1의 MVC

애노테이션 기반 MVC 전략을 사용하지 않는 경우에도 사용할 수 있는 두 가지 새로운 기능

플래시 맵 매니저 전략

플래시 맵
  • 플래시 맵은 플래시 애트리뷰트를 저장하는 맵이다. 플래시 애트리뷰트는 하나의 요청에서 생성되어 다음 요청으로 전달되는 정보를 말한다. 웹 요청 사이에 전달되는 정보라면 HTTP 세션을 생각할 수 있겠지만 플래시 애트리뷰트는 일반 HTTP 세션에 저장되는 정보처럼 오래 유지되지 않는다. 플래시 애트리뷰트는 다음 요청에서 한 번 사용되고 바로 제거되는 특징이 있다.
  • 플래시 애트리뷰트는 Post/Redirect/Get 패턴을 적용하는 경우에 POST 단계의 작업 결과 메시지를 리다이렉트된 페이지로 전달할 때 주로 사용된다.
  • 플래시 맵은 여러 개의 애트리뷰트를 저장할 수 있도록 맵 타입으로 되어 있고, 플래시 애트리뷰트가 사용될 URL 조건과 제한시간을 지정할 수 있게 해준다.
    플래시 맵 매니저
  • 컨트롤러에서 만들어진 플래시 맵 오브젝트는 요청이 끝나기 전에 서버 어딘가에 저장돼야 한다. 그래야 다음 요청이 올 때까지 정보를 유지했다가 사용할 수 있다.
  • 플래시 맵을 저장하고, 유지하고, 조회하고, 제거하는 등의 작업을 담당하는 오브젝트를 플래시 맵 매니저라 하고 FlashMapManager 인터페이스를 구현해서 만든다. 플래시 맵 매니저는 DispatcherServlet이 미리 준비해준 것을 사용하면 된다.
    플래시 맵 매니저 전략
  • HTTP 세션 이용
  • 서버를 여러 대 사용하는 경우라면 서버 사이에 분산되어 저장되고 조회되는 분산 데이터 그리드를 이용
  • 저장소에 저장

    WebApplicationInitializer를 이용한 컨텍스트 등록

    스프링 3.1에서는 ServletContainerInitializer를 이용하면 스프링 컨텍스트 설정과 등록 작업에 자바 코드를 이용할 수 있다. 기본적으로 스프링의 웹 모듈 내에 ServletContainerInitializer를 구현한 클래스가 포함되어 있고 여기서 WebApplicationInitializer 인터페이스를 구현한 클래스를 찾아 컨텍스트의 초기화 작업을 위임한다.

WebApplicationInitializer를 구현한 클래스를 만들어두면 웹 애플리케이션이 시작될 때 onStartup() 메소드가 자동으로 실행된다. 이때 메소드 파라미터로 ServletContext 오브젝트가 전달되는데, 이를 이용해 필요한 컨텍스트 등록 작업을 수행하면 된다.

루트 웹 컨텍스트 등록

서블릿 컨텍스트가 만드는 이벤트는 컨텍스트 초기화 이벤트와 컨텍스트 종료 이벤트다. 웹 애플리케이션이 시작되고 종료되는 시점에 이벤트가 발생하고, 리스너를 만들어두면 이 두 가지 이벤트를 전달받게 된다.

스프링이 리스너를 이용해 루트 애플리케이션 컨텍스트를 생성하는 이유는 루트 컨텍스트의 생명주기가 서블릿 컨텍스트와 일치하기 때문이다. 서블릿 컨텍스트가 초기화될 때 루트 컨텍스트를 만들고, 서블릿 컨텍스트가 종료될 때 루트 컨텍스트도 함께 종료해야 할 필요가 있다. 이렇게 웹 애플리케이션과 같은 범위 동안 유지되는 서비스를 관리하기 위해 리스너가 사용된다.

서블릿 컨텍스트 등록

루트 컨텍스트가 리스너 안에서 초기화되고 관리되듯이 서블릿 컨텍스트는 서블릿 안에서 초기화되고 서블릿이 종료될 때 같이 종료된다. 이때 사용되는 서블릿이 바로 DispatcherServlet이다.


정리

  • 스프링 MVC에는 MVC의 세 가지 핵심 컴포넌트와 이를 엮어주는 MVC 엔진인 DispatcherServlet에서 사용할 수 있는 다양한 전략이 제공된다. 또한 새로운 전략을 만들어 적용할 수도 있다.
  • 스프링 애플리케이션의 웹 계층에는 스프링 MVC와 스프링 포트폴리오의 웹 기술 그리고 서드파티 웹 프레임워크를 모두 사용할 수 있다. 이를 위해 웹 계층과 나머지 계층 간의 의존관계를 제거하고 독립적으로 개발할 수 있어야 한다.
  • 웹 계층의 테스트도 적절한 목 오브젝트를 이용한다면 서버에 배치하지 않고 자동으로 실행가능한 단위 테스트로 작성할 수 있다. 스프링은 웹 계층의 테스트를 위해 유용하게 쓸 수 있는 다양한 목 오브젝트를 제공한다.
  • 스프링 MVC의 핵심 엔진인 DispatcherServlet은 7가지 종류의 전략을 제공한다. 각 전략은 빈으로 등록하고 설정할 수 있다. 직접 등록하지 않는 경우에는 디폴트 전략 구성을 활용한다.
  • 컨트롤러는 여러 가지 방식으로 개발할 수 있다. 핸들러 어댑터를 함께 만든다면 새로운 컨트롤러 타입을 추가할 수 있다.
  • 핸들러 매핑은 다양한 전략을 통해 요청정보와 이를 처리하는 컨트롤러를 연결해준다.
  • 핸들러 인터셉터는 컨트롤러를 실행하기 전후에 적용할 부가기능을 만들 때 사용한다.
  • 뷰 리졸버는 컨트롤러가 리턴한 논리적인 뷰 이름을 이용해 뷰 오브젝트를 찾아준다.
  • 핸들러 예외 리졸버를 이용하면 애플리케이션에서 발생한 예외를 처리하는 방법을 지정해줄 수 있다.
  • 스프링 3.1에는 플래시 맵 매니저 전략이 추가됐고 WebApplicationInitializer를 이용해 컨텍스트 생성과 등록을 위한 초기화 코드를 작성할 수 있다.

출처 : https://github.com/Masssidev/toby-vol2