IT/Spring

스프링 프레임워크와 서블릿 컨테이너의 관계

Bamdule 2024. 12. 24. 18:53

스프링 프레임워크와 서블릿 컨테이너의 관계에 대해서 정리해보려고 한다.
기초적인 이야기지만 창피하게도 필자는 정확히 알지 못했다. 

이야기를 시작하기 앞서서 스프링 프레임워크에서 서블릿은 필요한가?
필요하다면 서블릿 컨테이너와 스프링 컨테이너는 어떤 차이가 있는가?
디스패처 서블릿은 무엇이고 왜 서블릿이라는 용어가 뒤에 붙는가?

이 질문들에 대해서 답하지 못한다면 스프링의 기초적인 내용을 놓치고 있는 것이다.
이번 글에서 아래와 같은 내용에 대해서 간단히 정리해보겠다.

1. 스프링프레임워크
2. 서블릿 컨테이너
3. 디스패처 서블릿
4. 디스패처 서블릿 동작 흐름
5. 스프링부트 애플리케이션 실행 흐름

1. 스프링 프레임워크란?

간단하게 말해서 스프링 프레임워크(Spring Framework)는 자바(Java) 기반의 애플리케이션 개발을 지원하는 오픈 소스 프레임워크이다.

그리고 복잡한 애플리케이션 개발을 간소화하고, 개발 생산성과 코드의 유연성을 높이는 데 도움을 준다.

1) 주요 특징

  • POJO (Plain Old Java Object) 기반 개발
    • 순수 Java 객체를 이용해 비즈니스 로직을 구현
    • POJO를 지향하는 이유는 옛날에 사용하던 EJB(Enterprise JavaBean)는 굉장히 무거웠고 복잡했으며 객체 간 의존성이 높았기 때문에 유지보수 및 개발이 어려웠고 이 때문에 순수 POJO 방식 개발이 선호되었다.
  • 의존성 주입(Dependency Injection, DI)
    • 객체 간의 의존 관계를 명시적으로 설정하고 스프링 컨테이너가 이를 관리하도록 한다.
    • 이를 통해서 코드의 결합도를 낮추고, 유지 보수가 쉬워졌다.
스프링 컨테이너(Spring Container)는 스프링 프레임워크의 핵심으로, 객체를 생성하고, 관리하며, 필요한 의존성을 주입하는 역할을 한다. 스프링 컨테이너는 애플리케이션의 객체들을 Bean이라고 부르며, 이 Bean들을 생성, 초기화, 소멸, 의존성 주입 등을 담당한다.
  • AOP(Aspect-Oriented Programming)
    • 애플리케이션의 핵심 로직과 부가적인 기능(예: 로깅, 트랜잭션 관리)을 분리할 수 있다.
  • 테스트 용이성
    • DI와 POJO 기반 개발을 통해 단위 테스트와 통합 테스트를 쉽게 작성할 수 있다.
  • 모듈화된 구조
    • 다양한 모듈로 구성되어 있으며, 필요한 부분만 선택적으로 사용할 수 있다.
      • Spring Core
        • DI 및 IoC(제어의 역전)를 지원하는 핵심 모듈.
      • Spring MVC
        • 웹 애플리케이션 개발을 위한 Model-View-Controller 아키텍처를 지원.
      • Spring Data
        • 데이터베이스 작업을 쉽게 처리하기 위한 모듈.
      • Spring Security
        • 인증과 권한 부여를 다루는 보안 모듈.
여담이지만 스프링 프레임워크는 자바 진형에서 축복같은 존재다. 스프링 프레임워크의 철학으로 인해 객체지향 개발이 이토록 보편화될 수 있었던 것 같다!

 

서블릿 컨테이너 (Servlet Container)란?

서블릿 컨테이너란 무엇일까? 스프링 개발 시 필요한가?
결론부터 말하자면, 필요하다.

서블릿 컨테이너는 Java EE(현재는 Jakarta EE) 표준의 일부로, 서블릿과 JSP(JavaServer Pages)를 실행하는 환경을 제공하는 소프트웨어다. 이 컨테이너는 웹 애플리케이션에서 HTTP 요청을 처리하고, 응답을 반환하는 주요 역할을 한다

