[스프링 Web Servlet 공식문서 읽기]1. Spring MVC (1) - DispatcherServlet

ybw·2021년 11월 12일
post-thumbnail

1. 스프링 웹 MVC

Spring Web MVCServlet API를 기반으로 구축된 독창적인 웹 프레임워크이며 처음부터 Spring Framework에 포함되었습니다.
정식 명칭인 "Spring Web MVC"는 해당 소스 모듈( spring-webmvc) 의 이름에서 유래 했지만 일반적으로 "Spring MVC"로 알려져 있습니다.

Spring Web MVC와 병행하여 Spring Framework 5.0"Spring WebFlux"라는 이름도 소스 모듈( spring-webflux)을 기반으로 하는 반응 스택 웹 프레임워크를 도입했습니다 .
이 섹션에서는 Spring Web MVC를 다룹니다.
Servlet 컨테이너 및 Java EE 버전 범위와의 기준 정보 및 호환성은 Spring Framework Wiki를 참조하십시오 .

1.1 Dispatcher Servlet

다른 많은 웹 프레임 워크와 마찬가지로 Spring MVC는 프론트 컨트롤러 패턴을 중심으로 설계되었습니다. 중앙 ServletDispatcherServlet은 요청 처리를 위한 공유 알고리즘을 제공하며 실제 작업은 구성 가능한 delegate components에 의해 수행된다. 이 모델은 유연하며 다양한 워크 플로우를 지원한다.

DispatcherServlet은 Servlet와 마찬가지로 Java configuration또는 web.xml을 사용하여 서블릿 규격에 따라 선언되고, 매핑되어야 합니다.

그 다음, DispatcherServlet은 Spring Counfiguration을 사용하여 요청 매핑, 뷰 레졸루션, 예외 처리등에 필요한 delegate components를 검색합니다.

다음 Java Configuration 예는 Servlet컨테이너에 의해 자동 감지되는 DispatcherServlet를 등록하고 초기화합니다.

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

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

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

ServletContext API를 직접 사용하는 것 외에도 AbstractAnnotationConfigDispatcherServletInitializer특정 메서드를 확장 하고 재정의 할 수도 있습니다 ( 컨텍스트 계층 아래의 예제 참조 ).

다음 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>

Spring Boot는 다른 초기화 순서를 따릅니다.
Spring BootServlet 컨테이너의 수명 주기에 연결하는 대신 Spring Configuration을 사용하여 자체 및 포함된 Servlet 컨테이너를 부트스트랩합니다.
Filter및 Servlet선언Spring Configuration에서 감지되고 Servlet 컨테이너에 등록됩니다.

1.1.1 컨텍스트 계층

DispatcherServlet은 자체 구성에 WebApplicationContext(일반 ApplicationContext를 상속함)를 기대합니다. WebApplicationContextServletContext 링크와 연관된 Servlet가 있습니다. 또한 어플리케이션이 RequestContextUtils의 정적 메소드를 사용하여 WebApplicationContext에 액세스해야 할 경우 이를 조회할 수 있도록 ServletContext에 바인딩되어있습다.

많은 응용 프로그램에서 하나의 WebApplicationContext으로 간단하고 충분합니다. 하나의 루트 WebApplicationContext가 여러 DispatcherServlet(또는 다른 Servlet) 인스턴스에서 공유 되는 컨텍스트 계층 구조를 가질 수도 있으며 각각 고유한 하위 WebApplicationContext 구성이 있습니다.

루트 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()통해 getRootConfigClasses()및 null에서 모든 구성을 반환할 수 있습니다.

다음 예는 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>

애플리케이션 컨텍스트 계층이 필요하지 않은 경우, 애플리케이션들은 "루트" 컨텍스트만 구성하고 contextConfigLocationServlet 매개변수를 비워 둘 수 있습니다.

1.1.2 Special Bean Types

DispatcherServlet은 요청을 처리하고 적절한 응답을 제공하기 위한 특수한 빈을 위임합니다. "special beans"은 프레임워크 계약을 구현하는 Spring에 의해 관리되는 Object 인스턴스를 의미 합니다. 일반적으로 기본 제공 계약과 함께 제공되지만 속성을 사용자 정의하고 확장하거나 교체할 수 있습니다.

다음 표에서 DispatcherServlet 감지되는 special beans이 나열되어 있습니다.

