웹 요청, 응답 흐름
Web 서버
개념
정적 컨텐츠 제공 : WAS를 거치지 않고 바로 자원을 제공
동적 컨텐츠 제공 : 클라이언트의 request를 WAS에 보내기
클라이언트(외부 방문객) -> 웹 서버 (건물 입구) -> WAS (건물 내부) -> 서블릿 필터 체인(건물 로비의 각종 검문소/안내 데스크) -> DispatcherServlet (중앙 안내 센터) -> Controller (담당 연구원) -> 서비스 로직 -> 응답
WAS는 요청을 처리할 서블릿(스프링 MVC에서는 DispatcherServlet)에게 넘기기 전, 먼저 등록된 '서블릿 필터 체인'을 순서대로 통과시킨다. 이는 FilterChain이라는 객체를 통해 관리된다.
DispatcherServlet은 요청을 분석해 HandlerMapping, HandlerAdapter 등을 통해 적절한 Controller를 찾아 실행시킴.
이때 클라이언트에게 최종 응답은 필터 체인을 다시 한번 통과할 수 있다.
Web Server, WAS, Servlet이 동작하는 원리 서술
client - web server - was - db의 형태로 애플리케이션을 구성했을 경우를 가정한다.
클라이언트의 요청이 만약 정적 컨텐츠라면, 웹 서버 단에서 바로 응답을 보낸다.
요청이 만약 동적 컨텐츠라면 요청을 먼저 웹서버에서 받아 was에 보내 관련된 servlet을 메모리에 올린다.
was는 web.xml을 참조해 메모리에 올라간 servlet에 대한 쓰레드를 생성한다.
이때 HttpServletRequest 객체와 HttpServletResponse 객체를 생성하여 servlet에 전달한다.
쓰레드는 servlet의 메서드를 호출하고, 해당 메서드는 로직을 통해 db에 접근하여 response를 작성하고 , 이를 was에 전달한다.
was는 response를 HttpServletResponse 객체의 형태로 바꿔 웹 서버로 전달한다.
was는 생성된 쓰레드를 종료하고, HttpServletRequset 객체와 HttpServletResponse 객체를 제거한다.
실제 복잡한 예시로
클라이언트 요청
-> WAS
-> 서블릿 필터 체인 시작
-> 개발자 등록 필터 1 (예: 인코딩 필터)
-> 개발자 등록 필터 2 (예: 로깅 필터)
-> [스프링 시큐리티 필터 체인 시작] (이 자체가 서블릿 필터 중 하나)
-> SecurityContextPersistenceFilter
-> LogoutFilter
-> UsernamePasswordAuthenticationFilter
-> ExceptionTranslationFilter
-> FilterSecurityInterceptor
-> ... (수많은 스프링 시큐리티 내부 필터들)
-> [스프링 시큐리티 필터 체인 끝]
-> 개발자 등록 필터 3 (예: CORS 필터)
-> ... (다른 서블릿 필터들)
-> 서블릿 필터 체인 끝
-> DispatcherServlet
-> HandlerMapping
-> HandlerAdapter
-> Controller 실행
-> Service, Repository... (비즈니스 로직)
-> Controller 결과 반환 (모델 & 뷰 이름)
-> ViewResolver
-> View 렌더링
-> 응답 데이터 생성
-> 서블릿 필터 체인 역순 통과 (응답 시 처리할 필터들)
-> WAS
-> 클라이언트 응답
"서블릿에서 어떻게 구현되어 있기에 각 요청마다 알맞은 컨트롤러가 호출되는가?" → 이 질문은 서블릿과 스프링 MVC의 연결 구조를 이해하면 명확하게 설명할 수 있어요.
✅ 흐름 요약
브라우저가 요청 (GET /hello)
WAS(예: Tomcat)가 DispatcherServlet에 요청 전달
DispatcherServlet의
service()
→doGet()
실행HandlerMapping으로 알맞은 @Controller 메서드 찾기
찾은 메서드 실행 후, 결과를 ViewResolver를 통해 응답으로 전송
✅ 그럼 정말로 doGet()
이 실행되냐?
doGet()
이 실행되냐?✔ 맞습니다.
DispatcherServlet
은 결국HttpServlet
을 상속한 서블릿입니다.그래서 WAS는 DispatcherServlet의
doGet()
,doPost()
를 호출하게 됩니다.
public class DispatcherServlet extends FrameworkServlet {
// FrameworkServlet → HttpServlet 상속
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
→ 내부적으로는 processRequest()
라는 메서드가 호출됩니다.
✅ 그 다음, 어떻게 컨트롤러로 연결될까?
📦 processRequest()
내부는 대략 이런 흐름:
processRequest()
내부는 대략 이런 흐름:HandlerMapping에서 요청 URL에 맞는 컨트롤러 메서드(
@RequestMapping
)를 찾음 (예:/hello
→HelloController.hello()
)HandlerAdapter를 통해 해당 컨트롤러 메서드를 실행
결과(
ModelAndView
)를 ViewResolver로 해석해 응답
✅ 실제 구조 (중요 메서드 흐름)
DispatcherServlet.service()
└── doGet() / doPost() → processRequest()
└── doDispatch()
├── getHandler(request) // 어떤 @Controller 메서드 호출할지 결정
├── getHandlerAdapter() // 호출 가능한 방식인지 확인
├── handlerAdapter.handle() // 실제 컨트롤러 메서드 실행
└── processDispatchResult() // 결과를 뷰로 반환
✅ 결론
요청이 오면 서블릿의 doGet()
이 호출되나요?
✅ 맞습니다. DispatcherServlet이 서블릿이고, WAS가 doGet()
호출
근데 왜 @Controller 메서드가 실행되죠?
DispatcherServlet이 내부적으로 HandlerMapping
과 HandlerAdapter
를 통해 매핑된 메서드를 찾아 호출
우리가 doGet/doPost를 안 써도 되던데요?
스프링이 DispatcherServlet 내부에서 대신 구현해두었기 때문입니다
Last updated