우선 서블릿 컨테이너가 무엇인지 간단하게 알아보자

  • 주요 기능
    • HTTP 요청 처리: 클라이언트로부터 받은 HTTP 요청을 서블릿에 전달하여 처리한다.
    • 서블릿 생명주기 관리: 서블릿의 생성, 초기화, 소멸 과정을 관리한다.
    • 동시 요청 처리: 여러 요청을 동시에 처리할 수 있도록 멀티스레드 환경을 제공한다.
    • JSP 파일 처리: JSP 파일을 서블릿으로 변환하여 실행한다
    • 응답 반환: 처리한 결과를 클라이언트로 HTTP 응답으로 반환한다

 

서블릿 컨테이너와 WAS

서블릿 컨테이너 환경을 구축하려면 WAS(Web Application Server)를 설치해야 한다. WAS는 서블릿 컨테이너와 더불어 웹 애플리케이션을 실행하고 관리하는 서버 소프트웨어이다.

하지만 스프링 부트에서는 내장 서블릿 컨테이너(예: Tomcat, Jetty, Undertow)를 제공하여, 별도의 외부 서블릿 컨테이너 설치 없이 애플리케이션을 실행할 수 있게 되었다.

그래서 뭔데!?

서블릿 컨테이너와 스프링 컨테이너가 무슨 관계가 있는데요!? 라는 질문에 대해서 디스패처 서블릿이 그 답을 줄 것이다.
디스패처 서블릿은 스프링 MVC 매커니즘의 핵심요소이니 잘 이해해야한다!

디스패처 서블릿(Dispatcher Servlet)

스프링 프레임워크에서 디스패처 서블릿(DispatcherServlet)은 HTTP 통신의 핵심 역할을 수행하는 서블릿이다.
스프링 애플리케이션 실행 시 디스패처 서블릿은 서블릿 컨테이너에 등록되며, 클라이언트의 모든 HTTP 요청을 처리하는 진입점 역할한다.

왜 서블릿이라는 명칭이 붙는가?

디스패처 서블릿은 서블릿 컨테이너와 협력하기 위해 설계되었기 때문에 서블릿이라는 이름이 붙었다.
(디스패처 서블릿도 서블릿 컨테이너 입장에서는 단순히 서블릿이기 때문이다!)
스프링 프레임워크는 HTTP 요청 및 응답 처리를 자체적으로 구현하지 않고, 이러한 역할을 서블릿 컨테이너에 위임했다.
그렇다. 이제 답이 나왔다.
스프링 프레임워크는 HTTP 통신을 위해 서블릿 컨테이너와 협업을 해야한다. 그러므로 서블릿 컨테이너가 필요하다!

스프링 프레임워크에서 서블릿 컨테이너를 의존하는 방법은 다음과 같다

# gradle.build
...

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    내장 톰캣만 사용하고 싶다면 아래 의존성을 가지면 된다. 
    implementation 'org.springframework.boot:spring-boot-starter-tomcat'
}

디스패처 서블릿의 특징

  1. HTTP 요청 진입점
    • 클라이언트의 모든 HTTP 요청이 디스패처 서블릿으로 전달되며, 이를 통해 애플리케이션의 각 구성 요소(Controller, Service 등)로 적절히 연결된다.
  2. 유연한 확장성 제공
    • 디스패처 서블릿은 스프링 MVC의 핵심 인터페이스를 기반으로 설계되어, 개발자가 작성한 Controller, AOP, Interceptor 등의 컴포넌트와 유연하게 연동할 수 있는 메커니즘을 제공한다.

디스패처 서블릿의 동작 방식

https://terasolunaorg.github.io/guideline/5.0.1.RELEASE/en/Overview/SpringMVCOverview.html#overview-of-spring-mvc-processing-sequence

  • 클라이언트 요청
    • 클라이언트가 HTTP 요청을 전송한다 (예: /hello)
  • DispatcherServlet이 요청 수신
    • 서블릿 컨테이너가 요청을 받아 DispatcherServlet에 전달한다.
  • Handler Mapping
    • 요청 URL에 따라 어떤 컨트롤러가 요청을 처리할지 결정한다.
  • Handler Adapter
    • 컨트롤러 호출에 필요한 준비를 하고, 컨트롤러를 호출한다.
  • 컨트롤러 실행
    • 요청을 처리한 후, 결과 데이터(모델)와 뷰 이름(View Name)을 DispatcherServlet에 반환한다.
  • View Resolver
    • 뷰 이름을 기반으로 렌더링할 뷰를 결정한다. (예: JSP, Thymeleaf, JSON 등)
  • 응답 반환
    • 렌더링된 뷰나 데이터(JSON, XML 등)를 클라이언트에 반환한다.

스프링은 멀티 스레드 환경을 어떻게 제공할 수 있는가?

