서블릿 컨테이너와 스프링 컨테이너(Servlet Container, Spring Container)

rvlwldev·2023년 3월 2일
0

Spring

목록 보기
2/8

서블릿 컨테이너란 웹서버 확장 프로그램의 하나이며
이름 그대로 여러 서블릿을 담고 있으면서 실행하거나
HTTP Response 생성하고 스레드, 세션 관리를 도와준다.
대표적으로 Tomcat, Jetty 등이 서블릿 컨테이너라고 불린다.

스프링 컨테이너란 프로그램이 유연하게 작동될 수 있도록
스프링에서 관리하는 객체(Bean)들의 생성, 관리, 의존성 주입(Dependency Injection), 라이프사이클 관리 등을 담당하는 컨테이너이다.

각각의 서블릿들은 스프링 컨테이너로 빈(Bean)객체를 주입받아 사용할 수 있어
두 컨테이너는 스프링에서 밀접한 관계에 있으며 서로 상호보완적으로 동작한다.

1. Servlet Container란?

웹 서버라고 하면 웹페이지를 사용자에게 전송하는 서버를 말하는데
기본적으로 사용자는 HTTP 정적인 웹페이지만이 요청이 가능하다.

사용자의 상황에 따라서, 사용자가 입력한 값에 따라서 동적으로 웹페이지가 생성되어야 한다면
Tomcat, Jetty 같은 Servlet Container를 사용함으로써 소켓 생성 및 API를 구성하지 않고도 사용자와 통신할 수 있게 해준다.

HTTP 요청이 들어오면 Servlet Container에서 요청을 분석하여 적절한 서블릿의 서비스를 실행시키고 응답을 보내는 과정을 실행한다.

Servlet Container는 서블릿을 초기화 후 담고 있으며
그때 그때 사용자의 요청을 처리할 수 있는 서블릿이 사용되는 환경을 제공하며
서블릿들의 생명주기 또한 관리한다.

1-1. Servlet이란?

Servlet Container에 의해 동작하는 Servlet은 javax.servlet 패키지안에 정의된 인터페이스이며 HTTP 요청, 응답을 처리하며 웹페이지를 동적으로 생성하는 역할을 한다.
기본적으로 init, service, destroy 메소드가 정의되어 있다.

대표적으로 DispatcherServlet, HttpServlet등의 구현체가 있으며 서블릿들은 일반적으로 HttpServlet을 상속받는다.

1-2. Servlet과 Servlet Container의 동작방식

1. 서블릿의 생성

  • 서블릿 컨테이너는 요청에 따라 필요한 서블릿들을 init() 메소드를 통해 초기화 한다.
  • init() 메소드는 생성되는 시점 딱 한번 실행되며, 자원들을 미리 불러오고 설정값을 초기화하는 등의 작업을 하며 Spring Container를 사용하여 빈객체를 생성/주입 받을 수도 있다.
  • 이후 초기화된 서블릿 인스턴스들은 메모리에 상주하게 되며 생명주기가 시작된다.

2. 웹 브라우저에서 요청이 들어왔을때

  • 해당 URL의 정보를 분석하여 요청을 처리할 web.xml에 등록된 서블릿 찾는다.
  • 서블릿에 대한 Thread를 생성하고 HttpServletRequestHttpServletResponse 객체를 생성한 뒤 서블릿을 service() 메소드로 호출한다.
    (서블릿이 생성되지 않았다면 위 서블릿의 생성과정을 실행)
  • 해당 서블릿 인스턴스는 생성된 Thread에서 동작한다.

스프링부트를 사용한다면 내장 서블릿 컨테이너를 포함하고있기때문에 web.xml 파일이 없어도 @EnableAutoConfiguration, @ComponentScan, @Configuration 등의 어노테이션으로 필요한 서블릿을 자동으로 설정된다.

