1. 개요
스프링 애플리케이션에 요청이 들어오면, 스프링에서는 자동으로 요청 정보 (RequestParam, RequestBody)를 Java Type으로 바인딩해준다. @RequestParam, @RequestBody와 같은 어노테이션으로 이 기능을 사용할 수 있는데, 이 과정이 어떻게 이루어지는지 자세히 알아볼 예정이다.
2. 요청 초기
클라이언트에서 요청을 보내면 자바 애플리케이션의 경우 제일 처음 톰캣에 요청이 들어오게 된다.
톰캣은 WAS이자, 서블릿 컨테이너로써 서블릿의 라이프사이클을 관리한다.
즉 요청이 들어오면, Tomcat을 거쳐 서블릿의 구현체인 DispatcherServlet(Spring Application)으로 요청이 흘러간다.
3. 요청을 처리할 메서드 찾기
우리는 보통 아래와 같이 스프링 RequestMapping 로직을 작성한다.
DispatcherServlet은 요청을 받고, 그 요청 처리할 코드와 같은 메서드를 찾는다.
@GetMapping("/{value}")
public void test(@RequestParam("value") String value){
return value;
}
그렇다면 스프링 내부적으로 이 메서드를 찾기 위해 어떤 과정을 거칠까?
스프링 애플리케이션이 실행될 때, 요청 처리 메서드를 HandlerMethod 클래스의 객체로 포장한다.
그리고 HTTP메서드 (GET, POST, DELETE, PATCH, PUT)와 URI를 가지고 RequestMappingInfo 객체를 만들어서, 아래와 같이 Map을 생성한다.
Map<RequestMappingInfo, HandlerrMethod> handlerMethods;
이 Map은 스프링의 주요 구성 요소 중 하나인 HandlerMapping의 구현체RequestMappingHandlerMapping의 필드로 보관된다.
애플리케이션이 실행되고 요청이 들어오면, DistpatcherServlet은 HandlerMapping.getHandler(HttpRequest request) 메서드를 호출한다.
HandlerMethod를 저장했을 때와 마찬가지로 request 객체로부터 요청 URI와 HTTP메서드를 추출하여 RequestMappingInfo 객체를 생성, 요청을 처리할 HandlerMethod를 찾는다.
4. 요청 처리
이제 DispatcherServlet은 요청을 처리할 메서드에 대해서 알게 되었다.
DistpatcherServlet은 메서드를 알고 있지만, 직접 이 메서드를 실행하지 않는다.
스프링에서는 내부적으로 다양한 기능을 유연하게 제공하기 위해 DistpatcherServlet, HandlerMapping 과같이 역할을 분리하여 요청을 처리한다.
HandlerMethod를 처리하는 역할은 HandlerAdapter의 구현체인 RequestMappingHandlerAdapter가 수행한다.
아래 코드는 HandlerMapping을 설명할 때, 봤던 예시 코드이다. 이 메서드가 요청을 처리할 HandlerMethod라고 가정한다음 설명하겠다.
@GetMapping("/")
public void test(@RequestParam("value") String value){
return value;
}
스프링에서는 HTTP 요청데이터를 편리하게 로직을 작성할 수 있도록 Java Type으로 바인딩해준다고 개요에서 언급하였다.
HandlerAdapter가 이 메서드를 실행할 때, @RequestParam이라는 어노테이션을 만나게된다.
스프링은 어노테이션을 통해 내부 기능을 제공하는데, 이 내부기능을 HandlerMethodArgumentResolver 객체로 제공한다.
RequestMappingHandlerMapping 내부에 HandlerMethod 정보를 담고 있던 Map 필드가 있었던 것처럼
HandlerAdapter에는 내부적으로 HandlerMethodArgumentResolver의 리스트가 존재한다.
List<HandlerMethodArgumentResolver> argumentResolvers;
Spring 애플리케이션이 실행될 때 내부적으로 afterPropertiesSet() 메서드를 호출하여 미리 Resolver들을 등록해놓는다. HandlerMethodArgumentResolver는 supportParameter라는 메서드가 존재하는데, 이 메서드를 통해 어노테이션을 구현할 ArgumentResolver를 찾을 수 있다.
RequetMappingHandlerAdapter는 ArgumentResolver리스트를 순회하면서 supportParameter메서드를 호출한다. → 맞는 ArgumentResolver를 찾을시 true 반환
True 값이 반환되면 RequsetMappingHandlerAdapter는 ArgumentResolver의 resolveArgument 메서드를 호출하여 어노테이션을 구현한다. → 요청 데이터를 Java Type으로 매핑
참고
HandlerMethodArgumentResolver의 경우 네종류의 구현체가 존재한다.
- RequestParamMethodArgumentResolver (예시에서 사용되는 Resolver)
- PathVariableMethodArgumentResolver
- RequestResponseBodyMethodProcessor
- ModelAttributeMethodProcessor
위 그림은 전체 과정을 나타낸다.
5. 결론
- 스프링은 내부적으로 역할을 분리하며, 유연한 구조 위에서 다양한 기능을 제공한다.
- 기능 중 하나인 요청 데이터 바인딩을 통하여 개발자가 비즈니스 로직에 집중할 수 있도록 도와준다.
'Springboot' 카테고리의 다른 글
[Spring Boot] 에러 로그를 이메일로 자동 전송하기 – SMTP + Logback 연동 (0) | 2025.03.23 |
---|---|
[Spring + Redis] 비밀번호 설정 및 Lettuce 연결 설정 정리 (0) | 2025.03.21 |
[스프링부트] ObjectMapper JsonNode Object로 변환 (0) | 2025.03.12 |
[SpringSecurity] BCrypt 비밀번호 인증 구현 (0) | 2025.03.12 |
[SpringBoot] Filter에서의 예외처리 (0) | 2025.03.08 |