본문 바로가기
Spring

Spring | Spring MVC 패턴 - HTTP 요청을 받고 응답하기까지의 전 과정(2)

by Hoya324 2024. 9. 5.

HTTP 요청 전체 흐름

흐름을 이해하기 좋은 자료들

  1. 클라이언트 요청을 Dispatcher Servlet에 전달
  2. WAS에서 HttpServletRequest, HttpServletResponse 객체로 변환하고, 이를 DispathcherServlet에게 넘겨준다.
  3. DispathcherServlet.doDispatch()가 호출된다.
  4. HandlerMapping에서 해당 Handler(controller)에 처리 요청한다.
  5. DispathcherServlet은 찾아낸 Handler를 실행할 수 있는 HandlerAdapter를 찾고, 실행한다. HandlerAdapter가 실제 Handler를 실행한다.
  6. DispathcherServlet에서 전달받은 ModelAndView 객체를 이용하여 매핑되는 View를 검색한다.
  7. View 반환
  8. View 는 해당하는 뷰를(ex. JSP, Thymleaf..) 호출하며, Model 객체에서 화면 표시에 필요한 정보를 가져와 화면 표시를 처리한다.
  9. DispathcherServlet에서 최종 응답 결과를 클라이언트에게 반환

1. 클라이언트 요청을 Dispatcher Servlet에 전달

  • 클라이언트가 HTTP 요청을 보내면, WAS는 TCP/IP 연결 대기 과정(welcome 소켓)에서 클라이언트를 위한 소켓(연결 소캣)을 생성해서 연결한다.
  • WAS는 HTTP 메시지를 파싱해서, Web Server에서만 필요한 정보면, 정적 페이지를 반환한다.
  • Request, Response 객체를 만들어 Filter 객체에 던져준다.
  • Filter에서 요청된 내용을 변경(인코딩 변환 처리)하거나, 여러가지 체크(XSS 방어)를 한다. 여기서 체크에 걸린다면 예외를 반환한다.

Filter 객체

  • 위의 Spring MVC request Life cycle에서 알 수 있듯이 Filter 객체는 Dispatcher Servlet 전, 후로 동작한다.
  • Filter는 J2EE 표준 스펙 기능으로, Dispatcher Servlet에 요청이 전달되기 전, 후에 url 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공한다.
  • 즉, Spring Container가 아닌 Tomcat과 같은 Web Container에 의해 관리가 되므로 Dispatcher Servlet으로 가기 전에 요청을 처리하는 것이다.
  • 그렇기 때문에 Filter에서는 Spring과 무관하게 전역적으로 처리해야 하는 작업들을 수행하면 좋다.
  • 필터는 연속된 체인 형태로 구성되는데, 필터의 순서를 선택해서 등록할 수 있다.
  • 만약 여러 필터를 거치는 도중 적절하지 않은 요청이라면 다음과 같이 강제로 / 경로로 리다이렉트를 시키는 등의 작업을 할 수 있다.

대표적인 예시

1. 보안과 관련된 공통작업

  • Filter는 Interceptor보다 앞단에서 동작하기 때문에 전역적으로 해야하는 보안 검사(XSS 방어 등)을 하여 올바른 요청이 아닐 경우 차단을 할 수 있다.
    • XSS(Cross Site Scripting): 서버로 보내는 폼이나 데이터 안에 스트링 형태의 자바스크립트를 보내 개발자가 의도한 코드와 다르게 코드가 동작하여 주로 사용자의 데이터를 가져가거나 악성코드를 심는 행위.
  • 그러면 Spring Container까지 요청이 전달되지 못하고 차단되므로, 안전성을 더욱 높일 수 있다.

2. 이미지나 데이터의 압축이나 문자열 인코딩과 같이 웹 애플리케이션에 전반적으로 사용되는 기능 구현

  • Filter는 다음 체인으로 넘기는 ServletRequest/ServletResponse 객체를 조작할 수 있다는 점에서 Interceptor보다 훨씬 강력한 기술이다.

Filter 인터페이스를 구현할 수 있는 메서드 3가지

1. init()

  • init 메서드는 필터 객체를 초기화하고 서비스에 추가하기 위한 메서드이다.
  • 웹 컨테이너가 1회 init 메서드를 호출하여 필터 객체를 초기화하면 이후의 요청들은 doFilter를 통해 처리된다.

2. doFilter()

  • doFilter메서드는 HTTP 요청이 디스패처 서블릿으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메서드이다.
  • doFilter 의 파라미터로는 FilterChain이 있는데, FilterChain의 doFilter 통해 다음 대상으로 요청을 전달하게 된다.
  • chain.doFilter() 전/후에 우리가 필요한 처리 과정을 넣어줌으로써 원하는 처리를 진행할 수 있다.

