본문 바로가기

JAVA/Spring

IoC 컨테이너 계층 구조 - 토비의 스프링

반응형

IoC 컨테이너 계층 구조

빈을 담아둘 IoC 컨테이너는 애플리케이션마다 하나씩이면 충분하다.
그러나 한 개 이상의 IoC 컨테이너를 만들어두고 사용해야 할 경우가 있다.

부모 컨테이너를 이용한 계층구조 효과

                    루트 애플리케이션 컨텍스트
                     ^                      ^
                     |                      |
애플리케이션 컨텍스트 A1              애플리케이션 컨텍스트 A2
             ^
             |
애플리케이션 컨텍스트 B1

계층 구조 안의 모든 컨텍스트는 각자 독립적인 설정정보를 이용해 빈 오브젝트를 만들고 관리한다.
각자 독립적으로 자신이 관리하는 빈을 갖고 있긴 하지만 DI를 위해 빈을 찾을때 부모 애플리케이션 컨텍스트의 빈까지 모두 검색한다.
자신이 관리하는 빈 중에서 찾고 없으면 부모 컨텍스트(A1)에 빈 찾기 요청을 한다.
없으면 그 위의 부모 컨텍스트(루트)에게 다시 요청한다.

그래서 미리 만들어진 애플리케이션 컨텍스트의 설정을 그대로 가져다가 사용하면 그중 일부 빈만 설정 변경하고 싶다면?
애플리케이션 컨텍스트를 두개 만들어서 하위 컨텍스트에서 바꾸고 싶은 빈을 다시 설정해줘도 된다.

성격이 다른 설정을 분리해서 두 개 이상의 컨텍스트를 구성하면 각 컨텍스트가 공유하고 싶은게 있을때 계층 구조를 이용한다.

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

GenericApplicationContext child = new GenericApplicationContext(parent);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(child);
reader.loadBeanDifinitions(basePath + "childContext.xml");
child.refresh();

Printer printer = child.getBean("printer", Printer.class);
assertThat(printer, is(notNullValue()));


Hello hello = child.getBean("hello", Hello.class);

hello.print();
assertThat(printer.toString(), is("Hello Child"));

웹 애플리케이션 IoC 컨테이너 구성

서버에서 동작하는 애플리케이션에서 스프링 IoC 컨테이너를 사용하는 방법

  • 웹 모듈안에 컨테이너를 두는 방법.
  • 엔터프라이즈 애플리케이션 레벨에 두는 방법

자바 서버에는 하나 이상의 웹 모듈을 배치해서 사용할수 있다.
스프링을 사용하면 보통 독립적으로 배치 가능한 웹 모듈(WAR) 형태로 애플리케이션을 배포한다.

하나의 웹 애플리케이션은 여러 개의 서블릿을 가질수 있다.

웹 요청을 한번에 받을수 있는 대표 서블릿을 등록해두고, 공통적인 선행 작업을 수행하게 한 후에, 각 요청의 기능을 담당하는 핸들러라고 불리는 클래스를 호출하는 방식으로 개발하는 경우가 일반적이다.
몇개의 서블릿이 중앙집중식으로 모든 요청을 받아서 처리하는 이런 방식을 프론트 컨트롤러 패턴이라고 한다.
스프링도 프론트 컨트롤러 패턴을 사용한다.
따라서 스프링 웹 애플리케이션에서 사용되는 서블릿의 숫자는 하나이거나 많아야 두 세개 정도다.

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

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

이 2가지 방식을 모두 사용해 컨테이너를 만든다.
그래서 스프링 웹 애플리케이션에는 두개의 컨테이너, 즉 WebApplicationContext 오브젝트가 만들어진다.

웹 애플리케이션 컨텍스트 계층 구조

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

웹 애플리케이션에는 하나 이상의 스프링 애플리케이션의 프론트 컨트롤러 역할을 하는 서블릿이 등록될수 있다.
이 서블릿에는 각각 독립적으로 애플리케이션 컨텍스트가 만들어진다.
이런 경우 각 서블릿이 공유하게 되는 공통적인 빈들이 있을것이고, 이런 빈들을 웹 애플리케이션 레벨의 컨텍스트에 등록하면된다.
이런 경우 공통되는 빈들이 서블릿별로 중복돼서 생성되는 것을 방지할수 있다.

서블릿 A
애플리케이션 컨텍스트  --------------
                                     | 
                                     | -----------> 웹 애플리케이션(루트 애플케이션 컨텍스트)
서블릿 B                             |
애플리케이션 컨텍스트 ---------------

하나의 웹 애플리케이션 내에 두개의 스프링 서블릿이 존재하는 경우에 만들어지는 애플리케이션 컨텍스트와 그 관계들의 모습이다.
서블릿A와 서블릿B는 각각 자신의 전용 애플리케이션 컨텍스트를 갖고 있다.
동시에 두 컨텍스트가 공유해서 사용하는 빈을 담아 놓을수 있는 별도의 컨텍스트가 존재한다.(부모 컨텍스트)

