[번역] Spring(2) Spring Web MVC

rin·2020년 4월 25일
2

Document 번역

목록 보기
2/22
post-thumbnail

2020.04.23

ref. Web Servlet

Web on Servlet Stack

Version 5.2.5.RELEASE

이 문서에서는 Servlet API를 기반으로하고 Servlet 컨테이너에 배포된 Servlet-stack web applications에 대한 지원사항을 다룬다. 여기에는 Spring MVC, View technologies, CORS Support 및 WebSocket Support가 포함된다.

이 글에서는 Spring MVC만 다룬다.

Spring Web MVC

📌Spring Web MVC

  • Servlet API를 기반으로 구축된 최초의 웹 프레임 워크
  • 처음부터 Spring Framework에 포함되었다.
  • 공식 이름은 Spring Web MVC 소스모듈(spring-webmvc)에서 유래됐지만 일반적으로 "Spring MVC"로 알려져 있다.

DispatcherServlet

다른 많은 웹 프레임 워크와 마찬가지로 Spring MVC는 프론트 컨트롤러 패턴을 중심으로 설계되었다. 중앙 ServletDispatcherServlet은 요청 처리를 위한 공유 알고리즘을 제공하며 실제 작업은 구성 가능한 델리게이트 컴포넌트(delegate components, 대리 구성 요소=업무를 분담시킬 하위 컴포넌트라고 생각하면 될 듯)에 의해 수행된다. 이 모델은 유연하며 다양한 워크 플로우를 지원한다.

DispatcherServletServlet와 마찬가지로 Java configuration또는 web.xml을 사용하여 서블릿 규격에 따라 선언되고, 매핑되어야 한다.
👉 스프링은 Web.xml이 반드시 필요한 줄 알았는데 굳이 사용하지 않아도 된다고 한다! Spring3.1부터 Servlet3.0을 지원하는데, 이 이후로 web.xml없는 자바 웹 어플리케이션을 만들 수 있다. 스프링 빈 설정 또한 Spring3.0부터 지원을 해왔고 3.1부터 이 기능을 대폭 강화하였다고 한다.

그 다음, DispatcherServlet은 Spring Configuration을 사용하여 요청 매핑, 뷰 리솔루션, 예외 처리 등에 필요한 delegate components를 검색한다.
다음 Java configuration 예는 서블릿 컨테이너에 의해 자동으로 탐지되는 디스패처 서블릿을 등록하고 초기화한다. ( Servlet Config 참조 ).

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

ServletContext API를 직접 사용하는 것 외에도 AbstractAnnotaionConfigDispatcherSerlvetInitializer를 사용해 특정 메소드를 확장하고 대체 할 수도 있다. (아래의 컨텍스트 계층 구조 참조)

다음 web.xml 구성의 예에서도 DispatcherServlet을 등록하고 초기화한다.

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

스프링 부트는 다른 초기화 순서를 따른다. 스프링 부트는 서블릿 컨테이너의 라이프 사이클에 연결하는 대신 스프링 configuraion을 사용하여 자체 내장된 서블릿 컨테이너를 부트 스트랩한다. 필터 및 서블릿 선언은 스프링 configuraion을에서 감지되어 서블릿 컨테이너에 등록된다. 자세한 내용은 스프링 부트 설명서를 참조하십시오.

Context Hierarchy : 컨텍스트 계층

디스패처 서블릿은 자체 구성에 WebApplicationContext(일반 ApplicationContext를 상속함)를 기대한다. WebApplicationContext에는 서블릿 컨텍스트와 이와 연관된 서블릿에 대한 링크가 있다. 또한 어플리케이션이 RequestContextUtils의 정적 메소드를 사용하여 WebApplicationContext에 액세스해야 할 경우 이를 조회할 수 있도록 서블릿 컨텍스트에 바인딩되어있다.

많은 어플리케이션의 경우 단일 WebApplicationContext가 간단하면서도 충분하다. 또한 하나의 루트 WebApplicationContext가 여러 디스패처 서블릿(또는 다른 서블릿) 인스턴스에 걸쳐 공유되고, 각각 고유한 하위 WebApplicationContext구성을 갖는 컨텍스트 계층을 가질 수도 있다. 컨텍스트 계층 기능에 대한 자세한 내용은 ApplicationContext의 추가 기능을 참조하라.