3. destroy()

  • destroy 메서드는 필터 객체를 서비스에서 제거하고 사용하는 자원을 반환하기 위한 메서드이다.
  • 이는 웹 컨테이너에 의해 1번 호출되며 이후에는 이제 doFilter에 의해 처리되지 않는다.

init() 과 destroy() 는 default 메소드이기 때문에 반드시 구현할 필요는 없다.

Interceptor

  • Interceptor에서는 클라이언트 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리할 수 있다.
  • 대표적으로 인증이나 인가와 같은 클라이언트 요청과 관련된 작업 등이 있다. 이런한 작업들은 컨트롤러로 넘어가기 전에 검사해야 하므로 Interceptor가 처리하기에 적합하다.
  • 또한 Interceptor는 Filter와 다르게 HttpServletRequest나 HttpServletResponse 등과 같은 객체를 제공받으므로 객체 자체를 조작할 수는 없다. (Filter와의 차이점)
  • 대신 해당 객체가 내부적으로 갖는 값은 조작할 수 있으므로 Controller로 넘겨주기 위한 정보를 가공하기에 용이하다.
    • 예를 들어, JWT 토큰 정보를 파싱해서 Controller에게 사용자 정보를 제공하도록 가공할 수 있는 것이다.
  • 또한, 다양한 목적으로 API호출에 대한 정보들을 기록할 때, HttpServletRequest나 HttpServletResponse를 제공해 주는 Interceptor는 클라이언트의 IP나 요청 정보들을 포함해 기록하기에 용이하다.

Interceptor 인터페이스를 구현할 수 있는 메서드 3가지

  • Interceptor 정상 흐름

  • Interceptor 예외 발생 상황 흐름

  • 예외가 발생하면 postHandle() 은 호출되지 않기 때문에, 예외와 무관하게 공통 처리를 하기 위해서는 afterCompletion() 메소드를 사용해야한다.

1. preHandle()

  • preHandle 메서드는 컨트롤러가 호출되기 전에 실행된다.
  • 그렇기 때문에 컨트롤러 이전에 처리해야 하는 전처리 작업이나 요청 정보를 가공하거나 추가하는 경우에 사용할 수 있다.
  • preHandle의 3번째 파라미터인 handler 파라미터는 핸들러 매핑이 찾아준 컨트롤러 빈에 매핑되는 HandlerMethod라는 새로운 타입의 객체로써, @RequestMapping이 붙은 메서드의 정보를 추상화한 객체이다.
  • 또한 preHandle의 반환 타입은 boolean인데 반환값이 true 이면 다음 단계로 진행이 되지만, false라면 작업을 중단하여 이후의 작업(다음 인터셉터 또는 컨트롤러)은 진행되지 않는다.

2. postHandle()

  • postHandle 메서드는 컨트롤러를 호출된 후에 실행된다.
  • 그렇기 때문에 컨트롤러 이후에 처리해야 하는 후처리 작업이 있을 때 사용할 수 있다.
  • 이 메서드에는 컨트롤러가 반환하는 ModelAndView 타입의 정보가 제공되는데, 최근에는 json 형태로 데이터를 주고받는 RestAPI 기반의 컨트롤러(@RestController)를 만들면서 자주 사용되지는 않는다.

3. afterCompletion()

  • afterCompletion 메서드는 이름 그대로 모든 뷰에서 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행된다.
  • 요청 처리 중에 사용한 리소스를 반환할 때 사용하기에 적합하다.

필터와는 달리 인터셉터는 addPathPatterns() 와 excludePathPattern() 메소드로 매우 정밀하게 URL 패턴을 지정할 수 있다.

Filter vs Interceptor 차이점 정리

2. WAS에서 HttpServletRequest, HttpServletResponse 객체로 변환하고, 이를 DispathcherServlet에게 넘겨준다.

  • ServletContainer가 servlet의 구현에 대한 부분을 담당하고, 이후 servlet에 요청에 대한 역할을 위임하는 방식 하는 방식이기 때문이다.