3. 요청을 처리할 Servlet 인스턴스 생성과 처리

  • Servlet객체는 service() 메소드가 호출되고 요청에 따라 doGet(), doPost() 등의 메소드가 호출된다.
  • 이 때, Servlet Container에서 제공하는 HttpServletRequestHttpServletResponse 객체를 사용하여 HTTP요청 및 응답을 처리한다.
  • 이후 처리된 정보를 HttpServletResponse 객체에 설정해주고 HTTP 응답 정보를 반환한다.
  • Servlet Container는 HTTP Response 형태로 전환한 뒤 웹서버에 전달한다.
  • 서블릿이 동작했던 Thread를 종료하고 HttpServletRequestHttpServletResponse 객체를 소멸시킨다.

4. 서블릿의 소멸 시기
웹 어플리케이션이 종료되면 Servlet Container는 destroy() 메서드를 통해 Servlet 객체를 제거한다.
destroy() 메서드는 Servlet 객체가 사용한 자원을 해제하는 작업을 담당하며 이후, Servlet 객체는 Garbage Collector에 의해 메모리에서 제거된다.

그림으로 요약하면 아래와 같다.

Servlet과 Servlet Container의 동작방식_그림

2. Spring Container

위 Servlet Container는 서블릿들을 초기화할 때,
Spring Container는 각각의 서블릿이 제대로 동작하기 위해 필요한 의존성 주입(DI)을 제공한다.
두 컨테이너는 이렇게 밀접한 관계에 있기 때문에 Spring Container에 짚고 넘어갈 필요가 있다.

Spring Container의 역할은 빈(Bean) 객체들을 생성하고,
빈 객체들 간의 의존성을 관리하며 빈 객체들을 필요한 곳에서 주입(Injection)해주는 등의 역할을 맡는다.

빈 객체를 생성하고 생명주기를 관리할 때 스프링 컨테이너가 직접 컨트롤 하기 때문에
Inversion of Control (IoC) 방식이라고 불리며
객체 생성, 관리, 의존성 관련 코드들이 아닌, 서비스에 대한 코드에만 집중할 수 있는 장점이 있다.

때문에 Spring Container는 IoC Container라고도 불린다.

IoC와 DI란

IoC는 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라,
외부에서 결정되는 디자인패턴을 말하는데 이 객체에 대한 제어권을 스프링 컨테이너가 가지고 있기때문에 제어의 역전이라고 불린다.

대표적 예시 중 하나로 스프링부트에서 @SpringBootApplication 어노테이션이 보통 프로젝트 메인 클래스에 정의되어 있다.
이 어노테이션이 스프링 컨테이너를 생성하고 컴포넌트 스캔을 수행하여 빈 객체를 찾아 의존성을 자동으로 주입한다.

주로 @Component, @Service, @Repository, @Controller 등의 어노테이션이 붙은 클래스들이 빈으로 등록될 대상이 되며
@ComponentScan 어노테이션으로 basePackages, basePackageClasses, includeFilters, excludeFilters 등의 옵션으로 Spring Container의 빈 객체 관리에 대해 세밀하게 지정할 수 있다.

개발자가 직접 객체 간의 의존성을 생성하고 관리하면 추후 코드의 가독성과 유지보수성을 떨어뜨리는 문제 등이 발생할 수 있어 고안된 방법이며 코드의 가독성과 유지보수성을 높일 수 있다.

IoC의 구현 방식 중 하나인 DI(Dependency Injection)은 IoC를 구현하는 방법 중 하나이다.
IoC가 제어의 역전 개념을 통틀어 말한다면
DI는 이 개념을 구현하기 위해 의존관계를 외부에서 주입(Injection)하는 방식이다.

간단한 예시로는

// 예시1)

public class Aclass {
	private Bclass b = new Bclass();
    ...
}
// 예시2)

public class Aclass {
	private Bclass b;
    
    public Aclass(Bclass b) {
        this.b = b;
    }
}

예시1과 2모두 A클래스는 B클래스에 대해 의존성을 가지고 있어 B가 변경되면 A도 변경된다.
하지만 예시2는 외부에서 생성자를 통해 B를 주입받는다. 이런 생성자를 활용한 DI말고도
Setter주입, 인터페이스 주입 방식이 있다.

