오늘은 제가 참여하고 있는 부트캠프 과정인 Kernel360
에서 유명 자바 강사이신 박은종 디렉터
님과 함께 스터디를 진행하였습니다.
스터디의 주제는 Spring Web MVC Framework
이었고 그 중 해당 프레임워크의 구조에 대해서 이야기를 나누고 있었습니다.
Spring Web MVC 프레임워크의 요청 처리 흐름
해당 이미지를 보고 처리 흐름을 알아보겠습니다.
- 클라이언트로부터의 요청이 서버에 도착합니다.
- 서버의 요청은
Dispatcher Servlet
에 먼저 도달합니다. 이것은 스프링 프레임워크가프론트 컨트롤러 패턴
으로 구성되어있기 때문인데요, 모든 요청을 적절한 처리기에 전달하는 역할을 합니다. - 요청이 도착했으면 Dispatcher Servlet은
HandlerMapping
을 통해 요청을 처리해야할 적절한 컨트롤러를 찾게 됩니다. - 이 때, 적절한 컨트롤러를 찾았다면 Dispatcher Servlet은 다시
HandlerAdapter
를 통해서 컨트롤러의 처리 메서드를 호출합니다. - 이 때 호출된 컨트롤러는 비즈니스 로직을 실행하고 데이터와 뷰의 이름을 반환합니다.
- 컨트롤러가 반환하는 뷰 이름을 바탕으로
View Resolver
가 동작하여 해당하는 뷰 객체를 찾아줍니다. - 그리고 뷰는 모델 데이터를 사용해 최종적으로 사용자에게 보여줄 화면을 생성합니다.
- 생성된 뷰가 클라이언트 응답으로 전송됩니다.
저는 이러한 구조를 보고 의문을 가지게 되었습니다. 🤔
왜 굳이 나뉘었을까?
해당 구조를 보니, Dispatcher Servlet이 Controller로 요청을 보낼 때, 두번에 거쳐서 요청을 보내게 됩니다.
먼저 HandlerMapping을 통해서 적절한 컨트롤러를 찾아내고 HandlerAdapter를 통해 실제 메서드를 호출하게 되죠.
이 때 HandlerMapping에서 컨트롤러를 찾아서 바로 요청을 보내는게 더 간단하고 빠를 것 같다고 느꼈습니다.
왜 굳이 HandlerAdapter를 통해서 요청을 보내게 될까요?
HandlerMapping과 HandlerAdapter를 알아보자
먼저 HandlerMapping과 HandlerAdapter는 모두 인터페이스 구조로 되어있습니다.
HandlerMapping.java
public interface HandlerMapping {
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
HandlerMapping 인터페이스는 getHandler
라는 메서드를 정의하고 있는데요, 이 메서드는 HttpServletRequest
객체를 받아서 HandlerExecutionChain
객체를 반환합니다.
이 때, HandlerExecutionChain 객체는 요청을 처리할 핸들러 즉 Controller
와 요청을 처리하기 전 후로 실행해야 하는 Interceptor
를 포함하고 있습니다.
HandlerAdapter.java
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
Long getLastModified(HttpServletRequest request, Object handler);
}
HandlerAdapter 인터페이스는 supports
메서드를 통해 해당 핸들러를 지원하는지 여부를 판단하고, handle
메서드를 통해 실제 요청을 처리합니다.
처리 결과는 ModelAndView
객체에 담겨 DispatcherServlet 으로 반환되며 이를 통해 뷰를 렌더링하고 클라이언트에게 응답을 보냅니다.
이렇게 두 인터페이스가 책임을 나누고 있는 이유는 다양한 종류의 컨트롤러를 지원하고 확장가능하게 만들도록 하기 위해서 입니다. 이 구조를 가짐으로써 Spring MVC의 Plug And Play
아키텍처를 가능하게 합니다.
Plug & Play
Plug and Play 아키텍처는 컴포넌트나 모듈을 시스템에 추가할 때 추가적인 설정 없이 쉽게 통합하고 사용할 수 있도록 설계하는 아키텍처를 말합니다.
따라서 소프트웨어가 독립적인 모듈로 구성되어 있어야하고, 컴포넌트를 쉽게 교체할 수 있어야 하고 이러한 작업이 일어났을 때 시스템의 재부팅이 필요 없이 자동으로 인식하고 필요한 구성을 처리할 수 있어야 합니다.
Spring 에서도 이미 저희는 플러그 앤 플레이 아키텍처를 경험했는데요,
Spring 의 IOC container
는 애플리케이션의 객체를 빈 이라는 형태로 자동으로 생성하고 생명주기를 관리합니다. 개발자는 자유롭게 @Component
, @Service
, @Repository
등의 어노테이션으로 빈을 정의하고 Spring 은 이를 자동으로 인식하고 의존성 주입을 합니다.
컨트롤러가 다양한 종류가 있었다고?
HandlerMapping 은 요청의 URL을 보고 어떠한 컨트롤러가 처리할지를 결정하는 매핑 정보를 관리합니다.
그리고 HandlerAdapter 는 실제 DispatcherServlet 과 컨트롤러 사이의 호환성을 제공합니다.
이때 Controller 의 다양한 타입에 맞게 호환성을 가지고 동작하도록 책임을 나누게 된 것 입니다.
@Controller
public class MyController {
@RequestMapping("/myPath")
public ModelAndView handleRequest() {
// 컨트롤러 로직..
}
}
public class MyHttpRequestHandler implements HttpRequestHandler {
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 직접 서블릿 API를 사용하는 컨트롤러 로직..
}
}
이건 가장 대표적인 두 종류의 컨트롤러 클래스 입니다.
MyController
는 어노테이션 기반의 컨트롤러이고, MyHttpRequestHandler
는 HttpRequestHandler
의 구현체로서, 서블릿 API를 직접 사용하는 형태의 컨트롤러 입니다.
이 두 컨트롤러는 요청을 처리하는 방식이 다릅니다.
HandlerMapping 은 /myPath 와 같은 URL을 입력받았을 때, 어떠한 컨트롤러 객체를 사용할지 결정합니다. 하지만 여기서 바로 컨트롤러 객체를 호출하는 것은 불가능합니다. 왜냐하면 컨트롤러의 종류에 따라 호출 방법이 다르기 때문입니다. 이 때 HandlerAdapter 가 각 컨트롤러 타입에 맞는 HandlerAdapter 를 구현하여 DispatcherServlet 이 일관된 방식으로 요청을 처리하도록 합니다.
public class MyControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Objects handler) {
return (handler instanceof MyController);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
return ((MyController)handler).handlerRequest();
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
// 마지막 수정 시간을 반환하는 로직
}
}
위 코드는 MyHttpRequestHandler 를 처리하는 예시입니다.
이처럼 각 타입마다 요청을 처리하는 방식이 다릅니다. 위는 ModelAndView 를 직접 반환이 가능하지만 아래 예제에서는 불가능합니다.
이는 간단한 예제이기 때문이고, 실제 구현은 더욱 복잡하게 구현되어 있으므로 모든 타입을 한번에 처리하기보다
HandlerMapping 을 통해 매핑 후 HandlerAdapter 를 통해 실제 호출 및 결과를 반환 받는 구조가 더 객체지향적인 구조가 됩니다.
좋은 인사이트를 나눠주신 박은종 디렉터님께 샤라웃을 날립니다. 🎉🎉🎉
'Backend > Spring 🌱' 카테고리의 다른 글
ServiceImpl 쓰지 말까? (0) | 2024.06.14 |
---|---|
스프링 빈과 컨테이너 (0) | 2024.03.12 |
스프링부트 프로젝트 시작하는 법 (0) | 2024.02.02 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