DI를 위한 빈 팩토리에 엔터프라이즈 애플리케이션을 개발하는 데 필요한 여러 가지 컨테이너 기능을 추가한 것을 애플리케이션 컨텍스트라고 부른다.
애플리케이션 컨텍스트는 그 자체로 IoC와 DI를 위한 빈 팩토리이면서 그 이상의 기능을 가졌다고 보면 된다.

1. WebApplicationContext란?

root-context와 servlet-context를 설명하기에 앞서 WebApplicationContext란 무엇인지 먼저 알고 있어야 한다.

WebApplicationContext는 스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트로, 이름 그대로 '웹 환경에서 사용할 때 필요한 기능이 추가된 컨텍스트'라고 볼 수 있다. 먼저 IoC 컨테이너를 적용했을 때 애플리케이션을 기동시키는 방법에 대해 살펴보자.

public static void main(String[] args) {
	ApplicationContext context = new AnnotationConfigApplicationContext(Hello.class);
	Hello hello = ac.getBean("hello", Hello.class);
}

이름이 hello로 등록 된 빈을 가져오려면 IoC 컨테이너(ApplicationContext)에게 요청해서 빈 오브젝트를 가져와야 한다. 그런데 이렇게 빈을 가져올 수 있도록 기동하는 방법은 테스트나 main() 메소드를 가진 클래스를 자바 VM에게 기동해달라고 요청하는 방법이 있다. 하지만 웹에서는 main() 메소드를 호출할 수 있는 방법이 없고, 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되어 있는 서블릿을 실행해주는 방식으로 동작한다.

웹 환경에서 동작하는 애플리케이션 구조

웹 환경에서 스프링 빈으로 이루어진 애플리케이션이 동작하는 구조다. main() 메소드나 테스트 메소드에서 했던 역할을 웹 애플리케이션과 소속된 서블릿이 대신 해준다고 보면 된다.

  1. 서블릿 컨테이너는 브라우저와 같은 클라이언트로부터 들어오는 요청을 받아서 서블릿을 동작시켜주는 일을 한다.

  2. 서블릿은 웹 애플리케이션이 시작될 때 미리 만들어둔 WebApplicationContext에게 빈 오브젝트로 구성된 애플리케이션의 기동 역할을 해줄 빈을 요청해서 받아둔다.

  3. 미리 지정된 메소드를 호출함으로서 스프링 컨테이너가 DI 방식으로 구성해둔 애플리케이션의 기능이 시작된다.

2. IoC 컨테이너(ApplicationContext) 계층구조

IoC 컨테이너는 애플리케이션마다 하나면 충분하다. 하지만 여러 개의 설정 파일을 사용 할 경우, 한 개 이상의 IoC 컨테이너를 필요로 할 때가 있다. 다음은 IoC 컨테이너 계층을 나타내는 구조다.

IoC 컨테이너 계층 구조

각 애플리케이션 컨텍스트는 자신이 관리하는 빈을 가지고 있다. IoC 컨테이너에게 요청해서 빈을 가져오는 경우 먼저 자신이 관리하는 빈 중에 검색하고 없으면 부모 컨텍스트에게 빈을 찾도록 요청을 한다. 중요한 점은 자식 컨텍스트에 요청을 할 수 없다.

예를 들어 애플리케이션 컨텍스트1에게 Bean1이라는 빈을 찾을 때, 먼저 자신이 Bean1 빈을 관리하고 있는지 검색하고 없으면 부모 컨텍스트에게 해당 빈을 찾도록 요청한다.
애플리케이션 컨텍스트1은 Bean1이라는 빈을 관리하고 있기 때문에 바로 찾아올 수 있고, Bean1을 관리하고 있지 않을 경우 부모 컨텍스트인 루트 애플리케이션 컨텍스트에게 요청하여 찾을 수 있다. 반면 애플리케이션 컨텍스트에서 Bean3이라는 빈을 요청할 경우 자식 컨텍스트로 요청을 할 수 없으므로 찾지 못하게 된다.

IoC 컨테이너 빈 요청 구조

2-1. 빈 요청 테스트

