여기서 파리미터 매핑을 스프링에서 해준다! 하지만 어떤 경우에는 어노테이션 (@RequestBody, @PathVariable)을 넣어줘야 되는 경우도 있고, 어떤 경우에는 어노테이션을 생략해도 된다.(@ModelAttribute, @RequestParam) 심지어 어느 경우는 어노테이션이 없는 경우도 있다.(Pageable)
이번 기회에 날잡아서 스프링에서 어떻게 파라미터에 값을 넣어주는지, 어떤 경우에 어노테이션이 필요한 지 살펴보자.
디스패처 서블릿 부터 시작한다
스프링 MVC는 프론트 컨트롤러 패턴을 사용한다. 요청을 처리하는 과정에서 중복되는 과정을 프론트 컨트롤러에서 모아서 처리한다.
우리가 궁금해하는 컨트롤러 메서드의 파라미터 처리도 디스패처 서블릿과 관련된 어디에선가 처리할 것이다!
doService
디스패처 서블릿은 doService라는 메서드를 통해 요청을 처리한다. doService는 doDispatch 메서드로 요청 처리를 넘긴다.
try { doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } if (this.parseRequestPath) { ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); } } }
doDispatch
doDispatcher는 핸들러에게 요청을 처리하도록 한다. 정확하게 말하면 ha.handle(processedRequest, response, mappedHandler.getHandler());를 통해 핸들러 어댑터를 통해 핸들러에게 요청을 처리하도록 한다.
// Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
// Determine handler adapter for the current request. HandlerAdapterha= getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler. Stringmethod= request.getMethod(); booleanisGet= HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { longlastModified= ha.getLastModified(request, mappedHandler.getHandler()); if (newServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } }
if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) { return; }
applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = newServletException("Handler dispatch failed: " + err, err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, newServletException("Handler processing failed: " + err, err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } }
Handler와 HandlerAdapter
위 DispatcherServlet의 ha.handle(processedRequest, response, mappedHandler.getHandler()); 코드가 핸들러 어댑터를 활용해서 핸들러에게 요청을 처리하도록 한다.
여기서 mappedHandler는 HandlerExecutionChain이라는 객체다. HandlerExecutionChain은 핸들러와 같이 실행되는 인터셉터들을 가지고 있다. HandlerMapping 인터페이스는 getHandler(HttpServletRequest request)를 통해 해당 요청을 처리해야하는 핸들러와 적용되야 하는 인터셉터를 포함한 HandlerExecutionChain을 반환한다.
여기서 HandlerExecutionChain은 핸들러를 Object로 저장하고 있다. 즉 요청을 처리할 핸들러가 어떤 메서드를 통해 요청을 처리할 줄 모른다는 뜻이다.
결국 Object로 핸들러를 전달받으면 어떤 메서드를 호출해야 할 지 알 수 없다. 특히 스프링에서는 다양한 종류의 핸들러가 존재해서 하나의 타입으로 캐스팅 할 수도 없다. 이래서 HandlerAdapter가 존재한다. HandlerAdapter는 전달받은 핸들러가 어떤 객체이든 해당 핸들러를 호출할 수 있는 방법을 추상화한 인터페이스이다!!!
RequestMappingHandlerAdapter에서 ArgumentResolver를 관리한다.
그래서 ha.handle(processedRequest, response, mappedHandler.getHandler());를 디버깅을 해보면 AbstractHandlerMethodAdapter의 handle메서드를 호출한다.