Bean Type 설명
HandlerMapping 사전 및 사후 처리를 위한 interceptors 목록과 함께 요청을 handler에 매핑합니다. 매핑은 일부 기준을 기반으로 하며 세부 사항은 HandlerMapping 구현에 따라 다릅니다.
      두 가지 주요 <b>HandlerMapping</b> 구현은 <b>RequestMappingHandlerMapping</b>(<b>@RequestMapping</b> annotated 메서드 지원)과 <b>SimpleUrlHandlerMapping</b>(handler에 대한 URI 경로 패턴의 명시적 등록을 유지 관리)입니다.
    </td>
  </tr>
  <tr>
    <td>
      HandlerAdapter
    </td>
    <td>
      핸들러가 실제로 호출되는 방식에 관계없이 <b>DispatcherServlet</b>이 요청에 매핑된 핸들러를 호출하도록 도와주세요. 예를 들어 annotation이 달린 컨트롤러를 호출하려면 annotation을 resolve해야 합니다. <b>HandlerAdapter</b>의 주요 목적은 이러한 세부 사항으로부터 <b>DispatcherServlet</b>을 보호하는 것입니다.
    </td>
  </tr>
  <tr>
    <td>
     HandlerExceptionResolver
    </td>
    <td>
      예외를 해결하기 위한 전략(가능한 경우 핸들러, HTML 오류 뷰 또는 기타 대상에 매핑 가능). <b>Exception</b>를 참조하십시오.
    </td>
  </tr>
      <tr>
    <td>
      ViewResolver
    </td>
    <td>
      핸들러에서 반환된 논리적 문자열 기반 뷰 이름을 응답에 렌더링할 실제 <b>View</b>로 확인합니다. <b>View Resolution</b><b>View Technologies</b>를 참조하십시오.
    </td>
  </tr>
  <tr>
    <td>
      LocaleResolver, LocaleContextResolver
    </td>
    <td>
      국제화된 뷰를 제공할 수 있도록 클라이언트가 사용 중인 Locale과 시간대를 resolve합니다. <b>Locale</b>을 참조하십시오.
    </td>
  </tr>
  <tr>
    <td>
      ThemeResolver
    </td>
    <td>
      예를 들어 개인화된 레이아웃을 제공하기 위해 웹 응용 프로그램에서 사용할 수 있는 테마를 확인합니다. <b>Themes</b>를 참조하십시오.
    </td>
  </tr>
  <tr>
    <td>
      MultipartResolver
    </td>
    <td>
      일부 Multipart parsing 라이브러리의 도움으로 Multipart 요청(예: 브라우저 양식 파일 업로드)을 parsing 하기 위한 추상화. <b>Multipart Resolver</b>를 참조하십시오.
    </td>
  </tr>
  <tr>
    <td>
      FlashMapManager
    </td>
    <td>
      일반적으로 리디렉션을 통해 한 요청에서 다른 요청으로 속성을 전달하는 데 사용할 수 있는 "입력" 및 "출력" <b>FlashMap</b>을 저장하고 검색합니다. <b>Flash Attributes</b>을 참조하십시오.
    </td>
  </tr>

1.1.3 Web MVC Config

애플리케이션은 요청을 처리하는 데 필요한 특수 빈 유형에 나열된 인프라 빈을 선언할 수 있습니다. DispatcherServletWebApplicationContext의 각 special bean을 확인합니다. 일치하는 빈 유형이 없으면 DispatcherServlet.properties.에 나열된 기본 유형으로 대체됩니다

대부분의 경우 MVC 구성 이 가장 좋은 출발점입니다. Java 또는 XML로 필수 빈을 선언하고 이를 사용자 정의하기 위한 상위 수준 구성 콜백 API를 제공합니다.

Spring Boot는 MVC Java 구성에 의존하여 Spring MVC를 구성하고 많은 추가 편리한 옵션을 제공합니다.

1.1.4 Servlet Config

Servlet 3.0+ 환경에서는 Servlet 컨테이너를 대안으로 또는 web.xml파일 과 함께 프로그래밍 방식으로 구성하는 옵션이 있습니다 . 다음 예제에서는 DispatcherServlet를 등록합니다.

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은 구현을 감지하고 자동으로 모든 Servlet 3 컨테이너를 초기화하도록 보장해주는 Spring MVC에서 제공하는 인터페이스입니다. AbstractDispatcherServletInitializer, WebApplicationInitializernamed의 추상 기본 클래스 구현은 서블릿 매핑과 디스패처 서블릿 구성의 위치를 지정하는 방법을 오버라이딩함으로써 DispatcherServlet에 쉽게 등록할 수 있게 해줍니다.

다음 예제와 같이 Java 기반 Spring Configuration을 사용하는 애플리케이션에 권장됩니다.

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 기반 Spring 구성을 사용하는 경우,다음 예제와 같이 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() };
    }
}

각 필터는 구체적인 유형에 따라 기본 이름으로 추가되고 자동으로 DispatcherServlet에 매핑됩니다.

AbstractDispatcherServletInitializer의 protected methode중 하나인 isAsyncSupported은 비동기상의 지원을 가능하게하는 하나의 장소 제공 DispatcherServlet과 이용에 매핑 된 모든 필터를. 기본적으로 이 플래그는 true로 설정됩니다 .

마지막으로 DispatcherServlet자체 를 추가로 사용자 지정해야 하는 경우, createDispatcherServlet메서드를 재정의할 수 있습니다.

1.1.5 Processing