루트 WebApplicationContext에는 일반적으로 여러 서블릿 인스턴스에서 공유해야 하는 데이터 저장소 및 비즈니스 서비스와 같은 인프라 빈이 포함되어 있다. 이러한 은 효과적으로 상속되며, 일반적으로 주어진 서블릿에 로컬로 존재하는 빈을 포함하는 서블릿별 하위 WebApplicationContext에서 오버라이딩(재정의, 재선언)될 수 있다. 다음 이미지는 이러한 관계를 보여준다.

다음은 WebApplicationContext 계층 구조의 구성 예이다.

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}

어플리케이션 컨텍스트 계층 구조가 필요하지 않다면, 어플리케이션은 getServletConfigClasses()에서의 null 설정과 getRootConfigClasses()을 통해 모든 configuration을 반환 할 수 있다.

다음은 web.xml에서 이다. :

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>

어플리케이션 컨텍스트 계층 구조가 필요하지 않은 경우, 어플리케이션은 "root" 컨텍스트만 구성하고 contextConfigLocation 서블릿 파라미터는 비워 둘 수 있다.

Special Bean Types

디스패처 서블릿은 요청을 처리하고 적절한 응답을 제공하기 위한 특수한 빈을 위임한다. 특수한 빈은 프레임워크 컨트렉트(contracts)을 이행하는 스프링에 의해 관리되는 오브젝트 인스턴스를 뜻한다. 이러한 계약에는 대개 내장 컨트랙트(built-in contracts)가 포함돼 있지만, 그것들을 확장하거나 교체 할 수 있으며 프로퍼티를 커스텀마이징 할 수 있다.

다음은 디스패처서블릿에 이해 특수한 빈으로 검출되는 것들이다.
⭐️⭐️⭐️
HandlerMapping

  • 사전/사후 처리를 위한 인터셉터 목록과 함께 request를 매핑한다.
  • 매핑은 일부 기준에 기초하지만 핸들러매핑 구현체에 따라 세부 사항이 달라진다.
  • 두 가지 주요 핸들러매핑 구현체에는 RequestMappingHandlerMapping와(@RequestMapping 어노테이션이 달린 메소드를 지원한다.) SimpleUrlHandlerMapping(URI 경로 패턴의 명시적 등록을 유지한다.)가 있다.

HandlerAdapter

  • 핸들러가 실제로 호출되는 방식에 관계없이 요청에 매핑된 핸들러를 호출하도록 도와준다.
  • 예를 들어, 어노테이션이 달린 컨트롤러를 호출하려면 어노테이션을 해석해야한다. HandlerAdapter의 주목적은 그러한 세부 작업들로부터 DispatcherServlet을 자유롭게 하는 것이다.

HandlerExceptionResolver

  • 예외 처리, 핸들러에 HTML 오류나 기타 대상들을 매핑하는 전략
  • Exception을 참조하라.

ViewResolver

  • 핸들러로부터 리턴된 논리적인 String 기반의 뷰 이름을 response에 뷰로 렌더링하기 위한 실제 이름으로 해석한다.
  • View ResolutionView Technologies을 참조하라.

LocaleResolver,LocaleContextResolver

  • 국제화된 뷰를 제공하도록 클라이언트가 사용하고 있는 Locale과 그에 맞는 timezone으로 해석한다.
  • Locale을 참조하라.

ThemeResolver

  • 개인적인 레이아웃을 제공하기 위해 웹 어플리케이션이 사용가능한 테마를 제공한다.

MultipartResolver

  • 몇 멀티파트 파싱 라이브러리를 사용해 multi-part request(e.g. 브라우저 양식 파일 업로드)을 파싱을 위한 추상체이다.
  • Multipart Resolver를 참조하라.

FlashMapManager

  • 일반적으로 리다이렉트를 통해 한 요청에서 다른 요청으로 어트리뷰트를 전달하는데 사용할 수 있는 "input"과 "output" FlashMap을 저장하고 검색한다.
  • Flash Attributes를 참조하라.

2020.04.24 추가

Web MVC Config

애플리케이션은 요청을 처리하는 데 필요한 특수한 빈 타입에 인프라 빈 리스트를 선언할 수 있다. DispatcherServlet은 각 특수한 빈의 WebApplicationContext를 검사한다. 일치하는 빈 타입이 없는 경우, 그것은 DispatcherServlet.properties에 쓰여 있는 기본 유형으로 되돌아간다.

보통 MVC Config는 가장 좋은 출발점이다. 그 이유는 필요한 빈을 자바나 XML로 선언하고 그것을 커스텀마이징 할 수 있는 더 높은 수준의 구성 콜백 API를 제공하기 때문이다.