일반적으로는 스프링의 애플리케이션 컨텍스트를 가지면서 프론트 컨트롤러 역할을 하는 서블릿은 하나만 만들어서 사용한다.

서블릿
애플리케이션 컨텍스트  --------------> 웹 애플리케이션(루트 애플리케이션 컨텍스트)

중요!
왜 이렇게 계층 구조를 만들까?
전체 애플리케이션에 웹 기술에 의존적인 부분과 그렇지 않는 부분을 구분하기 위해서이다.

스프링을 이용하는 웹 애플리케이션이라고 반드시 스프링이 제공하는 웹 기술을 사용해야 하는건 아니다.
데이터 액세스 계층이나 서비스 계층은 스프링 기술을 사용하고 스프링 빈으로 만들지만 웹을 담당하는 프레젠테이션 계층은 스프링 외의 기술을 사용하는 경우도 있다.
(모델1 JSP, 서블릿 기반 애플리케이션에서 스프링으로 만든 서비스 계층 빈을 사용해야 할 경우, 스트럿츠나 JSF같은 웹 프레임워크에서 스프링의 빈을 사용하는 경우)
이런 경우에는 사용할 스프링 빈은 대부분 서비스 계층이나 데이터 액세스 계층에 속해 있따.
따라서 스프링 서블릿을 사용하는 스프링의 웹 기술 외의 웹 기술을 고려 중이라면 계층 형태로 컨텍스트로 구분해두는것이 바람직하다.

JSP나 스트럿츠에서 루트 애플리케이션 컨텍스트로 접근할수 있다.
스프링은 웹 애플리케이션마다 하나씩 존재하는 서블릿 컨텍스트를 통해 루트 애플리케이션 컨텍스트에 접근할수 있는 방법을 제공한다.

스프링의 간단한 유틸리티 메서드를 이용하면 스프링 밖의 어디서라도 웹 애플리케이션의 루트 애플리케이션 컨텍스트를 얻을수 있다.
getBean() 메서드를 사용하면 루트 컨텍스트의 어떤 빈이든 가져와 쓸수 있다.

WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)

ServletContext는 웹 애플리케이션마다 하나씩 만들어지는것으로 서블릿 런타임 환경정보를 담고 있다.
HttpServletReqeust, HttpSession 오브젝트를 갖고 있다면 간단히 ServletContext를 가져올수 있다.
스프링과 연동돼서 사용할수 있는 서드파티 웹 프레임워크는 바로 이 방법을 이용해서 스프링 빈을 가져와 사용한다.

스프링 외의 웹 프레임워크 -------------->
WebService/AJAX Engine     -------------->  웹 애플리케이션(루트 애플리케이션 컨텍스트)
Model 1 JSP/Servlet        ------------->
스프링 서블릿             -------------->

이렇게 프레젠테이션 계층을 분리해서 계층 구조로 애플리케이션 컨텍스트를 구성해두면 언제든 간단히 웹 기술을 확장하거나 변경, 조합해서 사용할수 있다.
스프링 서블릿 한가지만 존재한다고 해도 이런 계층 구조로 만들어 두는 것이 권장된다.
단, 애플리케이션 컨텍스트의 계층구조가 만들어지기 때문에 계층 구조를 사용할대의 주의 사항을 염두에 둬야 한다.
서블릿 컨텍스트 빈은 루트 애플리케이션 컨텍스트의 빈을 참조 가능하지만, 반대는 안된다.
또 하나의 컨텍스트에 정의된 AOP 설정은 다른 컨텍스트의 빈에 영향을 미치지 않는다.

스프링 애플리케이션의 XML 설정파일을 보통 계층이나 성격에 따라 여러 개의 파일을 분리해서 작성하면 편리하다.
그리고 하나의 컨텍스트가 여러 개의 설정파일을 사용하게 만들수 있다.

웹 애플리케이션의 컨텍스트 구성방법

  • 서블릿 컨텍스트와 루트 애플리케이션 컨텍스트 계층구조
    가장 많이 사용하는 기본적인 구성방법
    스프링 웹 기술을 사용하는 경우 - 웹 관련 빈들은 서블릿 컨텍스트에 두고 나머지는 루트 애플리케이션 컨텍스트에 등록

  • 루트 애플리케이션 컨텍스트 단일구조
    스프링 웹 기술을 사용하지 않고 서드파티 웹 프레임워크나 서비스 엔진만 사용하는경우 - 서블릿의 애플리케이션 컨텍스트도 사용하지 않게 된다. 그래서 루트 애플리케이션 컨텍스트만 등록

  • 서블릿 컨텍스트 단일구조
    스프링 웹 기술만 사용하는 경우 - 루트 애플리케이션 컨텍스트를 생략하고, 서블릿에서 만들어지는 컨텍스트에 모든 빈을 다 등록.

루트 애플리케이션 컨텍스트 등록