DispatcherServlet은 다음과 같이 요청을 처리합니다.

  • WebApplicationContext는 요청에서 컨트롤러와 프로세스의 다른 요소가 사용할 수 있는 속성으로 검색되고 바인딩됩니다. 기본적으로 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 키 아래에 바인딩됩니다.

  • locale resolver는 프로세스의 요소가 요청을 처리(보기 렌더링, 데이터 준비 등)할 때, 사용할 로케일을 확인하도록 요청에 바인딩됩니다. locale resolve가 필요하지 않으면 로케일 resolver가 필요하지 않습니다.

  • theme resolver는 뷰와 같은 요소가 사용할 테마를 결정하도록 하는 요청에 바인딩됩니다. 테마를 사용하지 않는다면 무시하셔도 됩니다.

  • multipart file resolver를 지정하면 요청에서 멀티파트가 검사됩니다. 멀티파트가 발견되면 프로세스의 다른 요소에서 추가 처리를 위해 요청이 MultipartHttpServletRequest에 래핑됩니다. 멀티파트 처리에 대한 자세한 내용은 Multipart Resolver를 참조하세요.

  • 적절한 핸들러가 검색됩니다. 핸들러가 발견되면 핸들러(전처리기, 후처리기 및 컨트롤러)와 연결된 실행 체인이 실행되어 렌더링을 위한 모델을 준비합니다. 또는 annotation이 달린 컨트롤러의 경우, 뷰를 반환하는 대신 응답을 HandlerAdapter 내에서 렌더링할 수 있습니다.

  • 모델이 반환되면 뷰가 렌더링됩니다. 모델이 반환되지 않으면(보안상의 이유로 요청을 가로채는 전처리기 또는 후처리기로 인해) 요청이 이미 이행되었을 수 있으므로 보기가 렌더링되지 않습니다.

WebApplicationContext에 선언된 HandlerExceptionResolver 빈은 요청 처리 중에 발생한 예외를 해결하는 데 사용됩니다. 이러한 예외 해결 프로그램을 사용하면 예외를 처리하도록 논리를 사용자 지정할 수 있습니다. 자세한 내용은 Exception를 참조하십시오.

HTTP 캐싱 지원을 위해 핸들러는 WebRequestcheckNotModified 메서드와 HTTP Caching for Controller에 설명된 대로 annotation이 달린 컨트롤러에 대한 추가 옵션을 사용할 수 있습니다.

web.xml 파일의 Servlet 선언에 Servlet 초기화 매개변수(init-param 요소)를 추가하여 개별 DispatcherServlet 인스턴스를 사용자 정의할 수 있습니다. 다음 표에는 지원되는 매개변수가 나열되어 있습니다.

Parameter Description
contextClass 이 서블릿에 의해 인스턴스화되고 로컬로 구성될 ConfigurableWebApplicationContext를 구현하는 클래스입니다. 기본적으로 XmlWebApplicationContext가 사용됩니다.
contextConfigLocation 컨텍스트를 찾을 수 있는 위치를 나타내기 위해 컨텍스트 인스턴스(contextClass에 의해 지정됨)에 전달되는 문자열입니다. 문자열은 여러 컨텍스트를 지원하기 위해 잠재적으로 여러 문자열(쉼표를 구분 기호로 사용)으로 구성됩니다. Bean이 두 번 정의된 여러 컨텍스트 위치의 경우 최신 위치가 우선합니다.
namespace WebApplicationContext의 네임스페이스입니다. 기본값은 [서블릿 이름]-서블릿입니다.
throwExceptionIfNoHandlerFound 요청에 대한 처리기를 찾을 수 없는 경우 NoHandlerFoundException을 throw할지 여부입니다. 그런 다음 예외는 HandlerExceptionResolver로 포착되고(예: @ExceptionHandler 컨트롤러 메서드를 사용하여) 다른 것으로 처리될 수 있습니다.
    기본적으로 이것은 <b>false</b>로 설정되며, 이 경우 <b>DispatcherServlet</b>은 예외를 발생시키지 않고 응답 상태를 404(NOT_FOUND)로 설정합니다.

    <b>기본 서블릿 처리</b>도 구성된 경우, 해결되지 않은 요청은 항상 기본 서블릿으로 전달되고 404는 발생하지 않습니다.
  </td>
</tr>

1.1.6 Path Matching

Servlet API는 전체 요청 경로를 requestURI로 표시하고
Servlet이 매핑되는 방식에 따라 값이 달라지는 contextPath, servletPathpathInfo로 더 세분화합니다.
이러한 입력에서 Spring MVC는 contextPathservletMapping 접두사가 있는 경우를 제외하고 DispatcherServlet 자체의 매핑 내 경로인 핸들러 매핑에 사용할 조회 경로를 결정해야 합니다.

servletPathpathInfo가 디코딩되어 lookupPath를 유도하기 위해 전체 requestURI와 직접 비교할 수 없으며 이로 인해 requestURI를 디코딩해야 합니다.
그러나 경로에 "/" 또는 ";"와 같은 인코딩된 예약 문자가 포함될 수 있기 때문에 이 자체로 문제를 야기합니다. 이는 디코딩된 후 경로 구조를 변경하여 보안 문제로 이어질 수 있습니다.
또한 Servlet 컨테이너는 servletPath를 다양한 정도로 정규화하여 requestURI에 대해 startsWith 비교를 수행하는 것을 더 이상 불가능하게 만들 수 있습니다.

이것이 접두사 기반 servletPath 매핑 유형과 함께 제공되는 servletPath에 대한 의존을 피하는 것이 가장 좋은 이유입니다. DispatcherServlet"/"를 사용하여 기본 서블릿으로 매핑되거나 "/*" 접두사가 없고 서블릿 컨테이너가 4.0 이상인 경우 Spring MVC는 서블릿 매핑 유형을 감지하고 servletPathpathInfo의 사용을 모두 피할 수 있습니다.
3.1 서블릿 컨테이너에서 동일한 서블릿 매핑 유형을 가정하면 MVC 구성에서 Path Matching를 통해 UrlPathHelperalwaysUseFullPath=true를 제공하여 동등한 결과를 얻을 수 있습니다.

