다음 사진은 스프링 MVC 핵심 구성 요소 간의 관계를 보여준다.
Java 코드로 구현하는 HandlerMapping, Controller, ViewResolver는 모두 스프링 빈으로 등록해야 한다.
DispatcherServlet은 모든 연결을 담당한다.
@Controller
, Controller 인터페이스
, HTTPRequestHandler 인터페이스
를 동일한 방식으로 처리하기 위해 중간에 사용되는 빈이다.Handler
Dispatcher 서블릿은 클라이언트의 요청을 앞선에서 받고 알맞게 처리를 위임한다. 요청을 실제로 처리하는 컨트롤러를 찾기위해 HandlerMapping을 사용한다. 이에 ControllerMapping이라 하지 않고 HandlerMapping이라 하는지 궁금할 수 있다.
스프링 MVC는 범용 웹 프레임워크로 여러가지 방식을 제공한다. 위에서도 말했듯이
@Controller
외에도 여러 객체를 사용해 요청을 처리할 수 있다. 이런 요청을 실제로 처리하는 객체들을 묶어 Handler 라고 부른다.요청을 처리하는 방식이 다르니, 결과를 맞출 필요가 있다. 따라서 DIspatcherServlet이 처리하기 위한 ModelAndView 형식으로 맞춰줄 HandlerAdapter가 필요한 것이다.
DispatcherServlet은 web.xml파일로 설정하는데 이 때, 전달한 스프링 설정 클래스 목록으로 스프링 컨테이너를 생성한다. 그리고 컨테이너에서 앞에 3가지 빈들을 구한다.
빈을 구한다는 것은 빈으로 등록되어야 한다는 것인데, 이는 @EnableWebMvc
어노테이션이 대신 해준다.
@EnableWebMvc
어노테이션을 사용하면 `WebMvcConfigurer 타입의 빈을 이용해서 MVC 설정을 추가로 생성한다.
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer{
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { ... }
@Override
public void configureViewResolvers(ViewResolverRegistry registry) { ... }
}
위처럼 인터페이스를 구현하면 WebMvcConfigurer
타입의 빈이 되기 때문에 설정을 추가할 수 있는 것이다. 위 두 설정 외에도 다른 설정이 많은데, 이는 뒤에서 다룬다.
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/view/",".jsp");
}
위 설정은 내부적으로 InternallResourceViewResolver 클래스를 이용해서 다음 설정의 빈을 등록한다.
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver vr = new InternalResourceViewResolver();
vr.setPrefix("/WEB-INF/view/");
vr.setSuffix(".jsp");
return vr;
}
위처럼 View 객체를 리턴하고, DispatcherServlet이 응답 생성을 요청하면 객체는 지정한 JSP 코드를 실행해서 응답 결과를 생성한다.
DispatcherServlet은 실행 결과를 ModelAndView 형태로 받는데, Model에 담긴 값은 View 개체에 Map 형식으로 전달된다. 따라서 Key, Value를 설정하고, Key를 통해 불러올 수 있다.
web.xml 설정에 매핑 경로를 /로 설정했다. 이 경우 .jsp로 끝난느 요청을 제외한 모든 요청을 DispatcherServlet이 처리한다. 따라서 HTML/CSS 확장자에 대한 요청도 처리하게 된다.
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
하지만 @EnableWebMvc
어노테이션이 등록하는 HandlerMapping은 @Controller
어노테이션을 적용한 빈 객체가 처리할 수 있는 요청 경로만 대응할 수 있다. 따라서 등록이 안된 HTML/CSS 요청은 처리할 수 있는 컨트롤러를 찾지 못해 404응답을 전송한다.
실습에 했던 configureDefaultServletHandling()
메서드는 위 설정을 간단하게 해준다.
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
enable()
메소드는 다음의 두 빈 객체를 추가한다.
@EnableWebMvc
어노테이션이 등록하는 RequestMappingHandlerMapping 적용 우선순위가 enable()
메소드로 등록한 SimpleUrlHandlerMapping 적용 우선순위보다 높다. 따라서 DispatcherServlet은 아래와 같은 순서로 적용한다.
직접 구현하기 때문에 상당히 많은 줄의 코드가 필요하다.
@Configuration
//@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer{
// @Override
// public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// configurer.enable();
// }
@Bean
public HandlerMapping handlerMapping() {
RequestMappingHandlerMapping hm = new RequestMappingHandlerMapping();
hm.setOrder(0);
return hm;
}
@Bean
public HandlerAdapter handlerAdapter() {
RequestMappingHandlerAdapter ha = new RequestMappingHandlerAdapter();
return ha;
}
@Bean
public HandlerMapping simpleHandlerMapping() {
SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
Map<String, Object> pathMap = new HashMap<>();
hm.setUrlMap(pathMap);
return hm;
}
@Bean
public HttpRequestHandler defaultServletHandler() {
DefaultServletHttpRequestHandler handler = new DefaultServletHttpRequestHandler();
return handler;
}
// @Override
// public void configureViewResolvers(ViewResolverRegistry registry) {
// registry.jsp("/WEB-INF/view/",".jsp");
// }
@Bean
public HandlerAdapter requestHandlerAdapter() {
HttpRequestHandlerAdapter ha = new HttpRequestHandlerAdapter();
vr.setPrefix("/WEB-INF/view/");
vr.setSuffix(".jsp");
return vr;
}
}