이번 포스팅은 spring mvc 에 대해 써보려해요! 스프링 MVC가 요청을 어떻게 처리하고 돌려주는지에 대해 알아보기로 해요!
스프링 MVC 요청 처리 과정
스프링은 위에 그림과 같은 구조로 요청을 처리해요. 요청을 디스패처 서블릿, 핸들러 매핑, 컨트롤러, 뷰 리졸버 등으로 이동시키고 응답하게 되는 구조예요!
우선
요청(1) 이 브라우저에서 떠나면서 사용자가 요구하는 내용을 전달하게 돼요. 적어도 URL을 포함하고 있으며 폼에서 입력된 값들처럼 더 많은 추가 정보들이 전달 될 수도 있어요.
요청의 첫번째 도착지는 스프링의 DispatcherServlet 이에요. 대부분의 자바 웹 프레임워크와 마찬가지로 스프링 MVC 역시 많은 요청들을 하나의 프런트 컨트롤러 서블릿에서 처리해요. 단일 프론트 컨트롤러는 단일 서블릿이 실제 처리를 수행하기 위해 다른 컴포넌트에 대한 요청 책임을 위임하는 웹 애플리케이션 패턴을 제공해요. 스프링 MVC 에서는 DispatcherServlet이 이러한 프런트 컨트롤러의 역할을 해요.
DispatcherServlet의 임무는 요청을 스프링 MVC 컨트롤러에 전달해 주는 것이에요. 컨트롤러는 요청을 처리하기 위한 스프링 컴포넌트예요.
일반적으로 애플리케이션은 여러개의 컨트롤러를 가지고 있기 때문에 DispatcherServlet은 요청을 전달할 컴포넌트를 선택하기 위한 도움이 필요해요. 그래서 DispatcherServlet은 요청이 가야할 다음 목적지를 찾기 위해 여러개의 핸들러 매핑(2) 에게 도움을 요청하게 됩니다. 요청을 받은 핸들러 매핑들은 결정을 내릴 때 요청이 가져온 URL에 특별히 주목하게 됩니다.
적절한 컨트롤러가 선택되면 DispatcherServlet은 선택된 컨트롤러(3) 에 요청을 보내요. 그러면 컨트롤러에서 요청은 페이로드(사용자에 의해 입력된 정보)를 떨굽니다. 컨트롤러가 그 정보들을 처리할때까지 기다려요.(잘 디자인된 컨트롤러는 비즈니스 로직을 직접 처리하기보다는 다수의 서비스 객체에 처리를 넘기거나 아주 짧은 시간만을 할애합니다.)
컨트롤러에서 처리된 로직의 결과는 사용자의 브라우저에 표시되기 위한 형태의 정보로 반환돼요. 이 정보를 일반적으로 모델(Model) 이라고 해요. 하지만 이런 정보들을 사용자에게 그냥 반환하는 것은 효과적인 방식이 아니에요. 이런 정보를 HTML과 같이 사용자 편의성을 갖춘 형태로 전환하는데, 이때 JSP와 같은 뷰를 필요로 해요.
컨트롤러가 하는 마지막 일은 모델을 패키징하는 일과 결과물을 렌더링하기 위한 뷰의 이름을 확인하는거에요. 모델과 뷰 이름을 포함하여 DispatcherServlet에게 요청을 돌려보내요(4) .
DispatcherServlet에 전달된 뷰의 이름은 직접적으로 특정 JSP를 의미하지는 않아요. 이는 반드시 뷰가 JSP 일 필요는 없다는 말이죠. 대신 결과를 만들어 내기 위한 실제 뷰를 찾아내는데 필요한 논리적인 이름을 전달해주면 돼요. DispatcherServlet은 뷰 리졸버(5) 에게 논리적으로 주어진 뷰의 이름과 실제로 구현된 뷰를 매핑해 줄 것을 요청해요.(여기서 구현된 뷰는 JSP일수도! 아닐수도!)
이제 DispatcherServlet은 결과를 렌더링하기 위한 뷰가 어떤 것인지 알 수 있어요. 요청의 마지막 여정은 일반적으로 JSP로 모델 데이터를 전달해 주는 뷰의 구현(6) 에서 끝이나게 돼요. 뷰는 모델 데이터를 사용하여 결과를 렌더링하고 이것은 그다지 어렵지 않게 응답 객체(7) 에 의해 클라이언트로 전달 됩니다.
스프링 MVC 설정하기
여기에선 스프링 MVC 설정중에서 가장 간단한 접근법만을 설명하게요. xml 설정, java 설정이 있는데
저는 서블릿 스펙 3스펙, 스프링 3.2 이상에서 사용이 가능한 java 방법으로 설명해볼게요.
DispatcherServlet 설정하기
DispatcherServlet은 스프링 MVC의 핵심이에요. 이곳에서 요청이 처음으로 스프링 프레임워크와 만나게 되기 때문이죠.
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// DispatcherServlet 을 /에 매핑.
// 애플리케이션으로 들어오는 모든 요청을 처리한다.
@Override
protected String\[\] getServletMappings() {
return new String\[\] {"/"};
}
// 여기서 리턴된 @Configuration 클래스들은 ContextLoaderListener가 생성한
// 애플리케이션 컨텍스트를 설정하는데 사용된다.
@Override
protected Class\[\] getRootConfigClasses() {
return new Class\[\] {ApplicationConfig.class};
}
// 설정 클래스를 명시
// 여기서 리턴된 @Configuration 클래스들은 DispatcherServlet의 애플리케이션 컨텍스트에
// 대한 핀들을 정의한다.
@Override
protected Class\[\] getServletConfigClasses() {
return new Class\[\] {WebMvcContextConfiguration.class};
}
}
위 코드를 동작시키는 방법을 이해하기 위해서는 AbstractAnnotationConfigDispatcherServletInitializer 를 상속받은 클래스로 DispatcherServlet 과 애플리케이션의 서블릿 컨텍스트내의 스프링 애플리케이션 컨텍스트를 설정한다는 것만 알면 된다.
AbstractAnnotationConfigDispatcherServletInitializer
서블릿 3.0 환경에서 컨테이너는 클래스패스 내의 javax.servlet.ServletContainerInitializer 인터페이스를 구현한 모든 클래스를 찾기도 되어 있다. 여기서 발견된 클래스들은 서블릿 컨테이너들을 설정하는데 사용된다.
스프링은 SpringServletContainerInitializer의 구현을 제공하고 이것은 순차적으로 WebApplicationInitializer의 구현 클래스를 찾아 설정을 위임한다. 스프링 3.2에서는 AbstractAnnotationConfigDispatcherServletInitializer에 WebApplicationInitializer의 편리한 기본 구현이 제공된다. 위의 코드에 나온
WebInitializer 클래스 역시 AbstractAnnotationConfigDispatcherServletInitializer를 상속받아 구현한 것이므로 서블릿 3.0 컨테이너에서 배포될 때 자동적으로 발견되어 서블릿 컨텍스트를 자동 초기화 한다.
AbstractAnnotationConfigDispatcherServletInitializer는 세 개의 메소드를 오버라이드 해야해요.
첫번째, getServletMappings()는 DispatcherServlet이 매핑되기 위한 하나 혹은 여러개의 패스를 지정할 수 있는데,이번에는 / 만 매핑할거에요.
나머지 두 개의 메소드를 이해하기 위해서는 DispatcherServlet 과 ContextLoaderListener로 알고 있는 서블릿 리스너 사이의 관계에 대한 이해가 필요해요.
두 애플리케이션 컨텍스트에 대한 이야기
DispatcherServlet이 시작되면서 스프링 애플리케이션 컨텍스트를 생성하고 이를 클래스나 설정 파일로 선언된 빈으로 로딩하기 시작해요.
getServletConfigClasses() 메소드에서 DispatcherServlet이 애플리케이션 컨텍스트를 WebMvcContextConfiguration 클래스에서 정의된 빈으로 로딩하기를 요청하도록 되어 있어요.
그러나 스프링 웹 애플리케이션에서는 ContextLoaderListener에서 만들어진 다른 애플리케이션 컨텍스트가 사용돼요.
DispatcherServlet은 컨트롤러,뷰 리졸버,핸들러 매핑과 같은 웹 컴포넌트가 포함된 빈을 로딩할 것으로 생각되는 반면, ContextLoaderListener는 애플리케이션 내의 그 외의 다른 빈을 로딩할 것으로 생각돼요.
이런 빈들은 일반적으로 중간계층(middle-tier)이나 데이터 계층(data-tier) 컴포넌트들로 애플리케이션의 백엔드를 구성해요.
내부적으로 AbstractAnnotationConfigDispatcherServletInitializer는 DispatcherServlet과 ContextLoaderListener를 생성해요.
getServletConfigClasses()에서 리턴된 @Configuration 클래스들은 DispatcherServlet의 애플리케이션 컨텍스트에 대한 핀들을 정의해요, 반면 getRootConfigClasses() 에서 리턴된 @Configuration 클래스들은 ContextLoaderListener가 생성한 애플리케이션 컨텍스트를 설정하는데 사용돼요.
Spring MVC 활성화하기
Spring MVC 활성화하는 방법은 여러가지가 있어요. 예전에는 xml 파일에서 < mvc:annotation-driven > 요소를 넣어 스프링 MVC를 활성화 시켰어요.
아래 코드는 다른 방식으로 MVC 설정을 했습니다.
@Configuration @EnableWebMvc public class WebMvcContextConfiguration {
}
@EnableWebMvc 애너테이션을 붙여주기만 하면 MVC 설정이 되지만 더 해야할 것이 더 있어요.
-
뷰 리졸버를 설정해야 합니다. 스프링은 디폴트로 빈의 ID와 View 인터페이스를 구현한 뷰의 이름을 매칭시켜 뷰를 결정하는 BeanNameViewResolver를 사용해요.
-
컴포넌트 검색 기능이 비활성되어 있어요. 결과적으로 스프링이 컨트롤러를 찾는 유일한 방법은 명시적으로 설정에 해당 컨트롤러를 선언하는 것이에요.
-
DispatcherServlet은 애플리케이션의 디폴트 서블릿으로 매핑되어 이미지나 스타일 시트 같은 고정적인 리소스들에 대한 "모든" 요청을 처리해줘야해요.
위와 같은 기능을 추가한 코드는 아래와 같아요.
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.board.controller", "com.board.config"})
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
// default tomcat의 servlet handler를 사용하게 합니다.
// '/' 요청은 모든 요청을 말합니다. 이 요청은 원래 WAS 의 Default Servlet 가 처리해줍니다.
// '/' url을 처리하는 새 서블릿을 만들었는데, 만약 새 서블릿이 요청을 처리하지 못하면,
// 디폴트 서블릿이 처리하게끔 넘겨줍니다.
// 누가? DispatcherServlet이.
// 정적 컨텐츠 처리 설정.
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// jsp와 뷰 리졸버 설정.
@Bean
public InternalResourceViewResolver getInternalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
@ComponentScan 애너테이션이 붙으면, 애너테이션에 명시된 패키지에서 컴포넌트를 스캔해요.
개발한 컨트롤러들에 @Controller 애너테이션을 붙여주면 컴포넌트 스캐닝의 대상이 돼요. 그럼 설정 클래스 내부에 명시적으로 컨트롤러 선언을 해 줄 필요가 없어져요!
그리고 View Resolver가 추가됐어요. 여기서는 InternalResourceViewResolver가 사용되었는데, 이 뷰 리졸버는 특정한 접두사, 접미사가 사용된 뷰 이름을 가지고 JSP 파일들을 찾아주는 역할을 해요.(예를 들어, home이라는 뷰 이름은 /WEB-INF/views/home.jsp 라는 jsp 파일로 결정되는 식이죠!)
마지막으로 WebMvcConfigurerAdapter를 상속받아 구현한 클래스가 되었고 configureDefaultServletHandling() 메소드를 오버라이드 하고 있어요. DefaultServletHandlerConfigurer 의 enable() 호출은 DispatcherServlet이 고정적인 리소스들에 대한 요청을 자신이 직접 처리하지 않고 서블릿 컨테이너의 디폴트 서블릿이 처리하라고 요청하는 거에요.
이제 기본적인 Web 설정은 마친거 같아요. 그렇다면 위에 WebInitializer 파일의 getRootConfigClasses()에서 설정한 ApplicationConfig.class는 어떻게 해야할까요? 지금 이 포스팅은 MVC 설정법이 중점이니 이 파일은 따로 설정은 안해줄거에요. 후에 DataSource나 트랜잭션매니저 추가할때 사용할 겁니다.
@Configuration
@ComponentScan(basePackages = { "com.board.dao", "com.board.service"})
public class ApplicationConfig {
}
지금은 이렇게만 두어도 될것 같아요!!
이상 스프링 MVC 시작하기 였습니다! 감사합니다.
참고 : 스프링 인 액션 4판