3. DispathcherServlet.doDispatch()가 호출된다.

  • 이때 doDispatch 수행 중 mapping, adapter, interceptor, resolver에 만족하지 못한다면, 예외를 반환한다.
  • DispathcherServlet의 코드를 간단하게 분석하면 다음과 같다.(예외처리, 인터셉터 기능은 제외함)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    ModelAndView mv = null;

    // 1. 핸들러 조회
    mappedHandler = getHandler(processedRequest); 
    if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return; 
    }

    //2.핸들러 어댑터 조회-핸들러를 처리할 수 있는 어댑터
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환 
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView
mv, Exception exception) throws Exception {
    // 뷰 렌더링 호출
    render(mv, request, response);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    View view;
    String viewName = mv.getViewName(); 

    //6. 뷰 리졸버를 통해서 뷰 찾기, 7.View 반환
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

    // 8. 뷰 렌더링
    view.render(mv.getModelInternal(), request, response);
}

이와 같은 방식으로 동작하는 것을 알았으니, 좀더 자세히 알아보자.

4. HandlerMapping에서 해당 Handler(controller)에 처리 요청한다.

...
HandlerExecutionChain mappedHandler = null;

// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest); 
if (mappedHandler == null) {
  noHandlerFound(processedRequest, response);
  return; 
}
...
  • HandlerMapping은 HttpServletRequest을 보고 Handler를 찾아서 Handler의 이름과 함께 반환한다.
  • 이때 반환되는 것은 HandlerExecutionChain타입이다. (handler와 인터셉터 관련 상태를 가지고 있다.)

HandlerMapping의 종류

1. RequestMappingHandlerMapping

어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용

스프링 빈 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.

주의!

스프링 3.0 이상 스프링 부트 3.0(스프링 프레임워크 6.0)부터는 클래스 레벨에 @RequestMapping 이 있어도 스프링 컨트롤러로 인식하지 않는다.

오직 @Controller 가 있어야 스프링 컨트롤러로 인식한다. (참고로 @RestController 는 해당 애노테이션 내부에 @Controller 를 포함하고 있으므로 인식 된다.)

따라서 @Controller 가 없는 위의 두 코드는 스프링 컨트롤러로 인식되지 않는다. ( RequestMappingHandlerMapping 에서 @RequestMapping 는 이제 인식하지 않고, Controller 만 인식한다.)

2. BeanNameUrlHandlerMapping

  • 스프링 빈의 이름으로 핸들러를 찾는다.

5. DispathcherServlet은 찾아낸 Handler를 실행할 수 있는 HandlerAdapter를 찾고, 실행한다. HandlerAdapter가 실제 Handler를 실행한다.

//2.핸들러 어댑터 조회-핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환 
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

Handler(controller)에서 처리한 결과를 Handler Adapter에서 ModelAndView 객체로 변환하여 DispathcherServlet에 전달한다.

HandlerAdapter 종류

1. RequestMappingHandlerAdapter

  • 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용

2. HttpRequestHandlerAdapter

  • HttpRequestHandler 처리

3. SimpleControllerHandlerAdapter

  • Controller 인터페이스(어노테이션X, 과거에 사용) 처리

Argument Resolver

  • Argument Resolver란 @Controller의 "파라미터 & 애노테이션 정보"를 기반으로 실제 로직을 수행하는 컨트롤러에게 전달할 데이터를 생성해준다.
    • HttpServletRequest
    • Model
    • @RequestParam
    • @ModelAttribute
    • @RequestBody
    • HttpEntity
    • 등등
  • 즉, Request Mapping에 의한 데이터가 들어올 때 이 데이터들을 binding해주고 어노테이션에 맞게 modify해주는 기능을 제공해준다.
  • 어노테이션 기반의 컨트롤러가 위와 같은 다양한 파라미터를 사용할 수 있는 것은 RequestMappingHandlerAdapter가 ArgumentResolver를 호출하면서 필요로 하는 다양한 파라미터의 값을 생성한 후 컨트롤러를 호출하며 파라미터를 넘겨주기 때문이다.
  • ReturnValueHandler는 응답 값을 변환하고 처리하는데, 컨트롤러에서 String으로 뷰의 논리적 이름만 반환하더라도 동작하는 이유가 ReturnValueHandler 덕분이다.

HTTP Message Converter

용도

  • HTTP API처럼 JSON 데이터를 HTTP 메시지 바디 내 직접 읽거나 쓰는 경우 사용
  • @ResponseBody 어노테이션을 사용할 때 HTTP Body 내 문자 내용을 직접 반환하므로 HttpMessageConverter가 동작
  • String 문자 처리에는 StringHttpMessageConverter 사용
  • 객체 처리에는 MappingJackson2HttpMessageConverter 사용

이외에도 다양한 HttpMessageConverter가 존재

스프링 MVC의 HttpMessageConverter 적용 사례

  • HTTP 요청: @RequestBody, HttpEntity(RequestEntity)
  • HTTP 응답: @ResponseBody, HttpEntity(ResponseEntity)