테스트는 계층구조대로 자식 컨텍스트에서 빈을 검색 했을 때, 빈이 존재 하지 않으면 부모 컨텍스트에 요청하도록 진행한다. 먼저 parentContext.xml, childContext.xml 두 설정 파일의 IoC 컨테이너 설정을 한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="printer" class="com.example.tobi.context.StringPrinter" />

    <bean id="hello" class="com.example.tobi.context.Hello">
        <property name="name" value="Parent" />
        <property name="printer" ref="printer" />
    </bean>
</beans>

parentContext.xml에서 관리하는 빈은 printer, hello 두 개의 빈을 관리한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="hello" class="com.example.tobi.context.Hello">
        <property name="name" value="Child" />
        <property name="printer" ref="printer" />
    </bean>
</beans>

childContext.xml에서는 hello라는 하나의 빈만 관리한다.

@Test
public void getPrinterBean() throws Exception {

    ApplicationContext parent = new GenericXmlApplicationContext("parentContext.xml");

    GenericApplicationContext child = new GenericApplicationContext(parent);

    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(child);
    reader.loadBeanDefinitions("childContext.xml");
    child.refresh();

    StringPrinter printer = child.getBean("printer", StringPrinter.class);
    Assertions.assertThat(printer).isNotNull();
}

먼저 printer 빈을 요청했을 때 결과다.

@Test
public void getPrinterBean() throws Exception {

    ApplicationContext parent = new GenericXmlApplicationContext("parentContext.xml");

    GenericApplicationContext child = new GenericApplicationContext(parent);

    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(child);
    reader.loadBeanDefinitions("childContext.xml");
    child.refresh();

    StringPrinter printer = child.getBean("hello", StringPrinter.class);
    Assertions.assertThat(printer).isNotNull();
}

다음은 hello를 찾았을 때 결과다. 당연한 결과로 실패했다. hello 빈은 childContext.xml 파일에서 관리하는데 StringPrinter 클래스 타입의 빈은 parentContext.xml 파일에서만 관리하고 있기 때문이다.

3. RootContext, ServletContext

드이어 이번 블로그의 주제인 RootContext와 ServletContext에 대해서 공부한 내용을 포스팅 하려고 한다. 먼저 둘의 차이점은 다음과 같다.

RootContext

  • 전체 컨텍스트 계층의 부모 컨텍스트다.
  • ServletContext에서 관리하는 빈을 사용할 수 없다.
  • 데이터 엑세스 계층(Repository)과 서비스 계층(Service) 관련 빈을 등록한다.

ServletContext

  • RooContext에서 관리하는 빈을 사용할 수 있다.
  • DispatcherServlet이 직접 사용하는 컨트롤러를 포함한 웹 관련 빈을 등록할 때 사용한다.
  • 서블릿의 런타임 환경정보를 가지고 있다.

웹 애플리케이션 안에서 동작하는 IoC 컨테이너는 두 가지 방법으로 만들어 진다.

  1. 스프링 애플리케이션의 요청을 처리하는 서블릿 레벨에서 만들어지는 서블릿 컨텍스트
  2. 웹 애플리케이션 레벨에서 만들어지는 루트 컨텍스트

일반적으로 전체 계층구조에서 최상단에 위치하는 웹 애플리케이션 레벨에서 등록되는 컨테이너를 루트 애플리케이션 컨텍스트라고 한다. 계층구조의 최상단이기 때문에 서블릿 레벨에서 등록되는 컨텍스트들의 부모가 된다.

이렇게 컨텍스트를 구분하는 이유는 무엇일까?

  • 전체 애플리케이션에서 웹 기술에 의존적인 부분과 그렇지 않은 부분을 구분하기 위해서다.
  • 웹을 담당하는 프레젠테이션 계층은 스프링 외의 기술을 사용하는 경우가 종종있는데, 요청을 서블릿 애플리케이션 컨텍스트 계층에서 받는다.
    • JSP
    • AJAX 프레임워크
    • 서블릿 기반의 애플리케이션

스프링은 웹 애플리케이션마다 하나씩 존재하는 서블릿 컨텍스트를 통해 루트 컨텍스트로 접근할 수 있는 방법을 제공하여 스프링 밖의 어디서라도 웹 애플리케이션의 루트 컨텍스트를 얻을 수 있다.

profile
백엔드 개발자

0개의 댓글

Powered by GraphCDN, the GraphQL CDN