스프링 부트는 MVC Java 구성을 사용하여 Spring MVC를 구성하고 많은 추가적인 옵션을 제공한다.

Servlet Config

Servlet 3.0 이상 버전에서는 environment 또는 web.xml 파일로 서블릿 컨테이너를 프로그래밍 방식으로 구성할 수 있는 옵션이 있다.
다음 예제는 디스패처 서블릿을 등록하는 것이다.

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer는 구현체가 감지돼 자동으로 Sevlet 3 컨테이너를 초기화하는데 사용하는 것으로 Spring MVC가 제공하는 인터페이스이다.

AbstractDispatcherServletInitializer(추상 클래스인 WebApplicationInitializer의 구현체)는 서블릿 매핑과 디스패처 서블릿 구성의 위치를 지정하는 방법을 오버라이딩함으로써 디스패처 서블릿을 쉽게 등록할 수 있게 한다.
다음 예제와 같이 Java 기반 스프링 구성을 사용하는 어플리케이션에 권장한다.:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

만약 XML 기반의 스프링 구성을 사용한다면, 아래의 예처럼 직접 AbstractDispatcherServletInitializer를 확장해야 한다.

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

AbstractDispatcherServletInitializer은 또한 Filter 인스턴스를 추가하고 그것들을 자동으로 DispatcherServlet에 매핑 할 수 있는 편리한 방법을 제공한다. :

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}

각 필터는 구체적인 유형에 따라 기본적인 이름으로 추가되며 자동으로 디스패처 서블릿에 매핑된다.
AbstractDispatcherServletInitializerprotected 메소드인 isAsyncSupported는 디스패처 서블릿과 이에 매핑된 모든 필터를 비동기적으로 사용할 수 있게 해주는 하나의 장소를 제공한다. 기본적으로 이 플래그는 true이다.

마지막으로, 디스패처 서블릿을 커스텀마이징하고 싶다면 createDispatcherServlet 메소드를 오버라이딩 하면 된다.

2020.04.25 추가

Processing ⭐️

디스패처 서블릿은 다음과 같이 요청을 처리한다.

  1. 프로세스에서 컨트롤러와 다른 요소가 사용할 수 있는 속성인 WebApplicationContext은 request에서 검색되고 바인딩된다.
    • 기본적으로 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 키 아래에 바인딩된다.
  2. 요청을 처리 할 때 (뷰 렌더링, 데이터 준비 등) 프로세스의 요소가 로케일을 해석 할 수 있도록 locale resolver가 request에 바인딩된다.
    • 만약 로케일 확인이 필요하지 않으면, 로케일 리졸버 또한 필요하지 않다.
  3. theme resolver은 뷰 같은 요소가 사용할 테마를 결정하도록 요청에 바인딩된다.
    • 테마를 사용하지 않으면 무시해도 된다.
  4. multipart file resolver를 지정하면 request는 요청에서 multiparts가 검사된다.
    • 멀티 파트가 발견되면 request는 프로세스의 다른 요소에 의한 추가 처리를 위해 MultipartHttpServletRequest로 랩핑된다.
    • 멀티 파트 처리에 대한 자세한 내용은 멀티 파트 리졸버를 참조하라.
  5. 적절한 핸들러가 검색된다.
    • 핸들러를 찾으면 모델 또는 랜더링을 준비하기 위해 핸들러 연관있는 실행 체인(preprocessors, postprocessors, and controllers)이 실행된다.
    • 또는 어노테이션이 달린 컨트롤러의 경우, 뷰를 반환하는 대신 (HandlerAdapter 내에서) 응답을 랜더링 할 수도 있다.
  6. 모델이 반환되면 뷰가 렌더링된다.
    • 만약 모델이 리턴되지 않는다면 (아마도 보안상의 이유로 preprocessor나 postprocessor가 request를 인터셉트 하는 경우) 요청이 이미 이행되었을 수 있으므로 뷰는 랜더링되지 않는다.

📌스프링 어플리케이션의 application context
1. ContextLoaderListener → Root WebApplicationContext
- 서로 다른 Controller 들이 동시에 필요한 의존성(공통 빈)이 있는 경우 이를 이용해 설정한다.
- 서비스 계층이나 DAO를 포함한 웹 환경에 독립적인 빈들을 담아둔다.
- 서로 다른 ServletContext에서 공유해야하는 빈 등록
- ServletContext와 공통된 빈이 있다면 ServletContext가 우선
- WebApplication 전체에 사용가능한 DB연결, 로깅 기능들이 이용된다.
2. DispatcherServlet → WebApplicationContext
- DispatcherServlet이 직접 사용하는 컨트롤러를 포함한 웹 관련 빈을 등록
- DispatcherServlet은 각각 독자적인 WebApplicationContext를 가지고 있고, 동일한 Root WebApplicationContext를 공유한다.
Root는 상속되며, 내부의 메소드들은 오버라이딩 가능
DispatcherServlet은 ContextLoaderListener에 이해 ServletContext에 등록된 ApplicationContext를 상속받음 → WebApplicationContext 생성
DispatcherServlet이 여러개가 필요한 경우의 Application을 고려하여 상속받는 구조로 만들어졌다.