스프링에서는 이 방식을 사용하여 이 의존관계가 빈으로 등록되어 있다면 자동으로 @Autowired와 같은 어노테이션 하나로 의존성을 주입해주며 때문에 객체 간의 결합도도 낮추고 유연하고 확장성 높은 코드를 작성할 수 있다.

Spring Container의 종류

BeanFactory, ApplicationContext 크게 2가지로 나눌수 있다.

BeanFactory

BeanFactory는 빈을 등록하고 생성하고 조회하고 돌려주는 등 빈을 관리하는 역할을 한다.
getBean() 메소드를 통해 Bean을 인스턴스화 할 수 있으며 그 외에 부가적인 빈을 관리하는 기능을 담당한다.

즉 스프링컨테이너의 가장 기본적이며 핵심적인 기능을 담고있다.

getBean()이 호출되면 BeanFactory는 의존성 주입을 통해 빈을 인스턴스화하고, 빈의 특성을 설정하기 시작하며 여기서 빈의 라이프사이클이 시작된다.

ApplicationContext

스프링이 제공하는 각종 부가 서비스를 추가로 제공하며 BeanFactory를 상속한 인터페이스이다. 이 인터페이스를 구현하는 여러 컨테이너가 있으며

BeanFactory와 다른 점은 BeanFactory는 getBean() 메소드가 호출된 시점에서 지연로딩(LAZY)을 시작하여 생성해주지만 ApplicationContext 초기화 시점에 모든 빈을 미리 로드한 후(EAGER) 필요할때 빈을 지연 없이 얻을 수 있다.

ApplicationContext을 구현하는 컨테이너는 크게
FileSystemXmlApplicationContext, AnnotationConfigApplicationContext 또는
ClassPathXmlApplicationContext 등이 있다.

ClassPathXmlApplicationContext 와 FileSystemXmlApplicationContext
xml 파일로 빈객체들의 생성과 의존관계를 정의하면 이 xml 파일을 이용하여 빈 객체를 생성하고 관리한다. 스프링 프레임워크 초기부터 사용된 가장 간단한 형태의 스프링 컨테이너라고 할 수 있다.
두 컨테이너의 가장 큰 차이점은 xml 파일의 경로이여
ClassPathXmlApplicationContext는 리소스 폴더(src/main/resources/...)에서,
FileSystemXmlApplicationContext는 보통 프로젝트 외부 경로의 xml 파일을 사용할 때 사용된다.

xml 파일 기반으로 빈객체들을 관리하기 때문에 다음과 같은 단점이 있다.

  • xml 파일의 내용이 바뀌면 다시 빌드/배포해야하는 번거로움이 있다.
  • 규모가 커진다면 xml 파일 내용이 길어지고 가독성이 떨어진다.
  • 빈객체들을 런타임때 메모리에 로드되므로 (LAZY로딩) 자원소모가 큰 경우 적절하지 못하다.
  • 동적으로 빈객체들의 구성이 변경되어야한다면 사용할 수 없다.

이와 같은 단점을 가지고 있지만 비교적 쉽게 사용할 수 있기 때문에 간단한 작은 규모의 테스트 프로젝트 등에서 종종 사용된다.

AnnotationConfigApplicationContext
가장 자주 사용되는 스프링 컨테이너이며 xml 파일을 사용하는 대신 자바 어노테이션으로 구성된 설정 클래스를 사용한다.
이 설정 클래스를 생성하고, 이를 바탕으로 스프링 빈을 생성한다.

@Component, @Service, @Repository, @Controller 등으로 자동으로 빈객체를 구성할 수 있고
@Autowired, @Qualifier, @Value 등의 어노테이션을 사용하여 빈객체 간의 의존성을 관리할 수 있다.
자바 코드로 작성되기 때문에 컴파일 단계에서 에러를 검출할 수 있다.

0개의 댓글