공식문서는 Dispatcher Servlet에 대해서 뭐라고 할까요?
이번글은 공식 문서에서 소개하는 DispatchServlet에 대해 정리했습니다.
정리 범위 : Processing ~ view Resolution
Processing
The DispatcherServlet processes requests as follows:
DispatcherServlet은 요청을 다음과 같이 처리합니다:
- The WebApplicationContext is searched for and bound in the request as an attribute that the controller and other elements in the process can use. It is bound by default under the DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE key.
WebApplicationContext는 검색되어, 요청 내의 속성으로 바인딩되며, 컨트롤러와 프로세스 내의 다른 요소들이 이를 사용할 수 있습니다. 기본적으로 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 키 아래에 바인딩됩니다.
추가 설명
DispatcherServlet은 웹 애플리케이션의 핵심 구성 정보를 담은 WebApplicationContext를 요청의 일부로 만들어, 요청을 처리하는 모든 컴포넌트가 이를 공유하고 활용할 수 있도록 하는 역할을 한다는 의미입니다.
- The locale resolver is bound to the request to let elements in the process resolve the locale to use when processing the request (rendering the view, preparing data, and so on). If you do not need locale resolving, you do not need the locale resolver.
요청을 처리할 때, 뷰 렌더링이나 데이터 준비 등에서 사용할 로케일(locale)을 결정할 수 있도록 로케일 리졸버가 요청에 바인딩됩니다. 만약 로케일 결정 기능이 필요 없다면, 로케일 리졸버를 사용하지 않아도 됩니다.
로케일 리졸버는 다국어 지원 등에서 특정 언어나 지역에 맞는 처리를 할 때 유용한 도구지만, 그렇지 않다면 생략할 수 있다는 뜻입니다.
- The theme resolver is bound to the request to let elements such as views determine which theme to use. If you do not use themes, you can ignore it.
테마 리졸버는 뷰와 같은 컴포넌트가 어떤 테마를 사용할지 결정할 수 있도록 HTTP 요청에 연결됩니다. 만약 애플리케이션에서 테마 기능을 사용하지 않는다면, 이 기능은 신경 쓰지 않아도 됩니다.
- If you specify a multipart file resolver, the request is inspected for multiparts. If multiparts are found, the request is wrapped in a MultipartHttpServletRequest for further processing by other elements in the process. See Multipart Resolver for further information about multipart handling.
만약 multipart file resolver를 지정하면, HTTP 요청에서 여러 파트(파일 등)가 포함되어 있는지 검사합니다. 만약 여러 파트가 발견되면, 요청은 MultipartHttpServletRequest로 감싸져서 이후 프로세스의 다른 요소들이 이를 처리할 수 있도록 합니다. multipart 처리에 대한 자세한 내용은 Multipart Resolver를 참고하면 됩니다.
- An appropriate handler is searched for. If a handler is found, the execution chain associated with the handler (preprocessors, postprocessors, and controllers) is run to prepare a model for rendering. Alternatively, for annotated controllers, the response can be rendered (within the HandlerAdapter) instead of returning a view.
적절한 핸들러를 검색합니다. 만약 핸들러를 찾으면, 해당 핸들러에 연결된 실행 체인(전처리기, 후처리기, 그리고 컨트롤러)을 실행하여 렌더링할 모델을 준비합니다. 또는 어노테이션 기반 컨트롤러의 경우, 뷰를 반환하는 대신 HandlerAdapter 내에서 직접 응답을 렌더링할 수도 있습니다.
- If a model is returned, the view is rendered. If no model is returned (maybe due to a preprocessor or postprocessor intercepting the request, perhaps for security reasons), no view is rendered, because the request could already have been fulfilled.
모델이 반환되면, 해당 모델을 사용해 뷰가 렌더링됩니다. 반면, 모델이 반환되지 않는 경우(예를 들어, 전처리기나 후처리기가 보안 등의 이유로 요청을 가로챈 경우)에는, 요청이 이미 완료되었을 수 있으므로 뷰가 렌더링되지 않습니다.
The HandlerExceptionResolver beans declared in the WebApplicationContext are used to resolve exceptions thrown during request processing. Those exception resolvers allow customizing the logic to address exceptions. See Exceptions for more details.
WebApplicationContext에 선언된 HandlerExceptionResolver 빈들은 요청 처리 중 발생하는 예외들을 해결하는 데 사용됩니다. 이 예외 해결기들을 통해, 예외 처리 로직을 개발자가 원하는 방식으로 사용자 정의(customize)할 수 있습니다. 자세한 내용은 Exceptions 항목을 참고하세요.
For HTTP caching support, handlers can use the checkNotModified methods of WebRequest, along with further options for annotated controllers as described in HTTP Caching for Controllers.
You can customize individual DispatcherServlet instances by adding Servlet initialization parameters (init-param elements) to the Servlet declaration in the web.xml file. The following table lists the supported parameters:
각각의 DispatcherServlet 인스턴스는 web.xml 파일 내의 서블릿 선언에 Servlet 초기화 파라미터(init-param 요소)를 추가함으로써 사용자 정의할 수 있습니다. 다음 표는 지원되는 파라미터들을 나열합니다.
contextClass
ConfigurableWebApplicationContext를 구현하는 클래스로, 이 서블릿에 의해 인스턴스화되고 로컬로 구성됩니다. 기본값은 XmlWebApplicationContext입니다.
contextConfigLocation
contextClass에서 지정한 컨텍스트 인스턴스에 전달되는 문자열로, 컨텍스트를 찾을 수 있는 위치를 나타냅니다. 이 문자열은 여러 컨텍스트를 지원하기 위해 쉼표(,)로 구분된 여러 문자열로 구성될 수 있습니다. 만약 여러 위치에서 동일한 빈이 정의된다면, 가장 나중에 지정된 위치가 우선됩니다.
namespace
WebApplicationContext의 네임스페이스를 지정합니다. 기본값은 [servlet-name]-servlet입니다.
throwExceptionIfNoHandlerFound
요청에 대해 핸들러를 찾지 못할 경우 NoHandlerFoundException을 발생시킬지 여부를 결정합니다. 발생한 예외는 HandlerExceptionResolver(예: @ExceptionHandler 어노테이션이 적용된 컨트롤러 메서드)를 통해 다른 예외와 같이 처리될 수 있습니다.
6.1 버전부터 이 속성은 true로 설정되며, 더 이상 사용되지 않습니다.
또한, 기본 서블릿 처리도 구성되어 있는 경우, 해결되지 않은 요청은 항상 기본 서블릿으로 전달되어 404 오류가 발생하지 않습니다.
추가 설명
이 설정들은 스프링 MVC 애플리케이션에서 DispatcherServlet의 동작 방식을 세밀하게 제어하고자 할 때 사용합니다.
애플리케이션 컨텍스트 구성 변경: 기본적으로 XmlWebApplicationContext를 사용하지만, 다른 방식의 WebApplicationContext를 사용하고 싶을 때 contextClass를 설정할 수 있습니다.
빈 구성 파일 위치 지정: 여러 개의 설정 파일을 사용할 경우, contextConfigLocation을 통해 특정 위치(또는 여러 위치)를 지정해 빈 설정 파일을 로드할 수 있습니다.
네임스페이스 지정: 여러 DispatcherServlet 인스턴스가 있을 때, 각 서블릿별로 고유한 네임스페이스를 지정하여 서로의 설정이 겹치지 않도록 할 수 있습니다.
예외 처리 방식 조정: 요청에 대해 핸들러를 찾지 못한 경우 NoHandlerFoundException을 발생시켜, 사용자 정의 예외 처리 로직(@ExceptionHandler 등)을 적용하고 싶을 때 throwExceptionIfNoHandlerFound 옵션을 사용합니다.
즉, DispatcherServlet의 초기화 및 동작 방식을 세부적으로 설정하고자 할 때 이러한 init-param 요소들을 활용합니다.
Path Matching
The Servlet API exposes the full request path as requestURI and further sub-divides it into contextPath, servletPath, and pathInfo whose values vary depending on how a Servlet is mapped. From these inputs, Spring MVC needs to determine the lookup path to use for mapping handlers, which should exclude the contextPath and any servletMapping prefix, if applicable.
서블릿 API는 전체 요청 경로를 requestURI로 제공하며, 이를 contextPath, servletPath, 그리고 pathInfo로 나누어 줍니다. 이 세부 값들은 서블릿이 어떻게 매핑되었느냐에 따라 달라집니다. 스프링 MVC는 이러한 입력값들을 이용해 핸들러 매핑에 사용할 lookup path를 결정하는데, 이 lookup path에서는 contextPath와(해당되는 경우) 서블릿 매핑 접두사는 제외됩니다.
즉, 요청 처리 시 스프링 MVC는 실제 비즈니스 로직에 연결할 핸들러를 찾기 위해, 전체 경로에서 불필요한 부분(컨텍스트 경로와 서블릿 접두사)을 제거한 순수한 경로 정보를 추출하게 됩니다.
서블릿이 요청받은 URL 경로를 어떻게 세분화하는지와, Spring MVC가 이 정보를 어떻게 활용하는지를 설명합니다.
전체 요청 경로와 세분화: 서블릿 API는 클라이언트가 보낸 전체 URL 경로(requestURI)를 제공합니다. 이 전체 경로는 다시 contextPath(애플리케이션의 루트 경로) servletPath (서블릿이 매핑된 경로) pathInfo (추가적인 경로 정보)로 나누어집니다.
이 값들은 서블릿이 어떻게 매핑되었느냐에 따라 달라집니다.
Spring MVC의 핸들러 매핑: Spring MVC는 요청을 처리할 컨트롤러(핸들러)를 찾기 위해, 이 전체 경로에서 실제 필요한 부분만 사용해야 합니다. 그래서 애플리케이션 루트(contextPath)와 서블릿 매핑(servletPath) 같은 불필요한 부분을 제거하고, 남은 순수한 경로(lookup path)를 사용하여 어떤 핸들러가 이 요청을 처리할지 결정합니다.
즉, 클라이언트가 보낸 전체 URL에서 필요 없는 앞부분을 제거한 후, 실제 비즈니스 로직을 담당하는 핸들러를 찾는 데 필요한 경로 정보를 추출하는 과정을 설명하는 것입니다.
The servletPath and pathInfo are decoded and that makes them impossible to compare directly to the full requestURI in order to derive the lookupPath and that makes it necessary to decode the requestURI. However this introduces its own issues because the path may contain encoded reserved characters such as "/" or ";" that can in turn alter the structure of the path after they are decoded which can also lead to security issues. In addition, Servlet containers may normalize the servletPath to varying degrees which makes it further impossible to perform startsWith comparisons against the requestURI.
servletPath와 pathInfo는 디코딩되기 때문에 lookupPath를 도출하기 위해 전체 requestURI와 직접 비교할 수 없으며, 그로 인해 requestURI를 디코딩해야 합니다. 하지만, 이는 경로에 "/"나 ";"와 같은 인코딩된 예약 문자가 포함될 수 있어, 디코딩 후 경로 구조가 변경될 수 있으며, 이는 보안 문제로 이어질 수 있습니다. 또한, 서블릿 컨테이너는 servletPath를 서로 다른 정도로 정규화할 수 있어, requestURI에 대해 startsWith 비교를 수행하는 것이 더욱 불가능해집니다.
이 문장은 스프링 MVC가 핸들러를 찾기 위해 URL 경로를 처리할 때 발생하는 문제들을 설명합니다.
첫째, 서블릿 API는 servletPath와 pathInfo를 디코딩된 상태로 제공하므로, 이 값들을 전체 요청 URI(requestURI)와 직접 비교할 수 없습니다. 이에 따라 전체 requestURI도 디코딩해야 하는데, 이 과정에서 문제가 발생할 수 있습니다.
둘째, 경로에 "/"나 ";"와 같은 인코딩된 예약 문자가 포함되어 있을 경우, 디코딩 후 경로 구조가 변경되어 URL의 의미가 달라지거나 보안상의 위험이 생길 수 있습니다.
셋째, 서블릿 컨테이너는 servletPath를 서로 다르게 정규화할 수 있으므로, requestURI와의 startsWith와 같은 단순 문자열 비교가 어려워집니다.
즉, URL 디코딩과 정규화 과정에서 발생하는 문제들 때문에 핸들러 매핑에 필요한 lookupPath를 도출하는 과정이 복잡해지며, 보
안상의 문제까지 야기될 수 있습니다.
URL 인코딩(URL encoding) 방식으로 변환된 문자들을 원래의 형태로 복원하는 것을 의미합니다. 예를 들어, URL에 포함된 공백은 “%20”으로 인코딩되는데, 이를 디코딩하면 다시 공백 문자로 변환되는 것입니다.
예를 들어, URL에 "Hello World"를 표현할 때 공백이 "%20"으로 인코딩되어 "Hello%20World"가 됩니다. 이 URL을 디코딩하면 다시 "Hello World"로 복원됩니다.
This is why it is best to avoid reliance on the servletPath which comes with the prefix-based servletPath mapping type. If the DispatcherServlet is mapped as the default Servlet with "/" or otherwise without a prefix with "/*" and the Servlet container is 4.0+ then Spring MVC is able to detect the Servlet mapping type and avoid use of the servletPath and pathInfo altogether. On a 3.1 Servlet container, assuming the same Servlet mapping types, the equivalent can be achieved by providing a UrlPathHelper with alwaysUseFullPath=true via Path Matching in the MVC config.
이는 prefix 기반의 servletPath 매핑 타입에 의존하지 않는 것이 최선인 이유를 설명합니다.
만약 DispatcherServlet이 "/"와 같이 default 서블릿으로 매핑되거나, "/*"와 같이 prefix 없이 매핑되고 서블릿 컨테이너가 4.0 이상이라면, 스프링 MVC가 서블릿 매핑 유형을 감지하여 servletPath와 pathInfo의 사용을 전혀 피할 수 있습니다. 반면, 3.1 서블릿 컨테이너의 경우 동일한 서블릿 매핑 타입을 가정하면, MVC 설정에서 항상 전체 경로를 사용하도록(alwaysUseFullPath=true) UrlPathHelper를 제공하여 동일한 효과를 얻을 수 있습니다.
스프링 MVC가 URL 경로 정보를 해석할 때, prefix 기반의 servletPath 매핑 방식에 의존하는 것을 피하는 것이 좋다는 점을 설명합니다.
즉, DispatcherServlet이 "/"와 같이 기본 서블릿으로 매핑되거나, "/*"와 같이 prefix 없이 매핑되어 있고 서블릿 컨테이너가 4.0 이상인 경우에는, 스프링 MVC가 서블릿 매핑 타입을 자동으로 감지하여 servletPath나 pathInfo를 사용하지 않고 전체 경로 정보를 안전하게 처리할 수 있습니다.
반면, 만약 서블릿 컨테이너가 3.1이라면, 동일한 매핑 방식을 사용할 때 UrlPathHelper를 항상 전체 경로를 사용하도록(alwaysUseFullPath=true) 설정하여 같은 효과를 얻을 수 있습니다.
즉, URL 경로를 처리할 때 발생할 수 있는 문제들을 방지하기 위해, 환경에 따라 URL 처리 방법을 변경할 필요가 있다는 의미입니다.
Fortunately the default Servlet mapping "/" is a good choice. However, there is still an issue in that the requestURI needs to be decoded to make it possible to compare to controller mappings. This is again undesirable because of the potential to decode reserved characters that alter the path structure. If such characters are not expected, then you can reject them (like the Spring Security HTTP firewall), or you can configure UrlPathHelper with urlDecode=false but controller mappings will need to match to the encoded path which may not always work well. Furthermore, sometimes the DispatcherServlet needs to share the URL space with another Servlet and may need to be mapped by prefix.
다행히도 기본 서블릿 매핑인 "/"는 좋은 선택입니다. 그러나 요청 URI를 컨트롤러 매핑과 비교할 수 있도록 디코딩해야 한다는 문제가 있습니다. 이는 경로 구조를 변경할 수 있는 예약된 문자가 디코딩될 가능성이 있어 바람직하지 않습니다. 만약 그러한 문자가 예상되지 않는다면, Spring Security HTTP 방화벽처럼 이를 거부할 수 있거나, 또는 UrlPathHelper를 urlDecode=false로 설정할 수 있습니다. 다만, 이 경우 컨트롤러 매핑은 인코딩된 경로와 일치해야 하므로 항상 원활하게 작동하지 않을 수 있습니다. 또한, 때로는 DispatcherServlet이 다른 서블릿과 URL 공간을 공유해야 하므로 prefix로 매핑되어야 할 수도 있습니다.
이 문장은 기본 서블릿 매핑 "/"가 일반적으로 좋은 선택임에도 불구하고, 몇 가지 문제가 존재함을 설명합니다.
첫째, 컨트롤러 매핑과 비교하기 위해 requestURI를 디코딩해야 하는데, 이 과정에서 예약 문자가 디코딩되어 경로 구조가 변경될 가능성이 있어 바람직하지 않습니다.
둘째, 만약 이러한 예약 문자가 예상되지 않는다면, Spring Security HTTP 방화벽처럼 이를 거부할 수 있습니다. 또는 UrlPathHelper의 urlDecode 옵션을 false로 설정할 수도 있는데, 이 경우 컨트롤러 매핑은 인코딩된 경로와 일치해야 하므로 항상 원활하게 작동하지 않을 수 있습니다.
마지막으로, 때로는 DispatcherServlet이 다른 서블릿과 URL 공간을 공유해야 하는 상황이 발생하여, prefix를 사용한 매핑이 필요할 수도 있습니다.
즉, URL 디코딩과 매핑 처리 방식에 따라 발생할 수 있는 문제들을 고려하여 적절한 설정을 선택해야 함을 의미합니다.
The above issues are addressed when using PathPatternParser and parsed patterns, as an alternative to String path matching with AntPathMatcher. The PathPatternParser has been available for use in Spring MVC from version 5.3, and is enabled by default from version 6.0. Unlike AntPathMatcher which needs either the lookup path decoded or the controller mapping encoded, a parsed PathPattern matches to a parsed representation of the path called RequestPath, one path segment at a time. This allows decoding and sanitizing path segment values individually without the risk of altering the structure of the path. Parsed PathPattern also supports the use of servletPath prefix mapping as long as a Servlet path mapping is used and the prefix is kept simple, i.e. it has no encoded characters. For pattern syntax details and comparison, see Pattern Comparison.
위의 문제들은 AntPathMatcher를 사용한 문자열 경로 매칭 대신 PathPatternParser와 파싱된 패턴을 사용할 때 해결됩니다. PathPatternParser는 Spring MVC에서 5.3 버전부터 사용 가능하며, 6.0 버전부터 기본으로 활성화됩니다. AntPathMatcher는 lookup 경로를 디코딩하거나 컨트롤러 매핑을 인코딩해야 하는 반면, 파싱된 PathPattern은 한 번에 하나의 경로 세그먼트로 이루어진 RequestPath라는 경로의 파싱된 표현과 일치합니다. 이를 통해 경로의 구조를 변경할 위험 없이 각 경로 세그먼트 값을 개별적으로 디코딩하고 정제할 수 있습니다. 또한, 파싱된 PathPattern은 서블릿 경로 매핑이 사용되고 접두사가 단순(즉, 인코딩된 문자가 없는)한 경우, 서블릿 경로 접두사 매핑의 사용도 지원합니다. 패턴 구문 세부 사항과 비교에 대해서는 Pattern Comparison을 참조하시기 바랍니다.
기존 방식에서는 전체 URL을 한 번에 처리하다 보니, 디코딩 과정에서 경로의 구조가 변할 위험이 있었습니다. 이에 따라, 한 경로의 모든 부분을 한꺼번에 처리하는 대신, 새로운 방식에서는 URL을 여러 개의 작은 조각(세그먼트)으로 나누어 하나씩 처리합니다. 이로 인해 각 부분을 안전하게 디코딩하고 정리할 수 있어, 전체 경로 구조가 바뀌지 않도록 할 수 있습니다.
또한, 이 방식은 단순한 서블릿 경로 접두사를 사용할 경우에도 문제없이 동작합니다. 따라서 Spring MVC는 더 안정적이고 안전하게 요청 경로를 처리할 수 있게 됩니다.
세그먼트를 나눈다는 것은 URL을 "/"와 같은 구분자를 기준으로 나눈다는 의미입니다. 이렇게 하면 각 세그먼트를 따로 디코딩하고 정제할 수 있어, 전체 경로의 구조가 변경되는 위험을 줄일 수 있습니다.
Interception
All HandlerMapping implementations support handler interception which is useful when you want to apply functionality across requests. A HandlerInterceptor can implement the following:
모든 HandlerMapping 구현체는 요청 전반에 걸쳐 기능을 적용하고자 할 때 유용한 핸들러 인터셉션(handler interception)을 지원합니다. HandlerInterceptor는 다음과 같은 메서드들을 구현할 수 있습니다.
- preHandle(..) — callback before the actual handler is run that returns a boolean. If the method returns true, execution continues; if it returns false, the rest of the execution chain is bypassed and the handler is not called.
- postHandle(..) — callback after the handler is run.
- afterCompletion(..) — callback after the complete request has finished.
preHandle(…)
실제 핸들러가 실행되기 전에 호출되는 콜백 메서드입니다. 이 메서드는 boolean 값을 반환하는데, true를 반환하면 실행이 계속되고, false를 반환하면 실행 체인의 나머지 부분이 생략되어 핸들러가 호출되지 않습니다.
postHandle(…)
핸들러가 실행된 후에 호출되는 콜백 메서드입니다.
afterCompletion(…)
전체 요청 처리가 완료된 후에 호출되는 콜백 메서드입니다.
For @ResponseBody and ResponseEntity controller methods, the response is written and committed within the HandlerAdapter, before postHandle is called. That means it is too late to change the response, such as to add an extra header. You can implement ResponseBodyAdvice and declare it as an Controller Advice bean or configure it directly on RequestMappingHandlerAdapter.
@ResponseBody와 ResponseEntity를 사용하는 컨트롤러 메서드의 경우, 응답은 postHandle이 호출되기 전에 HandlerAdapter 내에서 작성되고 커밋된다. 이는 추가 헤더를 추가하는 등 응답을 변경하기에는 너무 늦은 시점임을 의미한다. ResponseBodyAdvice를 구현하여 Controller Advice 빈으로 선언하거나, RequestMappingHandlerAdapter에 직접 구성할 수 있다.
이 문장은 @ResponseBody나 ResponseEntity를 사용하는 컨트롤러 메서드의 경우, 핸들러 어댑터 내에서 응답 내용이 작성되고 이미 클라이언트로 전송(commit)되어 버리기 때문에, 그 이후에 응답을 변경하는 것이 불가능함을 의미합니다.
즉, 응답이 postHandle 단계에 도달하기 전, 이미 최종 응답이 확정되어 추가 헤더를 붙이는 등의 수정이 불가능합니다. 만약 응답 내용을 수정하고 싶다면, ResponseBodyAdvice를 구현하여 Controller Advice 빈으로 등록하거나 RequestMappingHandlerAdapter에 직접 설정해야 합니다.
여기서 말하는 컨트롤러 메서드는 ModelAndView를 사용하여 뷰를 반환하는 것이 아니라, @ResponseBody나 ResponseEntity를 사용하여 주로 JSON 형식의 데이터를 직접 응답 본문에 작성하는 메서드를 의미합니다.
See Interceptors in the section on MVC configuration for examples of how to configure interceptors. You can also register them directly by using setters on individual HandlerMapping implementations.
MVC 구성 섹션의 Interceptors 부분에서 인터셉터를 구성하는 예제를 확인하실 수 있습니다. 또한, 개별 HandlerMapping 구현체의 setter를 사용하여 인터셉터를 직접 등록할 수도 있습니다.
Interceptors are not ideally suited as a security layer due to the potential for a mismatch with annotated controller path matching. Generally, we recommend using Spring Security, or alternatively a similar approach integrated with the Servlet filter chain, and applied as early as possible.
인터셉터는 어노테이션 기반 컨트롤러 경로 매칭과의 불일치 가능성 때문에 보안 계층으로 이상적으로 적합하지 않습니다. 일반적으로, 우리는 Spring Security를 사용할 것을 권장하며, 또는 대안으로 서블릿 필터 체인에 통합된 유사한 방식을 사용하고 가능한 한 조기에 적용할 것을 추천합니다.
Exceptions
If an exception occurs during request mapping or is thrown from a request handler (such as a @Controller), the DispatcherServlet delegates to a chain of HandlerExceptionResolver beans to resolve the exception and provide alternative handling, which is typically an error response.
The following table lists the available HandlerExceptionResolver implementations:
요청 매핑 중에 예외가 발생하거나, 요청 핸들러(예: @Controller)로부터 예외가 던져질 경우, DispatcherServlet은 예외를 해결하고 대체 처리를 제공하기 위해 HandlerExceptionResolver 빈 체인에게 위임합니다. 이는 일반적으로 오류 응답을 의미합니다.
다음 표는 사용 가능한 HandlerExceptionResolver 구현체들을 나열합니다.
이 문장은 요청 매핑 과정에서 발생하는 예외나, @Controller와 같은 요청 처리기에서 예외가 발생했을 경우, DispatcherServlet이 그 예외를 직접 처리하지 않고, 여러 HandlerExceptionResolver 빈들이 순차적으로 예외를 해결하도록 위임한다는 의미입니다. 이러한 예외 해결기는 예외에 대해 대체 처리를 제공하며, 일반적으로는 오류 응답을 생성합니다. 이어지는 표에는 사용 가능한 HandlerExceptionResolver 구현체들이 나열되어 있습니다.

