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

Posted by yunki kim on August 21, 2022

스프링 MVC 핵심 구성 요소

스프링 MVC의 핵심 수성 요소와 그들의 관계를 정리하면 다음과 같다. 이 관계도는 API가 아닌 페이지를 응답하는 기준으로 작성되었다.

  위 그림에서 "<<spring bean>>이 붙은 요소는 빈 등록을 요구하는 요소다. JPS, Controller는 사용자가 직접 구현해야 한다.

1. 요청 전송

  DispatcherServlet은 클라이언트로 부터 요청을 받고 모든 연결을 담당한다.

2. 요청 URL과 매칭되는 컨트롤러 검색

  HandlerMapping 빈 객체에게 컨트롤러 검색을 요청한다. 그러면 HandlerMapping은 클라이언트의 요청 경로를 이용해 이를 처리한 컨트롤러 빈 객체를 DispatcherServlet에 전달한다.

  이 때, 스프링에서 컨트롤러를 구현할 수 있는 방식은 다음과 같이 3가지가 존재한다. 이 방식들로 구현된 컨트롤러들을 동일한 방식으로 처리해야 하므로 중간에 HandlerAdapter 빈을 사용한다.

  스프링에서 컨트롤러를 구현하는 방법

  1. @Contoller 어노테이션을 사용해 구현한다.

  2. Contoller 인터페이스를 사용한다(스프링 2.5 까지 사용되었다).

  3. 특수 목적으로 사용되는 HttpRequestHandler 인터페이스를 구현.

3. 처리 요청 ~ 6. 컨트롤러 실행 결과를 ModelAndView로 변환해 리턴

  DispatcherServlet은 HandlerMapping이 반환한 컨트롤러 객체를 처리할 수 있는 HandlerAdapter 빈에게 요청 처리를 위임한다. 그러면 HandlerAdapter가 컨트롤러의 알맞은 메서드를 호출해 요청을 처리하고 결과를 ModelAndView라는 객체로 DispatcherServlet에 반환한다.

7. 컨트롤러의 실행 결과를 보여줄 view 검색

  HandlerAdapter로부터 컨트롤러의 요청 처리 겨로가를 ModelAndView로 받으면 DispatcherServlet은 ViewResolver를 홀출해 결과를 보여줄 뷰를 찾는다. ModelAndView에는 컨트롤러가 반환한 뷰 이름이 존재한다. ViewResolver는 이 뷰 이름에 해당하는 View 객체를 찾거나 생성해 반환한다. 응답을 위해 JSP를 사용한다면 매번 새로운 View 객체를 생성해 DispatcherServlet에 반환한다.

8. 응답 생성 요청

  DispatcherServlet은 ViewResolver가 반환한 view 객체에게 응답 결과 생성을 요청한다.

컨틀로러와 핸들러

  DispatcherServlet은 클라이언트의 요청을 처리할 컨트롤러를 찾기 위해 HandlerMapping을 사용한다. 이 때, 컨트롤러를 찾아주는 객체의 이름이 ControllerMappind이 아닌 HandlerMapping인 이유는 스프링 MVC가 웹 요청을 처리할 수 있는 범용 프레임워크이기 때문이다. 클라이언트 요청을 처리하는 클래스는 반드시 '@Controller' 어노테이션을 사용할 필요가 없다. 사용자가 직접 정의한 클래스일 수도 있고, 스프링이 클라이언트 요청 처리를 위해 제공하는 타입인 HttpRequestHandler일 수도 있다.  따라서 스프링 MVC는 웹 요청을 실제로 처리하는 객체를 핸들러(Handler)라고 부르고 있다.

  DispatcherServlet은 핸들러 객체의 실제 타입에 상관없이 실행 결과를 ModelAndView로 받으면 된다. 하지만 핸들러의 실제 구현에 따라 반환 타입이 ModelAndView가 아닐 수도 있다. 이를 변환하기 위해 HandlerAdapter가 사용된다. DispatcherServlet에서 dispatching을 하는 doDispatch() 메서드를 보면 HandleAdapter를 사용해  반환 타입을 ModelAndView로 받아오는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
 
    ...
 
    try {
        ModelAndView mv = null;
        ...
 
        try {
            ...
 
            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
 
            ...
 
            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 
            // Process last-modified header, if supported by the handler.
            ...
                //
            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 
            ...
        }
        ...
    }
    ...
}
cs

  핸들러 객체의 실제 타입마다 그에 알맞은 HandlerPamming과 HandlerAdapter가 존재한다. 따라서 사용할 핸들러 종류에 따라 해당 HandlerMapping과 HandlerAdapter를 스프링 빈으로 등록해야 한다. 그때문에 DispatcherServlet의 필드로 HandlerMapping과 HandlerAdapter 인스턴스들을 리스트로 가지고 있고, 원하는 인스턴스를 찾는 메서드가 존재한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
 
    ...
 
    /** List of HandlerMappings used by this servlet. */
    @Nullable
    private List<HandlerMapping> handlerMappings;
 
    /** List of HandlerAdapters used by this servlet. */
    @Nullable
    private List<HandlerAdapter> handlerAdapters;
 
    ...
 
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
 
    ...
 
    /**
     * Return the HandlerExecutionChain for this request.
     * <p>Tries all handler mappings in order.
     * @param request current HTTP request
     * @return the HandlerExecutionChain, or {@code null} if no handler could be found
     */
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
}
 