HttpMessageConverter 메서드

canRead(), canWrite() 메서드: 메시지 컨버터가 해당 클래스 혹은 미디어 타입을 지원하는지 체크하는 용도

read(), write() 메서드: 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능 지원

HTTP 요청 데이터 읽는 과정

  • HTTP 요청이 들어오고, 컨트롤러에서 @RequestBody 혹은 HttpEntity 파라미터를 사용하는 상황
  • MessageConverter가 메시지를 읽을 수 있는 확인하기 위해 canRead() 메서드 호출 (대상 클래스 타입을 지원하는지, HTTP 요청의 Content-Type 미디어 타입을 지원하는지 체크)
  • canRead() 조건을 만족할 경우 read() 메서드를 호출해서 객체 생성 후 반환

HTTP 응답 데이터 생성하는 과정

  • 컨트롤러에서 @ResponseBody 혹은 HttpEntity로 값이 반환되는 상황
  • MessageConverter가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 메서드 호출 (대상 클래스 타입을 지원하는지, HTTP 요청의 Accept 미디어 타입을 지원하는지 체크)
  • canWrite() 조건을 만족할 경우 write() 메서드를 호출해서 HTTP 응답 메시지 바디 내 데이터 생성

주로 사용하는 MessageConverter(우선순위 순)

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • MappingJackson2HttpMessageConverter

스프링에서 메시지 컨버터를 선정할 때 대상 클래스 타입과 미디어 타입을 체크 후 사용 여부를 결정하고, 만족하지 않을 경우 다음 우선순위에 있는 메시지 컨버터로 넘어가 체크 진행

ByteArrayHttpMessageConverter


byte [] 데이터 처리 클래스 타입: byte []
미디어 타입: */* HTTP 요청 예시: @RequestBody byte [] example
HTTP 응답 예시: @ResponeBody return byte[] (쓰기 MediaType: application/octet-stream)

StringHttpMessageConverter

String 문자열로 데이터 처리 클래스 타입: String
미디어 타입: */* HTTP 요청 예시: @RequestBody String example
HTTP 응답 예시: @ResponseBody return example (쓰기 MediaType: text/plain)

MappingJackson2HttpMessageConverter

application/json 처리 클래스 타입: 객체 또는 HashMap
미디어 타입: application/json
HTTP 요청 예시: @RequestBody Example example
HTTP 응답 예시: @ResponseBody return example (쓰기 MediaType: application/json)

  • 위 과정에서 HttpMessageConverter는 요청과 응답을 처리하는 과정에서 모두 필요한데 아래와 같은 상황에서 사용이 된다.
    • 요청: @RequestBody 혹은 HttpEntity를 처리하는 ArgumentResovler가 HttpMessageConverter를 사용해서 필요하는 객체를 생성
    • 응답: @ResponseBody 혹은 HttpEntity를 처리하는 ReturnValueHandler에서 HttpMessageConverter를 사용해서 응답 결과 생성

6. DispathcherServlet에서 전달받은 ModelAndView 객체를 이용하여 매핑되는 View를 검색한다.

//6. 뷰 리졸버를 통해서 뷰 찾기, 7.View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
  • viewResolver 호출: 뷰 리졸버를 찾고 실행한다.
  • 처리 결과가 포함된 view를 DispathcherServlet에 전달한다.

7. View 반환

  • viewResolver 뷰의 논리이름을 물리이름으로 바꾸고, 렌더링 역할을 담당하는 뷰객체를 반환한다.

8. View 는 해당하는 뷰를(ex. JSP, Thymleaf..) 호출하며, Model 객체에서 화면 표시에 필요한 정보를 가져와 화면 표시를 처리한다.

  • 찾은 뷰에 모델 데이터를 랜더링하고 요청의 응답값을 생성한다.

9. DispathcherServlet에서 최종 응답 결과를 클라이언트에게 반환

질문 정리

Q1. filter는 @bean을 사용해서 어떻게 관리할 수 있는가?

A1.

  • 과거에 실제로 필터(Filter)가 스프링 컨테이너에 의해 관리되지 않았다.
  • 그래서 필터를 빈으로 등록할 수도 없고, 다른 빈을 주입받을 수도 없었다.
  • 하지만 DelegatingFilterProxy가 등장하면서 이제 이러한 설명은 더이상 유효하지 않다.

