Archive/Spring Web MVC

3.2 핸들러 메소드 : argument & return value (overview, 처리 구현체 자동 등록)

nimkoes 2021. 7. 12. 21:58
728x90

 

 

@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 를 별다른 설정 없이 너무나 당연한 권리 찾듯 사용하는게 어떻게 가능했는지에 대해 간략하게 살펴 보았다.

 

어떻게 해서 사용할 수 있었는지 이제는 알기 때문에 어떻게 활용할 수 있는지에 대해 정리해 보려 한다.

 

 

 

 

728x90