cs

DispatcherServlet과 스프링 컨테이너

  DispatcherServlet은 전달받은 설정 파일을 이용해 스프링 컨테이너를 생성한다. 위에서 언급한 HandlerMapping, HandlerAdapter, Controller, ViewResolver 등의 빈은 DispatcherServlet이 생성한 스프링 컨테이너에서 구한다.

  좀 더 정확히 말하자면 DispatcherServlet이 extends하고 있는 FrameworkServlet이 스프링 컨테이너(WebApplicationContext)를 생성한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
 
    ...
 
    /** WebApplicationContext for this servlet. */
    @Nullable
    private WebApplicationContext webApplicationContext;
 
    ...
 
    /**
     * Overridden method of {@link HttpServletBean}, invoked after any bean properties
     * have been set. Creates this servlet's WebApplicationContext.
     */
    @Override
    protected final void initServletBean() throws ServletException {
 
        ...
            
        
        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        ...
    }
 
    ...
 
    /**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
 
        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }
 
        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }
 
        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
 
        return wac;
    }
 
    ...
}
 
cs

  그리고 DispatcherServlet은 FrameworkServlet의 추상 메서드인 onRefresh() 메서드를 구현해 필요한 빈들을 구한다.

  FrameworkServelt에서 onRefresh()를 호출하는 코드와 onRefresh() 추상 메서드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
 
    ...
 
    /**
     * Callback that receives refresh events from this servlet's WebApplicationContext.
     * <p>The default implementation calls {@link #onRefresh},
     * triggering a refresh of this servlet's context-dependent state.
     * @param event the incoming ApplicationContext event
     */
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        synchronized (this.onRefreshMonitor) {
            onRefresh(event.getApplicationContext());
        }
    }
 
    /**
     * Template method which can be overridden to add servlet-specific refresh work.
     * Called after successful context refresh.
     * <p>This implementation is empty.
     * @param context the current WebApplicationContext
     * @see #refresh()
     */
    protected void onRefresh(ApplicationContext context) {
        // For subclasses: do nothing by default.
    }
 
    ...
}
 
cs

  DispatcherServlet에서 onRefresh()를 구현한 부분과 필요한 빈들을 초기화 하는 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
 
    ...
 
    /**
     * This implementation calls {@link #initStrategies}.
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
 
    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    
    ...
}
 
cs

@Controller를 위한 HandlerMapping과 HandlerAdapter

  DispatcherServlet 입장에서 @Controller를 적용한 객체는 한 종류의 핸들러 객체다. 따라서 DispatcherServlet은 핸들러에 알맞은 HandlerMapping 빈을 이용해 핸들러 객체를 찾고, 알맞은 HandlerAdapter 빈을 이용해 핸들러를 실행해야 한다.

  하지만 HandlerMapping과 HandlerAdapter 클래스를 빈으로 등록하기 위해 많은 양의 코드를 작성할 필요는 없다. 설정 클래스에 '@EnanbleWebMvc' 어노테이션을 추가하면 된다. 그러면 여러 클래스가 빈으로 추가되며 그 중엣는 @Controller 타입의 핸들러 객체를 처리하기 위한 클래스도 존재한다. @Controller 타입의 핸들러 객체를 처리하기 위핸 클라스는 다음과 같다.

  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

  RequestMappingHandlerMapping은 @Controller 어노테이션이 적용된 객체의 요청 매핑 어노테이션(@xxMapping) 값을 이용해 요청을 처리할 컨트롤러 빈을 찾는다.

  RequestMappingHandlerMapping에서 매핑 어노테이션을 이용해 컨트롤러 빈을 찾는 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {
    
    ...
 
    /**
     * Uses method and type-level @{@link RequestMapping} annotations to create
     * the RequestMappingInfo.
     * @return the created RequestMappingInfo, or {@code null} if the method
     * does not have a {@code @RequestMapping} annotation.
     * @see #getCustomMethodCondition(Method)
     * @see #getCustomTypeCondition(Class)
     */
    @Override
    @Nullable
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method);
        if (info != null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
            String prefix = getPathPrefix(handlerType);
            if (prefix != null) {
                info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
            }
        }
        return info;
    }
 
    ...
 
    /**
     * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
     * supplying the appropriate custom {@link RequestCondition} depending on whether
     * the supplied {@code annotatedElement} is a class or method.
     * @see #getCustomTypeCondition(Class)
     * @see #getCustomMethodCondition(Method)
     */
    @Nullable
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        // @RequestMapping 어노테이션을 이용해 컨트롤러 빈을 찾는 것을 볼 수 있다.
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }
 
    ...
}
cs

  RequestMappingHandlerAdapter는 컨트롤러의 메서들을 알맞게 실행하고 그 결과를 ModelAndView 객체로 변환해 DispatcherServlet에 반환한다. DispatcherServlet의 doDispatch() 메서드 구현을 보면 HandlerAdapter 인스턴스를 호출해 가져온 핸들러들을 ModelAndView로 변환하는 코드가 존재한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
 
    ...
 
    /**
     * Process the actual dispatching to the handler.
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    @SuppressWarnings("deprecation")
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
 
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
 
            try {
                ...
 
                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                ...
 
                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 
                // Process last-modified header, if supported by the handler.
                ...
                
 
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 
                ...
            }
            ...
        }
        ...
    }
    ...
}
 
cs

  AbstractHandlerMethodAdapter는 HandlerAdapter 인터페이스를 implements하고 있다. RequestMappingHandlerAdapter는 AbstractHandlerMethodAdpater를 extends하고 있다. AbstractHandleMethodAdapter가 구현하고 있는 handle() 메서드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Abstract base class for {@link HandlerAdapter} implementations that support
 * handlers of type {@link HandlerMethod}.
 *
 * @author Arjen Poutsma
 * @since 3.1
 */
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
 
    ...
 
    /**
     * This implementation expects the handler to be an {@link HandlerMethod}.
     */
    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
 
        return handleInternal(request, response, (HandlerMethod) handler);
    }
 
    /**
     * Use the given handler method to handle the request.
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handlerMethod handler method to use. This object must have previously been passed to the
     * {@link #supportsInternal(HandlerMethod)} this interface, which must have returned {@code true}.
     * @return a ModelAndView object with the name of the view and the required model data,
     * or {@code null} if the request has been handled directly
     * @throws Exception in case of errors
     */
    @Nullable
    protected abstract ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
 
    ...
}
 