DelegatingFilterProxy의 등장-Spring

  • Spring 1.2부터 DelegatingFilterProxy가 나오면서 서블릿 필터(Servlet Filter) 역시 스프링에서 관리가 가능해졌다.
  • DelegatingFilterProxy는 서블릿 컨테이너에서 관리되는 프록시용 필터로써 우리가 만든 필터를 가지고 있다.
  • 우리가 만든 필터는 스프링 컨테이너의 빈으로 등록되는데, 요청이 오면 DelegatingFilterProxy가 요청을 받아서 우리가 만든 필터(스프링 빈)에게 요청을 위임한다.

이러한 동작 원리를 쉽게 정리하면 다음과 같다.

  1. Filter 구현체가 스프링 빈으로 등록한다.
  2. ServletContext가 Filter 구현체를 갖는 DelegatingFilterProxy를 생성한다.
  3. ServletContext가 DelegatingFilterProxy를 서블릿 컨테이너에 필터로 등록한다.
  4. 요청이 오면 DelegatingFilterProxy가 필터 구현체에게 요청을 위임하여 필터 처리를 진행한다.

SpringBoot 등장

  • 위의 DelegatingFilterProxy를 등록하는 과정은 Spring이기 때문에 필요한 것이고, SpringBoot라면 DelegatingFilterProxy 조차 필요가 없다.
  • 왜냐하면 SpringBoot가 내장 웹서버를 지원하면서 톰캣과 같은 서블릿 컨테이너까지 SpringBoot가 제어가능하기 때문이다.
  • 그래서 SpringBoot에서는 ServletContext에 필터(Filter) 빈을 DelegatingFilterProxy로 감싸서 등록하지 않아도 된다. SpringBoot가 서블릿 필터의 구현체 빈을 찾으면 DelegatingFilterProxy 없이 바로 필터 체인(Filter Chain)에 필터를 등록해주기 때문이다.

Spring 필터 등록 로그

java.lang.RuntimeException
at com.mangkyu.MyFilter.doFilter(MyFilter.java:17)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)

SpringBoot 필터 등록 로그

at com.mangkyu.MyFilter.doFilter(MyFilter.java:17) [main/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.56.jar:9.0.56]

정리

  • DelegatingFilterProxy의 등장 이전: 스프링 빈으로 등록 및 다른 빈 주입이 불가능했음
  • DelegatingFilterProxy의 등장 이후: DelegatingFilterProxy를 통해 스프링 빈으로 등록 및 다른 빈 주입이 가능해짐
  • SpringBoot의 등장 이후: 웹서버를 직접 관리하면서 DelegatingFilterProxy조차 필요없게됨

Q2.json타입을 반환할 때 viewresolver가 어떻게 처리하는가?

A2.

MappingJackson2JsonView

MappingJackson2JsonView는 View를 Json 타입 View로 변환할 때 사용합니다.

MappingJackson2JsonView 동작

  • Spring에서는 Request가 들어오면 web.xml에 정의된 DispatcherServlet에서 가장 먼저 처리한다.
  • 몇 가지 가정을 거치고 Handler(Controller)에서 처리한 결과물을 DispatcherServlet에서 받는다.
  • 그럼 그 결과물을 ViewResolver가 View Template에 맞게 렌더링 한다
processDispatchResult() -> render() -> view.render()

Q3. filter는 어떻게 순서를 지정해주는가?

A3.

  • Filter는 Ordered 인터페이스로 구현한 @Order 어노테이션 또는 커스텀 구현 클래스를 사용합니다.
public interface Ordered {

    /**
     * Useful constant for the highest precedence value.
     * @see java.lang.Integer#MIN_VALUE
     */
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    /**
     * Useful constant for the lowest precedence value.
     * @see java.lang.Integer#MAX_VALUE
     */
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    int getOrder();

}
  • 값이 작을수록 우선순위가 높게 설정되어 있으며, 값의 범위는 int 값으로 지정되어 있네요. (디폴트는 MAX_VALUE)

Q4. HandlerAdapter에서 Controller를 조회하는 과정이 정확히 어떻게 되는가?

A4.

1. initHandlerAdapters에서 List로 handlers들을 저장

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }
    ...
}

2. AnnotationAwareOrderComparator.sort(this.handlerAdapters); 에서 내부적으로 Order 값을 비교하여 오름차순으로 저장한다.

3. HandlerAdapter를 순서대로 조회하고, supports 메서드를 통해 해당 어댑터가 요청된 handler를 처리할 수 있는지 판단한다.

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

Q5. adapter를 조회하는 로직의 시간복잡도는 무엇인가?

A5.

  • List 내부를 순환하며 조회, 판단만하면 되기 때문에 O(N)이 아닐까 생각합니다.

Reference

이전 게시물

Spring | Spring MVC 패턴 - MVC와 서블릿 (1)