다행히 기본 Servlet 매핑 "/"이 좋은 선택입니다. 그러나 컨트롤러 매핑과 비교할 수 있도록 requestURI를 디코딩해야 한다는 문제가 있습니다. 이는 경로 구조를 변경하는 예약된 문자를 디코딩할 가능성이 있기 때문에 다시 바람직하지 않습니다. 이러한 문자가 예상되지 않으면 이를 거부하거나(예: Spring Security HTTP 방화벽) urlDecode=falseUrlPathHelper를 구성할 수 있지만 컨트롤러 매핑은 항상 제대로 작동하지 않을 수 있는 인코딩된 경로와 일치해야 합니다. 또한 DispatcherServlet은 URL 공간을 다른 Servlet과 공유해야 하고 접두사로 매핑해야 할 수도 있습니다.

위의 문제는 PathMatcher에서 5.3 이상에서 사용할 수 있는 구문 분석된 PathPattern으로 전환하여 보다 포괄적으로 해결할 수 있습니다. 디코딩된 조회 경로 또는 인코딩된 컨트롤러 매핑이 필요한 AntPathMatcher와 달리 구문 분석된 PathPattern은 한 번에 하나의 경로 세그먼트인 RequestPath라는 경로의 구문 분석된 표현과 일치합니다. 이를 통해 경로 구조를 변경할 위험 없이 경로 세그먼트 값을 개별적으로 디코딩하고 삭제할 수 있습니다. 또한 구문 분석된 PathPattern은 접두사가 단순하게 유지되고 인코딩해야 하는 문자가 없는 한 servletPath 접두사 매핑 사용을 지원합니다.

1.1.7 Interception

모든 HandlerMapping 구현은 특정 요청(예: 보안 주체 확인)에 특정 기능을 적용하려는 경우에 유용한 핸들러 인터셉터를 지원합니다. 인터셉터는 모든 종류의 사전 처리 및 사후 처리를 수행하기에 충분한 유연성을 제공 하는 세 가지 메서드를 org.springframework.web.servlet 패키지에 있는 HandlerInterceptor로 구현해야 합니다.

  • preHandle(..): 실제 핸들러가 실행되기 전

  • postHandle(..): 핸들러 실행 후

  • afterCompletion(..): 완전한 요청이 완료된 후

preHandle(..) 메서드는 부울 값을 반환합니다. 이 메서드를 사용하여 실행 체인의 처리를 중단하거나 계속할 수 있습니다. 이 메서드가 true를 반환하면 핸들러 실행 체인이 계속됩니다. false를 반환하면 DispatcherServlet은 인터셉터 자체가 요청을 처리했다고 가정하고(예: 적절한 보기를 렌더링함) 다른 인터셉터와 실행 체인의 실제 핸들러를 계속 실행하지 않습니다.

인터셉터를 구성하는 방법의 예는 MVC 구성 섹션에서 인터셉터를 참조하십시오. 개별 HandlerMapping 구현에서 setter를 사용하여 직접 등록할 수도 있습니다.

postHandle은 응답이 HandlerAdapter 내에서 postHandle 이전에 작성되고 커밋되는 @ResponseBodyResponseEntity 메소드에서 덜 유용합니다. 즉, 추가 헤더를 추가하는 것과 같이 응답을 변경하기에는 너무 늦었습니다. 이러한 시나리오의 경우ResponseBodyAdvice를 구현하고 이를 Controller Advice 빈으로 선언하거나 RequestMappingHandlerAdapter에서 직접 구성할 수 있습니다.

1.1.8 Exceptions

요청 매핑 중에 예외가 발생하거나 @Controller와 같은 요청 처리기에서 예외가 발생하면 DispatcherServlet은 예외를 해결하고 일반적으로 오류 응답인 대체 처리를 제공하기 위해 HandlerExceptionResolver 빈 체인에 위임합니다.

다음 표에는 사용 가능한 HandlerExceptionResolver 구현이 나와 있습니다.

HandlerExceptionResolver Description
SimpleMappingExceptionResolver 예외 클래스 이름과 오류 뷰 이름 간의 매핑. 브라우저 응용 프로그램에서 오류 페이지를 렌더링하는 데 유용합니다.
DefaultHandlerExceptionResolver Spring MVC에서 발생한 예외를 해결하고 이를 HTTP 상태 코드에 매핑합니다. ResponseEntityExceptionHandlerREST API 예외를 참조하십시오.
ResponseStatusExceptionResolver @ResponseStatus annotation으로 예외를 해결하고 annotation으로의 값을 기반으로 HTTP 상태 코드에 매핑합니다.
ExceptionHandlerExceptionResolver @Controller 또는 @ControllerAdvice 클래스에서 @ExceptionHandler 메서드를 호출하여 예외를 해결합니다. @ExceptionHandler 메소드를 참조하십시오.

Chain of Resolvers

Spring 구성에서 여러 HandlerExceptionResolver 빈을 선언하고 필요에 따라 순서 속성을 설정하여 예외 resolver 체인을 형성할 수 있습니다. order 속성이 높을수록 예외 resolver가 나중에 배치됩니다.

HandlerExceptionResolver의 contract은 다음을 반환할 수 있다고 지정합니다.

  • 오류 보기를 가리키는ModelAndView

  • Resolver 내에서 예외가 처리된 경우 빈 ModelAndView

  • null, 예외가 해결되지 않은 상태로 남아 있으면 후속 Resolver가 시도할 수 있도록 하고 예외가 끝에 남아 있으면 Servlet 컨테이너까지 버블링할 수 있습니다.