SimpleMappingExceptionResolver
예외 클래스 이름과 오류 뷰 이름 간의 매핑입니다. 브라우저 애플리케이션에서 오류 페이지를 렌더링하는 데 유용합니다.
DefaultHandlerExceptionResolver
Spring MVC에서 발생하는 예외를 해결하고 이를 HTTP 상태 코드로 매핑합니다. 대안으로 ResponseEntityExceptionHandler와 Error Responses도 있습니다.
ResponseStatusExceptionResolver
@ResponseStatus 어노테이션이 붙은 예외를 해결하며, 어노테이션의 값에 따라 HTTP 상태 코드로 매핑합니다.
ExceptionHandlerExceptionResolver
@Controller 또는 @ControllerAdvice 클래스 내의 @ExceptionHandler 메서드를 호출하여 예외를 해결합니다.
Chain of Resolvers
You can form an exception resolver chain by declaring multiple HandlerExceptionResolver beans in your Spring configuration and setting their order properties as needed. The higher the order property, the later the exception resolver is positioned.
여러 HandlerExceptionResolver 빈들을 Spring 구성에서 선언하고 필요에 따라 그들의 order 속성을 설정함으로써, 예외 해결기 체인을 구성할 수 있습니다. order 속성이 높을수록 예외 해결기가 체인에서 더 나중에 위치하게 됩니다.
The contract of HandlerExceptionResolver specifies that it can return:
- a ModelAndView that points to an error view.
- An empty ModelAndView if the exception was handled within the resolver.
- null if the exception remains unresolved, for subsequent resolvers to try, and, if the exception remains at the end, it is allowed to bubble up to the Servlet container.
The MVC Config automatically declares built-in resolvers for default Spring MVC exceptions, for @ResponseStatus annotated exceptions, and for support of @ExceptionHandler methods. You can customize that list or replace it.
HandlerExceptionResolver의 계약은 다음과 같이 반환할 수 있음을 명시합니다.
- 오류 뷰를 가리키는 ModelAndView
- 예외가 리졸버 내에서 처리된 경우, 빈 ModelAndView
- 예외가 해결되지 않은 경우 null을 반환하여 후속 리졸버가 시도할 수 있도록 하며, 만약 최종적으로 예외가 남으면 서블릿 컨테이너로 전파되도록 허용됩니다.
또한, MVC 구성은 기본 Spring MVC 예외, @ResponseStatus 어노테이션이 붙은 예외, 그리고 @ExceptionHandler 메서드 지원을 위해 내장 리졸버들을 자동으로 선언합니다. 이 목록은 사용자 정의하거나 교체할 수 있습니다.
이 내용은 HandlerExceptionResolver가 예외를 처리할 때 반환할 수 있는 결과와 그 처리 방식에 대해 설명합니다.
ModelAndView 반환: 예외가 발생했을 때, 특정 오류 페이지(뷰)를 보여주기 위해 오류 뷰를 가리키는 ModelAndView를 반환할 수 있습니다.
빈 ModelAndView 반환: 리졸버 내부에서 예외를 처리한 경우, 빈 ModelAndView를 반환하여 추가적인 뷰 렌더링 없이 예외 처리가 완료되었음을 나타냅니다.
null 반환: 예외가 해당 리졸버에서 처리되지 않았을 경우 null을 반환하여, 다음에 등록된 리졸버가 예외를 처리할 수 있도록 합니다. 만약 모든 리졸버가 예외를 처리하지 못하면, 예외는 서블릿 컨테이너로 전달됩니다.
또한, Spring MVC는 기본적으로 예외 처리용 내장 리졸버들을 자동으로 등록합니다. 이 내장 리졸버들은 기본 Spring MVC 예외, @ResponseStatus 어노테이션이 붙은 예외, 그리고 @ExceptionHandler 메서드를 지원하도록 구성되어 있습니다. 필요에 따라 이 기본 목록을 변경하거나 교체할 수 있습니다.
일반적으로 @RestController나 @ResponseBody 방식을 사용할 때는 ModelAndView를 반환하지 않습니다. 즉, 예외가 처리되면 오류 응답이 JSON이나 XML 같은 형태의 데이터로 직접 작성되어 클라이언트에 전달되고, 별도의 뷰 렌더링을 위한 ModelAndView 객체는 사용되지 않습니다.
Container Error Page
If an exception remains unresolved by any HandlerExceptionResolver and is, therefore, left to propagate or if the response status is set to an error status (that is, 4xx, 5xx), Servlet containers can render a default error page in HTML. To customize the default error page of the container, you can declare an error page mapping in web.xml. The following example shows how to do so
어떤 HandlerExceptionResolver도 예외를 해결하지 못하여 예외가 전파되거나, 응답 상태가 오류 상태(즉, 4xx, 5xx)로 설정된 경우, 서블릿 컨테이너는 HTML 형식의 기본 오류 페이지를 렌더링할 수 있습니다. 컨테이너의 기본 오류 페이지를 사용자 정의하려면, web.xml에 오류 페이지 매핑을 선언할 수 있습니다. 아래 예제는 이를 수행하는 방법을 보여줍니다.
<error-page>
<location>/error</location>
</error-page>
Given the preceding example, when an exception bubbles up or the response has an error status, the Servlet container makes an ERROR dispatch within the container to the configured URL (for example, /error). This is then processed by the DispatcherServlet, possibly mapping it to a @Controller, which could be implemented to return an error view name with a model or to render a JSON response, as the following example shows:
앞선 예제를 고려할 때, 예외가 전파되거나 응답에 오류 상태가 설정되면, 서블릿 컨테이너는 구성된 URL(예: /error)로 내부 ERROR 디스패치를 수행합니다. 이 디스패치는 DispatcherServlet에 의해 처리되며, 해당 요청이 @Controller에 매핑될 수 있습니다. 이 컨트롤러는 오류 뷰 이름과 모델을 반환하도록 구현될 수도 있고, 다음 예제와 같이 JSON 응답을 렌더링하도록 구현될 수도 있습니다.
이 내용은 만약 예외가 처리되지 않거나, 응답 상태가 4xx 또는 5xx와 같은 오류 상태일 경우, 서블릿 컨테이너(예: 톰캣)가 미리 설정된 오류 URL(예: /error)로 요청을 내부적으로 전달한다는 것을 의미합니다.
즉,예외가 해결되지 않아 전파되거나, 응답 상태가 오류로 설정되면, 서블릿 컨테이너는 해당 요청을 /error 같은 특정 URL로 전달합니다.
이때 DispatcherServlet이 다시 그 요청을 받아, 오류를 처리할 수 있도록 매핑된 @Controller에게 전달하게 됩니다. 이 @Controller는 오류 상황에 맞게 HTML 오류 페이지를 렌더링하거나, JSON 형식의 오류 응답을 만들어 반환할 수 있습니다.
"디스패치 서블릿이 다시 오류를 받는다"는 의미는, 예외가 전파되거나 응답 상태가 오류(예: 4xx, 5xx)로 설정될 경우, 서블릿 컨테이너가 미리 구성된 오류 페이지 URL(예: /error)로 요청을 내부적으로 전달(디스패치)한다는 뜻입니다. 이때 전달된 오류 요청은 DispatcherServlet에 의해 다시 받아들여지고, 정상 요청처럼 처리되지만, 오류 상황에 맞는 전용 컨트롤러나 로직으로 처리됩니다.
@ControllerAdvice와 @ExceptionHandler를 사용하면, 예외가 컨트롤러 내부에서 처리되므로 /error로의 내부 디스패치가 필요하지 않습니다. 하지만 @ControllerAdvice에서 예외를 처리하지 않거나, 처리할 수 없는 예외가 발생하면 여전히 서블릿 컨테이너가 /error로 내부 디스패치를 수행할 수 있습니다.
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
return map;
}
}
1. 서블릿 컨테이너가 오류를 감지
만약 요청 처리 중 예외가 발생했거나, 응답 상태 코드가 4xx 또는 5xx 오류라면, 서블릿 컨테이너(예: 톰캣, 제티) 는 미리 설정된 오류 처리 경로(/error)로 내부 디스패치를 수행합니다.
2. DispatcherServlet이 오류 요청을 받음
/error로 내부 디스패치된 요청을 DispatcherServlet이 받게 됩니다. DispatcherServlet은 기존 요청처럼 이 요청을 처리할 적절한 핸들러(컨트롤러)를 찾습니다.
3. HandlerMapping이 @RequestMapping("/error")을 찾음
HandlerMapping이 실행되어 /error 경로를 처리할 컨트롤러를 찾습니다. 위 코드에서는 /error 요청을 처리하는 handle() 메서드를 가진 ErrorController가 매핑됩니다.
4. HandlerAdapter가 컨트롤러 실행
DispatcherServlet은 /error에 매핑된 handle() 메서드를 실행하도록 HandlerAdapter를 통해 컨트롤러를 실행합니다.
5. 오류 정보를 JSON으로 반환
컨트롤러는 jakarta.servlet.error.status_code 및 jakarta.servlet.error.message 값을 가져와 JSON 형식으로 응답을 생성합니다. 클라이언트는 JSON 응답을 받게 됩니다.
View Resolution
Spring MVC defines the ViewResolver and View interfaces that let you render models in a browser without tying you to a specific view technology. ViewResolver provides a mapping between view names and actual views. View addresses the preparation of data before handing over to a specific view technology.
The following table provides more details on the ViewResolver hierarchy:
Spring MVC는 ViewResolver 및 View 인터페이스를 정의하여 특정 뷰 기술에 종속되지 않고 브라우저에서 모델을 렌더링할 수 있도록 합니다. ViewResolver는 뷰 이름과 실제 뷰 간의 매핑을 제공합니다. View는 특정 뷰 기술에 넘기기 전에 데이터를 준비하는 역할을 합니다.
다음 표는 ViewResolver 계층 구조에 대한 자세한 정보를 제공합니다.
Spring MVC는 특정 뷰 기술(JSP, Thymeleaf 등)에 종속되지 않도록 설계되었으며, ViewResolver를 통해 유연하게 뷰를 매핑할 수 있도록 합니다.
AbstractCachingViewResolver
AbstractCachingViewResolver의 하위 클래스들은 해결한 뷰 인스턴스를 캐싱합니다. 캐싱은 특정 뷰 기술의 성능을 향상시킵니다. 캐시는 cache 속성을 false로 설정하여 비활성화할 수 있습니다. 또한, 런타임에 특정 뷰를 새로고침해야 하는 경우(예: FreeMarker 템플릿이 수정될 때), removeFromCache(String viewName, Locale loc) 메서드를 사용할 수 있습니다.
UrlBasedViewResolver
ViewResolver 인터페이스의 간단한 구현체로, 명시적인 매핑 정의 없이 논리적 뷰 이름을 URL로 직접 변환합니다. 논리적 뷰 이름이 뷰 리소스의 이름과 단순하게 일치하며 임의의 매핑이 필요하지 않은 경우 적절합니다.
InternalResourceViewResolver
UrlBasedViewResolver의 편리한 하위 클래스이며, InternalResourceView(즉, 서블릿 및 JSP)와 JstlView와 같은 하위 클래스를 지원합니다. setViewClass(..)를 사용하여 이 리졸버가 생성하는 모든 뷰의 클래스를 지정할 수 있습니다. 자세한 내용은 UrlBasedViewResolver Javadoc을 참조하십시오.
FreeMarkerViewResolver
UrlBasedViewResolver의 편리한 하위 클래스이며, FreeMarkerView 및 해당 커스텀 하위 클래스를 지원합니다.
ContentNegotiatingViewResolver
ViewResolver 인터페이스의 구현체로, 요청된 파일 이름이나 Accept 헤더를 기반으로 뷰를 결정합니다. 자세한 내용은 Content Negotiation을 참조하십시오.
BeanNameViewResolver
ViewResolver 인터페이스의 구현체로, 뷰 이름을 현재 애플리케이션 컨텍스트에서 빈(bean) 이름으로 해석합니다.
이 방식은 매우 유연하며, 각각의 뷰를 XML 또는 Java 설정 클래스에서 빈으로 정의하여 다양한 뷰 타입을 혼합하여 사용할 수 있도록 해줍니다.
Handling
You can chain view resolvers by declaring more than one resolver bean and, if necessary, by setting the order property to specify ordering. Remember, the higher the order property, the later the view resolver is positioned in the chain.
The contract of a ViewResolver specifies that it can return null to indicate that the view could not be found. However, in the case of JSPs and InternalResourceViewResolver, the only way to figure out if a JSP exists is to perform a dispatch through RequestDispatcher. Therefore, you must always configure an InternalResourceViewResolver to be last in the overall order of view resolvers.
Configuring view resolution is as simple as adding ViewResolver beans to your Spring configuration. The MVC Config provides a dedicated configuration API for View Resolvers and for adding logic-less View Controllers which are useful for HTML template rendering without controller logic.
뷰 리졸버를 체인으로 연결하려면 둘 이상의 리졸버 빈을 선언하고, 필요한 경우 order 속성을 설정하여 순서를 지정할 수 있습니다. order 속성이 높을수록 뷰 리졸버 체인에서 더 나중에 위치하게 됩니다.
ViewResolver의 계약에 따르면, 해당 뷰를 찾을 수 없는 경우 null을 반환할 수 있습니다. 그러나 JSP InternalResourceViewResolver의 경우, JSP가 존재하는지 확인하는 유일한 방법은 RequestDispatcher를 통해 디스패치(전달) 하는 것입니다.
따라서, InternalResourceViewResolver는 항상 전체 뷰 리졸버 순서에서 마지막에 위치하도록 구성해야 합니다.
뷰 리졸버 구성을 설정하는 것은 Spring 설정에 ViewResolver 빈을 추가하는 것만큼 간단합니다.
MVC 구성은 뷰 리졸버를 위한 전용 구성 API를 제공하며, 컨트롤러 로직 없이 HTML 템플릿을 렌더링하는 데 유용한 View Controller 추가 기능도 지원합니다.
이 문장의 핵심 내용은
뷰 리졸버(ViewResolver)를 여러 개 설정할 수 있으며, 실행 순서를 정할 수 있다는 것입니다.
InternalResourceViewResolver의 동작 방식
JSP의 경우, 뷰 파일이 존재하는지 확인하는 방식이 다릅니다. JSP 뷰가 있는지 확인하는 유일한 방법은 RequestDispatcher.forward()를 실행하는 것입니다. 즉, 다른 뷰 리졸버처럼 "home.jsp"가 존재하는지 단순히 확인하는 것이 아니라, 서블릿 컨테이너(Tomcat 등)에게 JSP 처리를 위임해야 합니다.
InternalResourceViewResolver가 먼저 실행되면, "home.jsp"가 있는지 확인하려고 무조건 RequestDispatcher.forward()를 호출합니다. 이 과정에서 요청이 강제로 JSP로 전달되므로, 다른 ViewResolver(Thymeleaf, FreeMarker 등)가 동작할 기회를 잃게 됩니다.
Redirecting
The special redirect: prefix in a view name lets you perform a redirect. The UrlBasedViewResolver (and its subclasses) recognize this as an instruction that a redirect is needed. The rest of the view name is the redirect URL.
The net effect is the same as if the controller had returned a RedirectView, but now the controller itself can operate in terms of logical view names. A logical view name (such as redirect:/myapp/some/resource) redirects relative to the current Servlet context, while a name such as redirect:https://myhost.com/some/arbitrary/path redirects to an absolute URL.
뷰 이름에서 특별한 redirect: 접두사를 사용하면 리디렉션을 수행할 수 있습니다.
UrlBasedViewResolver(및 그 하위 클래스)는 이를 리디렉션이 필요하다는 지시로 인식합니다.
뷰 이름의 나머지 부분은 리디렉션할 URL이 됩니다.
그 결과는 컨트롤러가 RedirectView를 반환한 것과 동일하지만, 이제 컨트롤러는 논리적 뷰 이름을 사용하여 동작할 수 있습니다.
예를 들어, redirect:/myapp/some/resource와 같은 논리적 뷰 이름은 현재 서블릿 컨텍스트를 기준으로 상대적으로 리디렉션하며,
redirect:https://myhost.com/some/arbitrary/path와 같은 이름은 절대 URL로 리디렉션합니다.
Spring MVC에서는 redirect: 접두사를 사용하여 간단하게 리디렉션을 수행할 수 있습니다.
뷰 리졸버(ViewResolver)는 redirect:로 시작하는 뷰 이름을 감지하여 이를 리디렉션 지시로 인식하고 처리합니다.
상대 경로(redirect:/home)를 사용하면 현재 애플리케이션 내에서 이동하며, 절대 경로(redirect:https://google.com)를 사용하면 외부 사이트로 이동합니다.
이를 통해 컨트롤러에서 RedirectView를 직접 반환하지 않고도 리디렉션을 수행할 수 있습니다.
Forwarding
You can also use a special forward: prefix for view names that are ultimately resolved by UrlBasedViewResolver and subclasses. This creates an InternalResourceView, which does a RequestDispatcher.forward(). Therefore, this prefix is not useful with InternalResourceViewResolver and InternalResourceView (for JSPs), but it can be helpful if you use another view technology but still want to force a forward of a resource to be handled by the Servlet/JSP engine. Note that you may also chain multiple view resolvers, instead.
또한, 뷰 이름에 특별한 forward: 접두사를 사용할 수도 있으며, 이는 궁극적으로 UrlBasedViewResolver 및 그 하위 클래스에 의해 해결됩니다.
이 방식은 InternalResourceView를 생성하며, RequestDispatcher.forward()를 수행합니다. 따라서, 이 접두사는 InternalResourceViewResolver 및 InternalResourceView(JSP의 경우)와 함께 사용하는 것은 유용하지 않지만,
다른 뷰 기술을 사용하면서도 리소스를 서블릿/JSP 엔진에서 처리하도록 강제하고 싶을 때 유용할 수 있습니다.
또한, 여러 개의 뷰 리졸버를 체인으로 연결하는 방법도 사용할 수 있습니다.
Content Negotiation
ContentNegotiatingViewResolver does not resolve views itself but rather delegates to other view resolvers and selects the view that resembles the representation requested by the client. The representation can be determined from the Accept header or from a query parameter (for example, "/path?format=pdf").
ContentNegotiatingViewResolver는 자체적으로 뷰를 해결하지 않고, 대신 다른 뷰 리졸버에 위임하며, 클라이언트가 요청한 표현(representation)과 가장 유사한 뷰를 선택합니다.
이 표현은 Accept 헤더 또는 쿼리 파라미터(예: "/path?format=pdf")를 통해 결정될 수 있습니다.
The ContentNegotiatingViewResolver selects an appropriate View to handle the request by comparing the request media types with the media type (also known as Content-Type) supported by the View associated with each of its ViewResolvers. The first View in the list that has a compatible Content-Type returns the representation to the client. If a compatible view cannot be supplied by the ViewResolver chain, the list of views specified through the DefaultViews property is consulted. This latter option is appropriate for singleton Views that can render an appropriate representation of the current resource regardless of the logical view name. The Accept header can include wildcards (for example text/*), in which case a View whose Content-Type is text/xml is a compatible match.
ContentNegotiatingViewResolver는 요청 미디어 유형을 각 ViewResolver에 연결된 View가 지원하는 미디어 유형(또는 Content-Type)과 비교하여 요청을 처리할 적절한 View를 선택합니다.
리스트에서 호환 가능한 Content-Type을 가진 첫 번째 View가 클라이언트에게 응답을 반환합니다.
만약 ViewResolver 체인이 호환 가능한 View를 제공하지 못하면, DefaultViews 속성을 통해 지정된 뷰 목록을 참조합니다.
이러한 방식은 논리적 뷰 이름과 관계없이 현재 리소스를 적절한 형식으로 렌더링할 수 있는 싱글턴 뷰(singleton View)에 적합합니다.
또한, Accept 헤더는 와일드카드(예: text/*)를 포함할 수 있으며, 이 경우 Content-Type이 text/xml인 View는 호환되는 것으로 간주됩니다.
Spring MVC에서 JSON 응답을 반환하는 방법에는 두 가지 주요 방식이 있습니다.
HttpMessageConverter 사용 (기본 방식, @ResponseBody) → RESTful API에서 일반적으로 사용 ContentNegotiatingViewResolver 사용 → 여러 뷰 타입(JSON, XML, PDF 등)을 자동 선택할 때 사용
참조 문서 : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html
DispatcherServlet :: Spring Framework
Spring MVC, as many other web frameworks, is designed around the front controller pattern where a central Servlet, the DispatcherServlet, provides a shared algorithm for request processing, while actual work is performed by configurable delegate components
docs.spring.io
'Spring' 카테고리의 다른 글
@Transactional 동작원리 (디버깅 편) (0) | 2025.05.22 |
---|---|
[Spring DB] Connection Pool (0) | 2025.04.25 |
빈 후처리 (0) | 2025.03.13 |
어드바이저 (0) | 2025.03.12 |
프록시 팩토리 (0) | 2025.03.11 |