📌디스패처 서블릿의 요청처리

  • 디스패처 서블릿 : 모든 요청을 받음. 요청을 분석하고 처리하기 위해 적절한 컨트롤러로 요청전달(기능 위임)
  • 공통 서비스 : 모든 요청에 제공 (Servlet WebApplicationContext에 정의)
    로케일 해석, 테마 해석, 멀티파트(파일 업로드) 핸들링
  • 핸들러 맵핑 : HTTP 요청정보를 이용해 핸들러(컨트롤러 클래스 내 메서드)에 맵핑한다.
    • lookupHandlerMethod()에서 수행
    • urlLookup이라는 Map에 url 매핑정보 존재
    • @RequestMapping(@GetMapping 등..)을 적용 → Http경로와 핸들러를 매핑한 구현체를 자동으로 등록 (직접 구성할 필요X)
  • 핸들러 인터셉터 : DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청, 응답을 가공하는 일종의 필터
  • 핸들러 어댑터 : 결정된 Controller의 메소드를 호출하는 역할
    • DispatcherServlet이 Handlerㅏ 어떤 방식으로 구현되어 있는지 신경쓰지 않게 해줌
  • 핸들러 익셉션 리졸버(Handler Exception Resolver) : 핸들러의 요청처리 중 발생하는 예외를 처리
  • View Resolver : 어떤 종류의 view(JSP, html 등)를 사용할 것인지 선택하고 문자열을 기반으로 사용할 view를 선택하는 역할
    • 컨트롤러가 반환한 논리적이름을 기반으로 뷰를 해석해 찾아냄
    • 다양한 메커니즘을 지원하는 구현 클래스 존재
    • 선택된 view는 저장된 Model의 데이터를 이용해 페이지를 완성하고 사용자에게 응답

WebApplicationContext에 선언된 HandlerExceptionResolver 빈은 요청을 처리하는 동안에 발생한 예외를 해결하는 데 사용된다. 이러한 exception resolvers는 로직을 커스텀마이징하여 예외를 처리할 수 있다. 자세한 내용은 Exception을 참조하라.

스프링 디스패처 서블링은 또한 서블릿 API에 의해 지정된 대로 last-modification-date의 리턴을 지원한다. 특정 요청에 대한 최종 수정 날짜를 결정하는 프로세스는 간단하다. 디스패처 서블릿은 적절한 핸들러 맵핑을 찾고 발견된 핸들러가 LastModified 인터페이스의 구현체인지 테스트한다. 만약 그렇다면, LastModified 인터페이스의 메소드인 long getLastModified(request)의 값이 클라이언트로 리턴된다.

web.xml 파일의 서블릿 선언에 서블릿 초기화 파라미터(init-param elements)를 추가하여 개별적인 디스패처 서블릿 인스턴스를 커스텀마이징 할 수 있다. 다음 표에는 지원되는 파라미터가 나열돼있다.:
(표 1. DispatcherServlet 초기화 매개 변수)

Annotated Controllers

스프링 MVC는 어노테이션 기반의 프로그래밍 모델을 제공하고 @Controller@RestController 컴포넌트는 request mappings, request input, exception handling 등을 표현하는 어노테이션을 사용한다. 어노테이션이 있는 컨트롤러는 유연한 메소드 signatures가 있고 기본 클래스를 확장하거나 특정 인터페이스를 구현할 필요가 없다. 다음 예제는 어노테이션으로 정의된 컨트롤러를 보여준다.

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}

위의 예제에서 메소드는 Model을 받고 뷰 이름으로 String인 것을 반환하지만 다른 많은 옵션이 있으며 챕터의 뒷부분에서 설명한다.

spring.io의 가이드와 튜토리얼은 이 섹션에서 설명된 어노테이션 기반 프로그래밍 모델을 사용한다.

profile
🌱 😈💻 🌱

1개의 댓글

comment-user-thumbnail
2020년 7월 8일

좋은 글이네요!

답글 달기