스프링은 서블릿 컨테이너(Tomcat, Jetty)가 제공하는 스레드 풀(Thread Pool)과 멀티 스레드 환경을 활용한다.

스프링의 요청 처리 방식

스프링의 요청 처리 구조는 기본적으로 Thread-Per-Request 모델을 따른다.

  1. 클라이언트 요청이 서블릿 컨테이너로 전달됨
  2. 컨테이너는 스레드 풀에서 스레드를 할당하고 요청을 처리
  3. 요청이 끝나면 스레드는 스레드 풀로 반환됨

스프링에서 서블릿 컨테이너가 제공하는 스레드 풀 관련 설정은 다음과 같이 할 수 있다.

server:
  tomcat:
    threads:
      min-spare: 10
      max: 200
      max-threads: 200
      accept-count: 100
    connection-timeout: 20000   # 밀리초 단위
    keep-alive-timeout: 10000   # 밀리초 단위
  • min-spare: 서블릿 컨테이너가 유지할 최소 유휴 스레드 수이다. 이 값은 시스템 부하가 적을 때도 최소한의 스레드를 유지하도록 설정하는 데 사용된다.
  • max : 서블릿 컨테이너에서 동시에 허용할 수 있는 최대 커넥션 수를 설정한다. (최대 커넥션 수)
  • max-threads: 서블릿 컨테이너가 처리할 수 있는 최대 스레드 수이다. 이 값이 초과하면 요청은 대기열에 대기하게 된다. (최대 스레드 수)
  • accept-count: 대기 큐 크기로, 최대 스레드 수를 초과하는 요청이 들어왔을 때 대기열에서 처리할 수 있는 요청 수이다. 이 값이 초과하면 새로운 연결은 거부된다.
  • connection-timeout: 클라이언트와의 연결이 시간이 초과되면 연결을 종료하는 시간 설정이다.
  • keep-alive-timeout: 연결을 유지할 수 있는 최대 시간 설정이다. 이 시간 동안 클라이언트와의 연결을 재사용할 수 있다.

추가로 서블릿 컨테이너의 스레드풀과 비동기 스레드 풀을 혼동하면 안된다.

서블릿 컨테이너의 스레드 풀: HTTP 요청을 처리하는 데 사용된다. 요청이 들어올 때마다 서블릿 컨테이너는 이 풀에서 스레드를 할당하여 해당 요청을 처리한다

비동기 스레드 풀: 비동기 작업을 처리하기 위한 별도의 스레드 풀이다. @Async 어노테이션을 사용하거나 TaskExecutor를 통해 별도로 관리할 수 있다. 이 스레드 풀은 비동기 작업이 요청을 처리하는 주 스레드와는 다른 스레드에서 실행되도록 도와준다.

스프링부트 애플리케이션 실행 흐름

1) 스프링 애플리케이션 컨텍스트 실행

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
  • SpringApplication.run(MyApplication.class, args)가 호출되면, 스프링 부트 애플리케이션이 시작된다.
  • run() 메서드는 애플리케이션 컨텍스트(ApplicationContext)를 생성하고, 애플리케이션을 실행한다.

2) 애플리케이션 컨텍스트 초기화 및 빈 등록 및 의존성 주입

  • 스프링은 애플리케이션 컨텍스트 초기화 시 @ComponentScan을 통해 빈을 자동으로 검색한다
  • 기본적으로 @SpringBootApplication 어노테이션이 있는 클래스가 위치한 패키지와 그 하위 패키지들에서 빈을 스캔한다
  • @Component, @Service, @Repository, @Controller와 같은 어노테이션을 가진 클래스들이 자동으로 빈으로 등록되고 초기화되고 빈 간의 의존성 주입을 처리한다

3 ) 내장 서블릿 컨테이너 실행

  • 내장된 서블릿 컨테이너(예: Tomcat, Jetty, Undertow)가 자동으로 초기화된다. 이는 외부 WAS를 설정하지 않고 애플리케이션이 독립적으로 실행될 수 있게 한다.
  • 또한 이 과정에서 DispatcherServlet이 등록된다.

 

결론

스프링 프레임워크는 매우 잘 설계된 프레임워크로, 자바로 개발된 다양한 컴포넌트들이 조화롭게 결합되어 작동할 수 있다. 각 컴포넌트가 독립적으로 발전할 수 있는 구조는 큰 장점이다. 이러한 확장성유연성 덕분에 스프링은 지속적으로 발전하며, 많은 개발자들에게 사랑받는 프레임워크로 자리잡을 수 있었다고 생각한다.