MVC Config는 기본 Spring MVC 예외, @ResponseStatus annotatie된 예외 및 @ExceptionHandler 메소드 지원을 위한 내장 resolver를 자동으로 선언합니다. 해당 목록을 사용자 지정하거나 바꿀 수 있습니다.

Container Error Page

예외가 HandlerExceptionResolver에 의해 해결되지 않은 상태로 남아 있어 전파하도록 남겨두거나 응답 상태가 오류 상태(즉, 4xx, 5xx)로 설정된 경우, 서블릿 컨테이너는 HTML에서 기본 오류 페이지를 렌더링할 수 있습니다. 컨테이너의 기본 오류 페이지를 사용자 지정하려면 web.xml에서 오류 페이지 매핑을 선언할 수 있습니다. 다음 예에서는 그렇게 하는 방법을 보여줍니다.

<error-page>
    <location>/error</location>
</error-page>

앞의 예에서 예외가 발생하거나 응답에 오류 상태가 있는 경우 서블릿 컨테이너는 컨테이너 내에서 구성된 URL(예: /error)로 ERROR 디스패치를 만듭니다. 그런 다음 DispatcherServlet에 의해 처리되어 @Controller에 매핑될 수 있으며, 다음 예제와 같이 모델과 함께 오류 보기 이름을 반환하거나 JSON 응답을 렌더링하도록 구현할 수 있습니다.

@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}

Servlet API는 Java에서 오류 페이지 매핑을 생성하는 방법을 제공하지 않습니다. 그러나 WebApplicationInitializer와 최소한의 web.xml을 모두 사용할 수 있습니다.

1.1.9 View Resolution

Spring MVC는 특정 뷰 기술에 얽매이지 않고 브라우저에서 모델을 렌더링할 수 있게 해주는 ViewResolverView 인터페이스를 정의합니다. ViewResolver는 뷰 이름과 실제 뷰 간의 매핑을 제공합니다. View는 특정 보기 기술에 넘겨주기 전에 데이터를 준비합니다.

다음 표에서는 ViewResolver계층 구조 에 대한 자세한 내용을 제공합니다.

ViewResolver Description
AbstractCachingViewResolver AbstractCachingViewResolver는 캐시 뷰 인스턴스를 resolve하는 서브클래스입니다. 캐싱은 특정 보기 기술의 성능을 향상시킵니다. cache 속성을 false로 설정하여 캐시를 끌 수 있습니다. 또한 런타임에 특정 뷰를 새로 고쳐야 하는 경우(예: FreeMarker 템플릿이 수정된 경우) removeFromCache(String viewName, Locale loc) 메서드를 사용할 수 있습니다.
UrlBasedViewResolver 명시적 매핑 정의 없이 URL에 대한 논리적 뷰 이름의 직접 확인에 영향을 주는 ViewResolver 인터페이스의 간단한 구현입니다. 논리적 이름이 임의의 매핑 없이 간단한 방식으로 보기 리소스의 이름과 일치하는 경우에 적합합니다.
InternalResourceViewResolver InternalResourceView(사실상, Servlet 및 JSP)와 JstlViewTilesView와 같은 하위 클래스를 지원하는 UrlBasedViewResolver의 편리한 하위 클래스. setViewClass(..)를 사용하여 이 resolver에 의해 생성된 모든 뷰에 대한 뷰 클래스를 지정할 수 있습니다. 자세한 내용은 UrlBasedViewResolver javadoc을 참조하세요.
FreeMarkerViewResolver FreeMarkerView를 지원하는 UrlBasedViewResolver의 편리한 서브클래스와 그 서브클래스.
ContentNegotiatingViewResolver 요청 파일 이름 또는 Accept 헤더를 기반으로 보기를 확인하는 ViewResolver 인터페이스의 구현입니다. Content Negotiation을 참조하십시오.
BeanNameViewResolver 현재 애플리케이션 컨텍스트에서 뷰 이름을 빈 이름으로 해석하는 ViewResolver 인터페이스의 구현. 이것은 고유한 뷰 이름을 기반으로 다양한 뷰 유형을 혼합하고 일치시킬 수 있는 매우 유연한 변형입니다. 이러한 각 뷰는 빈(예. XML 또는 구성 클래스)으로 정의할 수 있습니다.

Handling

둘 이상의 resolver Bean을 선언하고 필요한 경우 order 속성을 설정하여 순서를 지정하여 view resolver를 연결할 수 있습니다. order 속성이 높을수록 뷰 리졸버가 체인에서 더 늦게 위치한다는 것을 기억하십시오.

ViewResolver의 contract는 뷰를 찾을 수 없음을 나타내기 위해 null을 반환할 수 있음을 지정합니다. 그러나 JSP와 InternalResourceViewResolver의 경우 JSP가 있는지 확인하는 유일한 방법은 RequestDispatcher를 통해 디스패치를 수행하는 것입니다. 따라서 항상 InternalResourceViewResolver를 전체 view resolver 순서에서 마지막으로 구성해야 합니다.

