[스프링 MVC] - 핸들러 매핑과 핸들러 어댑터의 구조 이해
스프링 MVC를 공부하던 도중 요청에 맞는 컨트롤러가 매핑되어 호출되는 과정에서 그 내부 구조가 궁금하여 학습하게 되었다.
스프링 MVC는 위와 같은 흐름으로 진행된다. 그림을 보면 Dispatcher Servlet이 중간에서 요청과 반환의 중심에 있는 것을 확인할 수 있다.
1. Dispatcher Servlet
Dispatcher Servlet이란? dispatch는 "보내다"라는 뜻을 가지고 있다. 이는 다른 많은 웹 프레임워크와 마찬가지로 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해 주는 프론트 컨트롤러(Front Controller)라고 정의할 수 있다. 즉 Dispatcher Servlet은 request에 대한 전반적인 요청을 처리한다고 볼 수 있다.
지금은 전혀 사용하지 않지만, 과거에 주로 사용했던 스프링이 제공하는 간단한 컨트롤러로 핸들러 매핑과 어댑터를 이해해 보자.
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
스프링이 처음에 제공했던 딱딱한 형식의 컨트롤러다. 이는 @Controller 어노테이션과는 전혀 다르다. 아래는 위 인터페이스를 구현한 클래스이다.
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return new ModelAndView("new-form");
}
}
이 컨트롤러는 어떻게 호출될 수 있을까? 이 질문에 대한 답이 바로 핸들러 매핑과 핸들러 어댑터이다.
2. 핸들러 매핑과 핸들러 어댑터
HandlerMapping(핸들러 매핑)
- 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다.
- ex) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
HandlerAdapter(핸들러 어댑터)
- 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
- ex) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.
스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해 두었다. 개발자가 직접 핸들러 매핑과 핸들러 어댑터를 만드는 일은 거의 없다.
스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터는 아래와 같다.
HandlerMapping
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용 1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다. |
HandlerAdapter
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용 1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리 2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리 |
핸들러 매핑도, 핸들러 어댑터도 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.
3. 핸들러 매핑과 핸들러 어댑터의 동작 과정
1. 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.
- 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController를 반환한다.
2. 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출한다.
boolean supports(Object handler); //올바른 handler adapter를 찾는 메서드
- SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
3. 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.
- SimpleControllerHandlerAdapter는 핸들러인 OldController를 내부에서 실행하고, 그 결과를 반환한다.
4. Dispatcher Servlet의 동작 방식
마지막으로 Dispatcher Servlet의 동작 방식에 대해 정리하면서 조금 더 이해를 돕고 마무리하겠다.
0. 클라이언트가 Dispatcher Servlet에게 HTTP 요청을 보낸다.
1. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾는다.
2. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달한다.
3. 핸들러 어댑터가 컨트롤러로 요청을 위임한다.
4. 비즈니스 로직을 처리한다.
5. 컨트롤러가 반환값을 반환한다.
6. 핸들러 어댑터가 반환값을 처리한다.
7. 서버의 응답을 클라이언트로 반환한다.(viewResolver를 통해 view를 반환하는 과정은 이번 글의 주제에 좀 더 집중하기 위해 생략한다.)
만약 위의 내용을 완벽히 이해하기 어렵다면 "디스패처 서블릿을 통해 요청을 처리할 컨트롤러를 찾아서 위임하고, 그 결과를 받아오는구나" 정도로만 이해해도 괜찮다.
5. @RequestMapping
스프링을 공부해 봤던 사람들이라면 대부분 알고 있을 어노테이션이다. 이것이 바로 지금 스프링에서 주로 사용하는 어노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터이다. 실무에서는 99.9% 이 방식의 컨트롤러를 사용한다.
느낀 점
스프링 MVC를 깊게 학습하기 전에 나는 일단 프로젝트를 느껴보고자 jpa를 먼저 배우고 개발을 했었다. 그렇지만 개발을 할 때마다 동작은 되지만 왜? 왜 이렇게 동작할까? 라는 의문점들을 항상 가지고 있었다. 요즘 스프링 MVC를 공부하면서 예전에 가지고 있었던 의문점들을 하나하나 해결해나가고 있다. 단순히 @RequestMapping를 사용했었던 나였지만 이제는 과거로 올라가서 이의 발전 과정, 동작 원리, 흐름을 알고 사용할 수 있어서 더 유용한 어노테이션이라는 것을 느낄 수 있었다. 참 신기하고 대단하다. 예전 개발자분들은 이런걸 어떻게.. 생각하고.. 그걸 또 만들었다..? 동기부여 받고 갑니다. 선배님들. |
출처/참고
인프런 - 스프링 MVC 1편(김영한 강사님)