2020.04.23
ref. Web Servlet
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-webmvc
)에서 유래됐지만 일반적으로 "Spring MVC"로 알려져 있다.다른 많은 웹 프레임 워크와 마찬가지로 Spring MVC는 프론트 컨트롤러 패턴을 중심으로 설계되었다. 중앙 Servlet
인 DispatcherServlet
은 요청 처리를 위한 공유 알고리즘을 제공하며 실제 작업은 구성 가능한 델리게이트 컴포넌트(delegate components, 대리 구성 요소=업무를 분담시킬 하위 컴포넌트라고 생각하면 될 듯)에 의해 수행된다. 이 모델은 유연하며 다양한 워크 플로우를 지원한다.
DispatcherServlet
은 Servlet
와 마찬가지로 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을에서 감지되어 서블릿 컨테이너에 등록된다. 자세한 내용은 스프링 부트 설명서를 참조하십시오.
디스패처 서블릿은 자체 구성에 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
서블릿 파라미터는 비워 둘 수 있다.
디스패처 서블릿은 요청을 처리하고 적절한 응답을 제공하기 위한 특수한 빈을 위임한다. 특수한 빈은 프레임워크 컨트렉트(contracts)을 이행하는 스프링에 의해 관리되는 오브젝트 인스턴스를 뜻한다. 이러한 계약에는 대개 내장 컨트랙트(built-in contracts)가 포함돼 있지만, 그것들을 확장하거나 교체 할 수 있으며 프로퍼티를 커스텀마이징 할 수 있다.
다음은 디스패처서블릿에 이해 특수한 빈으로 검출되는 것들이다.
⭐️⭐️⭐️
HandlerMapping
RequestMappingHandlerMapping
와(@RequestMapping
어노테이션이 달린 메소드를 지원한다.) SimpleUrlHandlerMapping
(URI 경로 패턴의 명시적 등록을 유지한다.)가 있다.HandlerAdapter
HandlerAdapter
의 주목적은 그러한 세부 작업들로부터 DispatcherServlet
을 자유롭게 하는 것이다.HandlerExceptionResolver
ViewResolver
LocaleResolver,LocaleContextResolver
ThemeResolver
MultipartResolver
multi-part request
(e.g. 브라우저 양식 파일 업로드)을 파싱을 위한 추상체이다.FlashMapManager
FlashMap
을 저장하고 검색한다.2020.04.24 추가
애플리케이션은 요청을 처리하는 데 필요한 특수한 빈 타입에 인프라 빈 리스트를 선언할 수 있다. DispatcherServlet은 각 특수한 빈의 WebApplicationContext를 검사한다. 일치하는 빈 타입이 없는 경우, 그것은 DispatcherServlet.properties
에 쓰여 있는 기본 유형으로 되돌아간다.
보통 MVC Config는 가장 좋은 출발점이다. 그 이유는 필요한 빈을 자바나 XML로 선언하고 그것을 커스텀마이징 할 수 있는 더 높은 수준의 구성 콜백 API를 제공하기 때문이다.
스프링 부트는 MVC Java 구성을 사용하여 Spring MVC를 구성하고 많은 추가적인 옵션을 제공한다.
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() };
}
}
각 필터는 구체적인 유형에 따라 기본적인 이름으로 추가되며 자동으로 디스패처 서블릿에 매핑된다.
AbstractDispatcherServletInitializer
의 protected
메소드인 isAsyncSupported
는 디스패처 서블릿과 이에 매핑된 모든 필터를 비동기적으로 사용할 수 있게 해주는 하나의 장소를 제공한다. 기본적으로 이 플래그는 true
이다.
마지막으로, 디스패처 서블릿을 커스텀마이징하고 싶다면 createDispatcherServlet
메소드를 오버라이딩 하면 된다.
2020.04.25 추가
디스패처 서블릿은 다음과 같이 요청을 처리한다.
WebApplicationContext
은 request에서 검색되고 바인딩된다. DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
키 아래에 바인딩된다.locale resolver
가 request에 바인딩된다. theme resolver
은 뷰 같은 요소가 사용할 테마를 결정하도록 요청에 바인딩된다. multipart file resolver
를 지정하면 request는 요청에서 multiparts
가 검사된다. MultipartHttpServletRequest
로 랩핑된다. HandlerAdapter
내에서) 응답을 랜더링 할 수도 있다.📌스프링 어플리케이션의 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을 고려하여 상속받는 구조로 만들어졌다.
📌디스패처 서블릿의 요청처리
WebApplicationContext
에 선언된 HandlerExceptionResolver
빈은 요청을 처리하는 동안에 발생한 예외를 해결하는 데 사용된다. 이러한 exception resolvers는 로직을 커스텀마이징하여 예외를 처리할 수 있다. 자세한 내용은 Exception을 참조하라.
스프링 디스패처 서블링은 또한 서블릿 API에 의해 지정된 대로 last-modification-date
의 리턴을 지원한다. 특정 요청에 대한 최종 수정 날짜를 결정하는 프로세스는 간단하다. 디스패처 서블릿은 적절한 핸들러 맵핑을 찾고 발견된 핸들러가 LastModified
인터페이스의 구현체인지 테스트한다. 만약 그렇다면, LastModified
인터페이스의 메소드인 long getLastModified(request)
의 값이 클라이언트로 리턴된다.
web.xml
파일의 서블릿 선언에 서블릿 초기화 파라미터(init-param elements)를 추가하여 개별적인 디스패처 서블릿 인스턴스를 커스텀마이징 할 수 있다. 다음 표에는 지원되는 파라미터가 나열돼있다.:
(표 1. DispatcherServlet 초기화 매개 변수)
스프링 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의 가이드와 튜토리얼은 이 섹션에서 설명된 어노테이션 기반 프로그래밍 모델을 사용한다.
좋은 글이네요!