view resolution을 구성하는 것은 Spring configuration에 ViewResolver 빈을 추가하는 것만큼 간단합니다. MVC ConfigView Resolver와 컨트롤러 로직 없이 HTML 템플릿 렌더링에 유용한 로직이 없는 View Controller를 추가하기 위한 전용 configuration API를 제공합니다.

Redirecting

뷰 이름에 특수 redirect: 접두사를 사용하면 리디렉션을 수행할 수 있습니다. UrlBasedViewResolver(및 해당 하위 클래스)는 이를 리디렉션이 필요하다는 명령으로 인식합니다. 나머지 뷰 이름은 리디렉션 URL입니다.

순 효과는 컨트롤러가 RedirectView를 반환한 것과 같지만 이제 컨트롤러 자체가 논리적 뷰 이름으로 작동할 수 있습니다. 논리적 뷰 이름(예: redirect:/myapp/some/resource)은 현재 서블릿 컨텍스트를 기준으로 리디렉션되는 반면, redirect:https://myhost.com/some/arbitrary/path와 같은 이름은 절대 URL로 리디렉션됩니다.

컨트롤러 메소드에 @ResponseStatus가 annotated된 경우, annotation이 RedirectView에서 설정한 응답 상태보다 우선합니다.

Forwarding

UrlBasedViewResolver 및 하위 클래스에 의해 궁극적으로 확인되는 보기 이름에 특수 forward: 접두사를 사용할 수도 있습니다. 이렇게 하면 RequestDispatcher.forward()를 수행하는 InternalResourceView가 생성됩니다. 따라서 이 접두사는 InternalResourceViewResolverInternalResourceView(JSP용)에서는 유용하지 않지만 다른 보기 기술을 사용하지만 Servlet/JSP 엔진에서 처리하도록 리소스를 강제로 전달하려는 경우에 유용할 수 있습니다. 대신 여러 view resolver를 연결할 수도 있습니다.

Content Negotiation

ContentNegotiatingViewResolver는 뷰 자체를 확인하지 않고 다른 view resolver에게 위임하고 클라이언트가 요청한 표현과 유사한 뷰를 선택합니다. 표현은 Accept 헤더 또는 쿼리 매개변수(예: "/path?format=pdf")에서 결정할 수 있습니다.

