@RequestMapping 으로 HTTP request 에 대한 handler 를 매핑하면 이 handler 를 사용할 때 다양한 매개변수와 반환 값을 유연하게 사용할 수 있다.
다음은 공식 문서를 참고해 작성한 handler method 에서 사용할 수 있는 매개변수들과 반환 값의 목록이다.
Controller method argument (handler method argument)
: WebRequest
: NativeWebRequest
: javax.servlet.ServletRequest
: javax.servlet.ServletResponse
: javax.servlet.http.HttpSession
: javax.servlet.http.PushBuilder
: java.security.Principal
: HttpMethod
: java.util.TimeZone +
: java.time.ZoneId
: java.io.InputStream
: java.io.Reader
: java.io.OutputStream
: java.io.Writer
: @PathVariable
: @MatrixVariable
: @RequestParam
: @RequestHeader
: @CookieValue
: @RequestBody
: HttpEntity<B>
: @RequestPart
: java.util.Map
: org.springframework.ui.Model
: org.springframework.ui.ModelMap
: RedirectAttributes
: @ModelAttribute
: Errors
: BindingResult
: SessionStatus + class-level
: @SessionAttributes
: UriComponentsBuilder
: @SessionAttribute
: @RequestAttribute
: Any other argument
Controller method return value (handler method return value)
: @ResponseBody
: HttpEntity<B>
: ResponseEntity<B>
: HttpHeaders
: String
: View
: java.util.Map
: org.springframework.ui.Model
: @ModelAttribute
: ModelAndView object
: void
: DeferredResult<V>
: Callable<V>
: ListenableFuture<V>
: java.util.concurrent.CompletionStage<V>
: java.util.concurrent.CompletableFuture<V>
: ResponseBodyEmitter
: SseEmitter
: StreamingResponseBody
: Reactive types - Reactor, RxJava, or others through ReactiveAdapterRegistry
: Any other return value
자세한 내용은 문서를 보는게 가장 좋겠지만 쉽지 않은 일일 수 있기 때문에, 자주 사용하는 것 위주로 정리를 할 계획이다.
개인적으로 활용하는 파트도 중요하지만 그 앞의 원리와 설정에 대해 아는게 더 중요하다고 생각한다.
handler method 의 argument 와 return type 을 실제로 활용하는 방법에 대해 정리하기 앞서 누가 어디서 뭘 어떻게 했길래 가능한건지부터 살펴보려 한다.
디버그 모드로 서버를 실행한 다음 한줄씩 실행 과정을 따라가 보면서 확인해보기 위해 가장 기본적인 형태의 handler method 를 만들고 DispatcherServlet 클래스의 doService 메소드에 break point 를 걸어두었다.
package me.nimkoes;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyHelloController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "hi";
}
}
HTTP GET 요청으로 /hello 를 보내면 "hi" 라는 문자열을 HTTP response body 에 실어 보내는 handler method 이다.
DispatcherServlet 의 doService 메소드에 있는 doDispatch 안으로 들어간다.
하는 일을 보면 크게
1. 요청을 처리 할 handler method 찾기 :: getHandler
2. 요청을 처리할 수 있는 adapter 찾기 :: getHandlerAdapter
3. 실제로 요청을 처리하기 :: ha.handle
4. 응답으로 보낼 결과 처리하기 :: processDispatchResult
정도인 것으로 보인다.
지금은 실제로 handler method 를 처리할 때 argument 를 어떻게 처리하는지 알아보는 것을 목적으로 하고 있기 때문에
// Actually invoke the handler. 라고 되어있는 ha.handle 의 안쪽으로 들어가 봤다.
한줄씩 실행하다보니 관련된 것 같아 보이는 부분을 발견 하였다.
argument 를 처리할 수 있는 argumentResolver 와 return value 를 처리하는 returnValueHandlers 를 등록해 주는 부분이 있었다.
한가지 의문은 등록 해준적도 없는 저 구현체들은 어디서 나왔냐는 건데, 실제로 handler method 를 실행하는 invokeHandlerMethod 메소드는 RequestMappingHandlerAdapter 클래스에 정의되어 있는데, 이 클래스에 getDefaultArgumentResolvers 메소드와 getDefaultReturnValueHandlers 메소드에서 등록해주고 있다.
그리고 parameterNameDiscoverer 를 등록하는 부분이 있는데, 아무래도 paramter 의 이름을 찾는 일을 하는 것 같다.
실제로 등록되는 클래스를 보면 Reflection 이나 Local 변수와 관련된 Table 정보를 참고해서 parameter name 을 찾는 것 같아 보인다.
이렇게 전처리?를 다 하고나면, 정말로 handler method 를 실행하는 부분인 invocableMethod.invokeAndHandle 메소드 에서 요청 처리를 시작 한다.
invokeAndHandle 은 요청을 처리한 다음 앞서 등록한 returnValueHandlers 로 return value 를 처리하도록 되어 있다.
getMethodArgumentValues 로 args 들을 추출해낸 다음 main 메소드 실행하 듯 doInvoke 를 호출하는 것을 볼 수 있다.
this.resolvers 에는 앞서 default 로 자동 등록해준 26개의 argument 를 처리할 수 있는 구현체들의 LinkedList 이다.
지금 만든 handler method 는 parameter 가 없기 때문에 return EMPTY_ARGS 를 실행 하겠지만, 만약 그렇지 않았다면 처리 가능한 parameter 인지 검사하고, 처리 가능한 경우 arg 배열에 하나씩 그 값을 추가 한다.
resolvers 의 resolveArgument 의 내용을 보면 HandlerMethodArgumentResolver 인터페이스를 구현한 구현체들의 resolveArgument 메소드를 실행하도록 되어 있다.
각 구현체가 어떻게 argument 를 처리하는지 까지는 다루지 않고 어떻게 사용하는지에 대해서만 정리할 예정이다.
드디어 모든 준비가 끝나면 doInvoke(args) 를 실행해서 handler method 를 실행 한다.
java Reflection 을 사용해서 method 의 modifier 가 (접근 제한자) public 이 아닌 경우 public 접근이 가능하도록 수정한 다음 handler method 를 실행 한다.
테스트 해보기 위해 만든 handler method 의 반환 값인 "hi" 문자열이 returnValue 에 담겼고, 이제 returnValueHandlers 의 handleReturnValue 로 이 값을 처리한다.
이후 처리되는 과정도 한줄씩 실행해보면서 보았지만 더 깊은 내용을 정리하는건 좋지 않다고 생각했다.
지금은 DispatcherServlet 의 doService 메소드를 시작점으로 break point 를 잡았지만, 사실 DispatcherServlet 은 단지 하나의 Servlet 일 뿐이라는 것을 생각해보면 filter, interceptor 등 전후에 실행하는 Servlet lifecycle 을 따라야 한다는 것 정도만 기억하고 넘어가도 괜찮다는 생각이다.
아무튼, 기나긴 여정을 지나 실행 결과를 받아볼 수 있었다.
본 글을 마무리 하자면, 맨 위에 목록으로 정리 한 handler method 의 Argument 와 Return value 를 별다른 설정 없이 너무나 당연한 권리 찾듯 사용하는게 어떻게 가능했는지에 대해 간략하게 살펴 보았다.
어떻게 해서 사용할 수 있었는지 이제는 알기 때문에 어떻게 활용할 수 있는지에 대해 정리해 보려 한다.
'Archive > Spring Web MVC' 카테고리의 다른 글
3.3 핸들러 메소드 (execute & using handler method) (작성중) (0) | 2021.07.15 |
---|---|
3.1 요청 매핑하기 (handler method) (1) | 2021.07.11 |
2.6 HTTP 메시지 컨버터 (JSON, XML) (0) | 2021.07.09 |
2.5 리소스 핸들러 (default Servlet) (1) | 2021.07.08 |
2.4 핸들러 인터셉터 (HandlerInterceptor interface) (0) | 2021.07.07 |