cs

  AbstractHandlerMethodAdapter의 handleInternal() 메서드의 구현은 RequestMappingHandlerAdapter에 존재한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * Extension of {@link AbstractHandlerMethodAdapter} that supports
 * {@link RequestMapping @RequestMapping} annotated {@link HandlerMethod HandlerMethods}.
 *
 * <p>Support for custom argument and return value types can be added via
 * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers},
 * or alternatively, to re-configure all argument and return value types,
 * use {@link #setArgumentResolvers} and {@link #setReturnValueHandlers}.
 *
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @author Sebastien Deleuze
 * @since 3.1
 * @see HandlerMethodArgumentResolver
 * @see HandlerMethodReturnValueHandler
 */
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
 
    ...
 
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 
        ModelAndView mav;
        checkRequest(request);
 
        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
 
        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }
 
        return mav;
    }
 
    ...
}
 
cs

WebMvcConfigurer 인터페이스와 설정

  @EnableWebMvc 어노테이션을 사용하면 @Controller 어노테이션을 붙인 컨트롤러를 위한 설정을 생성할 수 있고 WebMvcConfigurer 타입의 빈을 이용해 MVC 설정을 추가로 생성할 수 있다. 예시 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
 
    @Override
    public vodi configureDefautlServletHandling(DefaultServlethandlerConfigurer configurer) {
        configurer.enable();
    }
 
    @Override
    public void configurerViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/view/"".jsp");
    }
}
 
cs

 WebMvcConfigurer 내부를 보면 모든 메서드들이 default로 선언되 있다. 이는 원하는 메서드들만 구현해 사용하기 위함이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// WebMvcConfigurer 일부
public interface WebMvcConfigurer {
 
    default void configurePathMatch(PathMatchConfigurer configurer) {
    }
 
    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }
 
    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
 
    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }
 
    default void addFormatters(FormatterRegistry registry) {
    }
    
    ...
}
 
cs

  스프링 부트 1.5에서는 Java7을 지원했기 때문에 defualt method가 존재하지 않았다. 따라서 원하는 메서드들만 사용하기 위해 중간에 WebMvcConfigurereAdapter를 두고 사용했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// WebMvcConfigurerAdapter 일부
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
 
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    }
 
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }
 
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
 
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }
 
    @Override
    public void addFormatters(FormatterRegistry registry) {
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    }
 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }
 
    ...
}
 
cs

  하지만 스프링 부트 2.0 부터 java 8과 spring 5를 지원하면서 WebMvcConfigurerAdapter는 deprecated 되었다.

  스프링 부투에서 스프링 MVC 자동 구성은 WebMvcAutoConfiguration이 담당한다. WebMvcAutoConfiguration의 일부는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
 
    ...
 
    @SuppressWarnings("deprecation")
    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
        ...
    }
 
    ...
}
 
cs

  위 코드에서 WebMvcAutoConfiguration 상단에 위치한 @ConditionalOnMissingBean 어노테이션을 주목하자. 이는 WebMvcConfigurationSupport 타입의 빈을 찾을 수 없을 때 WebMvcAutoConfiguration을 활성화 한다. @EnableWebMvc를 스프링 MVC 구성을 위한 클래스에 선언하면(간단히 말하면 @EnableWebMvc, @Configuration을 모두 사용하는 클래스다), 해당 WebMvcConfigurationSupport를 불러와 스프링 MVC를 구성한다.

 

출처 - 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문