ContentNegotiatingViewResolver는 요청 미디어 유형을 각 ViewResolver와 연결된 View에서 지원하는 미디어 유형(Content-Type이라고도 함)과 비교하여 요청을 처리할 적절한 View를 선택합니다. 호환 가능한 Content-Type이 있는 목록의 첫 번째 View는 표현을 클라이언트에 반환합니다. ViewResolver 체인에서 호환 가능한 뷰를 제공할 수 없는 경우, DefaultViews 속성을 통해 지정된 뷰 목록을 참조합니다. 이 후자의 옵션은 논리적 뷰 이름에 관계없이 현재 리소스의 적절한 표현을 렌더링할 수 있는 싱글톤 View에 적합합니다. Accept 헤더에는 와일드카드(예: text/*)가 포함될 수 있으며, 이 경우 Content-Typetext/xmlView는 호환 가능한 일치 항목입니다.

구성에 대한 자세한 내용은 MVC Config에서 View Resolvers를 참조하십시오.

1.1.10 Locale

Spring 웹 MVC 프레임워크가 지원하는 것처럼 Spring 아키텍처의 대부분의 부분은 국제화를 지원합니다. DispatcherServlet을 사용하면 클라이언트의 로케일을 사용하여 메시지를 자동으로 해결할 수 있습니다. 이것은 LocaleResolver 객체로 수행됩니다.

요청이 들어오면 DispatcherServlet은 Locale Resolver를 찾고 찾으면 이를 사용하여 로케일을 설정하려고 합니다. RequestContext.getLocale() 메소드를 사용하여 Locale Resolver에 의해 resolve된 로케일을 항상 검색할 수 있습니다.

자동 로케일 확인 외에도 인터셉터를 핸들러 매핑에 연결(핸들러 매핑 인터셉터에 대한 자세한 내용은 인터셉터 참조)하여 특정 상황(예: 요청의 매개변수 기반)에서 로케일을 변경할 수도 있습니다.

Locale Resolver와 인터셉터는 org.springframework.web.servlet.i18n 패키지에 정의되어 있으며 일반적인 방식으로 애플리케이션 컨텍스트에서 구성됩니다. Spring에는 다음과 같은 로케일 리졸버 선택이 포함되어 있습니다.

Time Zone

클라이언트의 로케일을 얻는 것 외에도 시간대를 아는 것이 종종 유용합니다. LocaleContextResolver 인터페이스는 해석기가 시간대 정보를 포함할 수 있는 더 풍부한 LocaleContext를 제공할 수 있도록 하는 LocaleResolver에 대한 확장을 제공합니다.

사용 가능한 경우 사용자의 TimeZoneRequestContext.getTimeZone() 메서드를 사용하여 얻을 수 있습니다. 시간대 정보는 Spring의 ConversionService에 등록된 모든 Date/Time ConverterFormatter 객체에 의해 자동으로 사용됩니다.

Header Resolver

이 Locale Resolver는 클라이언트(예: 웹 브라우저)에서 보낸 요청의 accept-language 헤더를 검사합니다. 일반적으로 이 헤더 필드에는 클라이언트 운영 체제의 로케일이 포함됩니다. 이 resolver는 표준 시간대 정보를 지원하지 않습니다.

Cookie Resolver

이 Locale Resolver는 클라이언트에 존재할 수 있는 Cookie를 검사하여 Locale 또는 TimeZone가 지정되었는지 확인합니다. 그렇다면 지정된 세부 정보를 사용합니다. 이 Locale Resolver의 속성을 사용하여 쿠키의 이름과 최대 사용 기간을 지정할 수 있습니다. 다음 예제는 CookieLocaleResolver를 정의합니다.

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <property name="cookieName" value="clientlanguage"/>

    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge" value="100000"/>

</bean>

다음 표에서는 CookieLocaleResolver 속성을 설명합니다.

Property Default Description
cookieName classname + LOCALE 쿠키의 이름
cookieMaxAge Servlet container default 쿠키가 클라이언트에서 지속되는 최대 시간입니다. -1을 지정하면 쿠키가 유지되지 않습니다. 클라이언트가 브라우저를 종료할 때까지만 사용할 수 있습니다.
cookiePath / 쿠키의 가시성을 사이트의 특정 부분으로 제한합니다. cookiePath가 지정되면 쿠키는 해당 경로와 그 아래 경로에만 표시됩니다.

Session Resolver

SessionLocaleResolver를 사용하면 사용자의 요청과 관련될 수 있는 세션에서 LocaleTimeZone을 검색할 수 있습니다. CookieLocaleResolver와 달리 이 전략은 로컬에서 선택한 로케일 설정을 서블릿 컨테이너의 HttpSession에 저장합니다. 결과적으로 이러한 설정은 각 세션에 대해 일시적이므로 각 세션이 종료될 때 손실됩니다.

Spring Session 프로젝트와 같은 외부 세션 관리 메커니즘과 직접적인 관계가 없다는 점에 유의하십시오. 이 SessionLocaleResolver는 현재 HttpServletRequest에 대해 해당 HttpSession 속성을 평가하고 수정합니다.

Locale Interceptor

HandlerMapping 정의 중 하나에 LocaleChangeInterceptor를 추가하여 로케일 변경을 활성화할 수 있습니다. 요청에서 매개변수를 감지하고 그에 따라 로케일을 변경하여 디스패처의 애플리케이션 컨텍스트에서 LocaleResolversetLocale 메소드를 호출합니다. 다음 예는 siteLanguage라는 매개변수를 포함하는 모든 *.view 리소스에 대한 호출이 이제 로케일을 변경함을 보여줍니다. 예를 들어, URL https://www.sf.net/home.view?siteLanguage=nl에 대한 요청은 사이트 언어를 네덜란드어로 변경합니다. 다음 예는 로케일을 가로채는 방법을 보여줍니다.

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

1.1.11 Themes

Spring Web MVC 프레임워크 테마를 적용하여 애플리케이션의 전체적인 모양과 느낌을 설정함으로써 사용자 경험을 향상시킬 수 있습니다. 테마는 응용 프로그램의 시각적 스타일에 영향을 주는 정적 리소스(일반적으로 스타일 시트 및 이미지)의 모음입니다.

테마 정의
웹 애플리케이션에서 테마를 사용하려면 org.springframework.ui.context.ThemeSource 인터페이스의 구현을 설정해야 합니다. WebApplicationContext 인터페이스는 ThemeSource를 확장하지만 그 책임을 전용 구현에 위임합니다. 기본적으로 대리자는 클래스 경로의 루트에서 속성 파일을 로드하는 org.springframework.ui.context.support.ResourceBundleThemeSource 구현입니다. 사용자 정의 ThemeSource 구현을 사용하거나 ResourceBundleThemeSource의 기본 이름 접두사를 구성하려면 예약된 이름인 themeSource를 사용하여 애플리케이션 컨텍스트에 빈을 등록할 수 있습니다. 웹 애플리케이션 컨텍스트는 해당 이름의 빈을 자동으로 감지하여 사용합니다.

ResourceBundleThemeSource를 사용할 때 테마는 단순 속성 파일에 정의됩니다. 속성 파일은 다음 예제와 같이 테마를 구성하는 리소스를 나열합니다.

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

속성의 키는 뷰 코드에서 테마 요소를 참조하는 이름입니다. JSP의 경우 일반적으로 spring:message 태그와 매우 유사한 spring:theme 사용자 정의 태그를 사용하여 이를 수행합니다. 다음 JSP fragment는 이전 예제에서 정의한 테마를 사용하여 모양과 느낌을 사용자 정의합니다.

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
        ...
    </body>
</html>

기본적으로 ResourceBundleThemeSource는 비어있는 기본 이름 접두사를 사용합니다. 결과적으로 속성 파일은 클래스 경로의 루트에서 로드됩니다. 따라서 cool.properties 테마 정의를 클래스 경로의 루트에 있는 디렉토리(예: /WEB-INF/classes)에 넣습니다. ResourceBundleThemeSource는 표준 Java 리소스 번들 로딩 메커니즘을 사용하여 테마의 완전한 국제화를 허용합니다. 예를 들어 네덜란드어 텍스트가 있는 특수 배경 이미지를 참조하는 /WEB-INF/classes/cool_nl.properties가 있을 수 있습니다.

테마 결정

이전 섹션에서 설명한 대로 테마를 정의한 후 사용할 테마를 결정합니다. DispatcherServlet은 사용할 ThemeResolver 구현을 찾기 위해 themeResolver라는 빈을 찾습니다. theme resolver는 LocaleResolver와 거의 같은 방식으로 작동합니다. 특정 요청에 사용할 테마를 감지하고 요청의 테마를 변경할 수도 있습니다. 다음 표에서는 Spring에서 제공하는 theme resolver를 설명합니다.

Class Description
FixedThemeResolver defaultThemeName 속성을 사용하여 설정한 고정 테마를 선택합니다.
SessionThemeResolver 테마는 사용자의 HTTP 세션에서 유지됩니다. 각 세션에 대해 한 번만 설정해야 하지만 세션 간에 유지되지 않습니다.
CookieThemeResolver 선택한 테마는 클라이언트의 쿠키에 저장됩니다.

Spring은 또한 간단한 요청 매개변수로 모든 요청에 대한 테마 변경을 허용하는 ThemeChangeInterceptor를 제공합니다.

1.1.12 Multipart Resolver

org.springframework.web.multipart 패키지의 MultipartResolver는 파일 업로드를 포함한 멀티파트 요청을 구문 분석하기 위한 전략입니다. Commons FileUpload를 기반으로 하는 구현과 Servlet 3.0 멀티파트 요청 구문 분석을 기반으로 하는 구현이 있습니다.

멀티파트 처리를 활성화하려면 DispatcherServlet Spring Configuration에서 multipartResolver라는 이름으로 MultipartResolver 빈을 선언해야 합니다. DispatcherServlet은 이를 감지하여 들어오는 요청에 적용합니다. 콘텐츠 유형이 multipart/form-data인 POST가 수신되면 확인자는 콘텐츠를 구문 분석하여 현재 HttpServletRequestMultipartHttpServletRequest로 래핑하여 부분을 요청 매개변수로 노출하는 것 외에도 확인된 파일에 대한 액세스를 제공합니다.

Apache Commons FileUpload
Apache Commons FileUpload를 사용하려면 이름이 multipartResolverCommonsMultipartResolver 유형의 빈을 구성할 수 있습니다. 또한 클래스 경로에 대한 종속성으로 commons-fileupload jar가 있어야 합니다.

이 resolver 변형은 응용 프로그램 내의 로컬 라이브러리에 위임하여 서블릿 컨테이너 간에 최대한의 이식성을 제공합니다. 대안으로 아래에 설명된 대로 컨테이너 자체 파서를 통한 표준 서블릿 멀티파트 해결을 고려하십시오.

Commons FileUpload는 전통적으로 POST 요청에만 적용되지만 모든 multipart/ 콘텐츠 유형을 허용합니다. 자세한 내용과 구성 옵션은 CommonsMultipartResolver javadoc을 참조하십시오.

Servlet 3.0

Servlet 3.0 멀티파트 구문 분석은 Servlet 컨테이너 구성을 통해 활성화해야 합니다.
이렇게 하려면:

  • Java에서 Servlet 등록에 MultipartConfigElement를 설정합니다.

  • web.xml에서 "<multipart-config>" 섹션을 서블릿 선언에 추가합니다.

다음 예는 서블릿 등록에서 MultipartConfigElement를 설정하는 방법을 보여줍니다.

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

}

Servlet 3.0 구성이 준비되면 이름이 multipartResolverStandardServletMultipartResolver 유형의 빈을 추가할 수 있습니다.

이 resolver 변형은 서블릿 컨테이너의 멀티파트 파서를 있는 그대로 사용하여 잠재적으로 애플리케이션을 컨테이너 구현 차이에 노출시킵니다. 기본적으로 HTTP 메소드로 모든 multipart/ 컨텐츠 유형을 구문 분석하려고 시도하지만 모든 서블릿 컨테이너에서 지원되지 않을 수 있습니다. 세부사항 및 구성 옵션은 StandardServletMultipartResolver javadoc을 참조하십시오.

1.1.13 Logging

Spring MVC의 DEBUG 레벨 로깅은 컴팩트하고 최소이며 인간 친화적으로 설계되었습니다. 특정 문제를 디버깅할 때만 유용한 정보와 반복해서 유용한 고가치 정보에 중점을 둡니다.

TRACE 수준 로깅은 일반적으로 DEBUG와 동일한 원칙을 따르지만(예를 들어 소방 호스가 아니어야 함) 모든 문제를 디버깅하는 데 사용할 수 있습니다. 또한 일부 로그 메시지는 TRACE와 DEBUG에서 다른 수준의 세부 정보를 표시할 수 있습니다.

좋은 로깅은 로그를 사용한 경험에서 나옵니다. 명시된 목표를 충족하지 못하는 것을 발견하면 저희에게 알려주십시오.

민감한 정보

DEBUG 및 TRACE 로깅은 민감한 정보를 기록할 수 있습니다. 이것이 요청 매개변수와 헤더가 기본적으로 마스킹되고 전체 로깅이 DispatcherServletenableLoggingRequestDetails 속성을 통해 명시적으로 활성화되어야 하는 이유입니다.

다음 예에서는 Java Configuration을 사용하여 이를 수행하는 방법을 보여줍니다.

public class MyInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

    @Override
    protected String[] getServletMappings() {
        return ... ;
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        registration.setInitParameter("enableLoggingRequestDetails", "true");
    }

}
profile
유병우

0개의 댓글