웹 애플리케이션 레벨에 만들어지는 루트 애플리케이션 컨텍스트 등록하는 방법

  • 서블릿의 이벤트 리스너를 이용
    스프링은 웹 애플리케이션의 시작과 종료시 발생하는 이벤트를 처리하는 리스너인 SevletContextListener를 이용한다.
    SevletContextListener 인터페이스를 구현한 리스너는 웹 애플리케이션 전체에 적용 가능한 DB 연결 기능이나 로깅 같은 서비스를 만드는데 유용하게 쓰인다.
    이를 이용해서 웹 애플리케이션이 시작될때 루트 애플리케이션 컨텍스트를 만들어 초기화하고 ,웹 애플리케이션이 종료될때 컨텍스트를 함게 종료하는 기능을 가진 리스너를 만들수 있다.
    스프링은 이러한 기능을 가진 리스너인 ContextLoaderListener를 제공한다.

web.xml

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ContextLoaderListener는 웹 애플리케이션이 시작할때 자동으로 루트 애플리케이션 컨텍스트를 만들고 초기화해준다.

  • 애플리케이션 컨텍스트 클래스: XmlWebApplicationContext
  • XML 설정파일 위치: /WEB-INF/applicationContext.xml

루트 애플리케이션 컨텍스트는 웹 애플리케이션의 WEB-INF/applicationContext 파일을 디폴트 설정 파일로 사용한다.

  • contextConfiguration

설정파일 위치 지정

<context-param>
    <param-name>contextConfiguration</param-name>
    <param-value>
        /WEB-INF/daoContext.xml
        /WEB-INF/applicationContext.xml
    </param-value>
</context-param>
<context-param>
    <param-name>contextConfiguration</param-name>
    <param-value>
        classpath:applicationContext.xml    
    </param-value>
</context-param>
<context-param>
    <param-name>contextConfiguration</param-name>
    <param-value>
        /WEB-INF/**/*Context.xml
    </param-value>
</context-param>
  • contextClass
    ContextLoaderListener가 자동으로 생성하는 컨텍스트의 클래스는 기본적으로 XmlWebApplicationContext이다.
    이를 다른 애플리케이션 컨텍스트 구현 클래스로 변경하고 싶으면 contextClass 파라미터를 이용해 지정해준다.
    사용될 컨텍스트는 WebApplicationContext 인터페이스를 구현해야 된다.
<context-param>
    <param-name>contextClass</param-name>
    <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value>
</context-param>

AnnotationConfigWebApplicationContext를 컨텍스트 클래스로 사용할대는 contextConfiguration 파라미터를 반드시 선언해줘야 한다.
XML 파일의 위치가 아니라 설정 메타정보를 담고 있는 클래스 또는 빈 스캐닝 패키지를 지정할수 있다.

서블릿 애플리케이션 컨텍스트 등록

스프링의 웹 기능을 지원하는 프론트 컨트롤러 서블릿은 DispatcherServlet이다.
서블릿 이름을 다르게 지정해주면 하나의 웹 애플리케이션에 여러개의 DispatcherServlet을 등록할수도 있다.

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
DispatcherServlet에 의해 만들어진 애플리케이션 컨텍스트는 모두 독립적인 네임스페이스를 갖게 된다. 이 네임스페이스는 서블릿 단위로 만들어지는 컨텍스트를 구분하는 키가 된다. 서블릿 이름에 -servlet을 붙여서 만든다. 즉, spring-servlet이 된다. DispatcherServlet 이 사용할 디폴트 XML설정 파일 위치를 네임스페이스를 이용해서 만들기 때문이다.

디폴트 설정 파일의 위치는 /WEB-INF/spring-servlet.xml이 된다.
여러 개의 DispatcherServlet이 등록되더라도 각각 구분할수 있고 자신만의 디폴트 설정파일을 가질수 있도록 하기 위해서다.

서블릿 컨테이너가 등록된 서블릿을 언제 만들고 초기화할지 그 순서를 지정하는것이다. DispatcherServlet은 서블릿의 초기화 작업중에 스프링 컨텍스트를 생성한다. 웹 애플리케이션이 시작되고 가능한 한 빨리 서블릿 컨텍스트의 최기화가 진행되는것이 바람직하다. 컨텍스트와 빈 초기화작업을 통해 문제를 빨리 파악할수 있다.

DispatcherServlet의 컨텍스트에 대한 디폴트 설정을 변경시 contextConfiguration, contextClass를 지정해준다.

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfiguration</param-name>
        <param-value>
            /WEB-INF/spring-servlet.xml
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
반응형

'JAVA > Spring' 카테고리의 다른 글

의존관계주입  (0) 2021.12.26
빈 설정  (0) 2021.12.22
IoC 컨테이너와 DI - 토비의 스프링  (0) 2020.09.24
스프링3 XML를 이용한 설정  (0) 2018.03.19
스프링 3 의존관계 주입(DI)의 응용  (0) 2018.03.15