[Spring] Spring MVC

Fortice·2021년 1월 28일
0

Spring

목록 보기
7/13
post-thumbnail

VSCode 스프링 프로젝트 생성하기

스프링 MVC 프레임워크 동작 방식

다음 사진은 스프링 MVC 핵심 구성 요소 간의 관계를 보여준다.

Java 코드로 구현하는 HandlerMapping, Controller, ViewResolver는 모두 스프링 빈으로 등록해야 한다.

동작 방식

DispatcherServlet은 모든 연결을 담당한다.

  1. 클라이언트가 정보를 요청한다.
  2. 웹 브라우저로부터 요청이 들어오면 요청을 처리하기 위한 컨트롤러 객체를 검색한다.
    • 직접 검색하는 것이 아니라 HandlerMapping 빈 객체에게 컨트롤러 검색을 요청한다.
  3. HandlerMapping은 클라이언트의 요청 경로를 이용해 이를 처리할 컨트롤러 빈 객체를 DispatcherServlet에 전달한다.
    • DispathcerServlet은 HandlerMapping이 찾아준 컨트롤러 객체를 처리할 수 있는 HandlerAdapter 빈에게 요청 처리를 위임한다.
  4. HandlerAdapter는 컨트롤러의 알맞은 메소드를 호출해서 처리한다.
    • HandlerAdapter는 @Controller, Controller 인터페이스, HTTPRequestHandler 인터페이스를 동일한 방식으로 처리하기 위해 중간에 사용되는 빈이다.
  5. Controller에서 결과를 HandlerAdapter에게 넘긴다.
  6. 그 결과를 ModelAndView 객체로 변환하여 DispatcherServlet에 리턴한다.
  7. ViewResolver는 ModelAndView 객체의 뷰 이름을 보고, 이에 해당하는 View 객체를 찾거나 생성하여 리턴한다.
    • 응답 객체로 JSP를 사용해 매번 새로운 View 객체를 생성해 DispatcherServlet에 리턴한다.
  8. DispatcherServlet은 View객체에게 응답 결과 생성을 요청한다.

Handler

Dispatcher 서블릿은 클라이언트의 요청을 앞선에서 받고 알맞게 처리를 위임한다. 요청을 실제로 처리하는 컨트롤러를 찾기위해 HandlerMapping을 사용한다. 이에 ControllerMapping이라 하지 않고 HandlerMapping이라 하는지 궁금할 수 있다.

스프링 MVC는 범용 웹 프레임워크로 여러가지 방식을 제공한다. 위에서도 말했듯이 @Controller 외에도 여러 객체를 사용해 요청을 처리할 수 있다. 이런 요청을 실제로 처리하는 객체들을 묶어 Handler 라고 부른다.

요청을 처리하는 방식이 다르니, 결과를 맞출 필요가 있다. 따라서 DIspatcherServlet이 처리하기 위한 ModelAndView 형식으로 맞춰줄 HandlerAdapter가 필요한 것이다.

DispatcherServlet과 스프링 컨테이너

DispatcherServlet은 web.xml파일로 설정하는데 이 때, 전달한 스프링 설정 클래스 목록으로 스프링 컨테이너를 생성한다. 그리고 컨테이너에서 앞에 3가지 빈들을 구한다.

빈을 구한다는 것은 빈으로 등록되어야 한다는 것인데, 이는 @EnableWebMvc 어노테이션이 대신 해준다.

WebMvcConfigurer 인터페이스

@EnableWebMvc 어노테이션을 사용하면 `WebMvcConfigurer 타입의 빈을 이용해서 MVC 설정을 추가로 생성한다.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer{
    
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { ... }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) { ... }
}

위처럼 인터페이스를 구현하면 WebMvcConfigurer 타입의 빈이 되기 때문에 설정을 추가할 수 있는 것이다. 위 두 설정 외에도 다른 설정이 많은데, 이는 뒤에서 다룬다.

ViewResolver

@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를 통해 불러올 수 있다.

디폴트 핸들러와 HandlerMapping의 우선순위

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() 메소드는 다음의 두 빈 객체를 추가한다.

  • DefaultServletHttpRequestHandler
    • 클라이언트의 모든 요청을 WAS가 제공하는 디폴트 서브릿에 전달한다.
  • SimpleUrlHandlerMapping
    • 모든 경로("/**")를 DefaultServletHttp RequestHandler를 이용해서 처리하도록 설정한다.

@EnableWebMvc 어노테이션이 등록하는 RequestMappingHandlerMapping 적용 우선순위가 enable()메소드로 등록한 SimpleUrlHandlerMapping 적용 우선순위보다 높다. 따라서 DispatcherServlet은 아래와 같은 순서로 적용한다.

  1. RequestMappingHandlerMapping을 사용해서 요청을 처리할 핸들러를 검색한다.
    • 존재하면 해당 컨트롤러를 이용해서 요청을 처리한다.
  2. 존재하지 않으면 SimpleUrlHandlerMapping을 사용해서 요청을 처리할 핸들러를 검색한다.
    • DefaultServletHandlerConfigure#enable() 메소드가 등록한 SimppleUrlHandlerMapping은 "/**"인 모든 경로에 대해 DefaultServletHttpRequestHandler를 리턴한다.
    • DispatcherServlet은 DefaultServletHttpRequestHandler에 처리를 요청한다.
    • DefaultServletHttpRequestHandler는 디폴트 서블릿에 처리를 위임한다.

직접 설정 해보기

직접 구현하기 때문에 상당히 많은 줄의 코드가 필요하다.

@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;
    }
}
profile
서버 공부합니다.

0개의 댓글