IoC 컨테이너와 DI
IoC/DI는 자바의 표준 프로그래밍 모델로 발전하였다.
DI 설정방식과 IoC 컨테이너 활용 방법은 매우 다양하다.
IoC 컨테이너: 빈 팩토리와 애플리케이션 컨텍스트
오브젝트의 생성과 관계설정, 사용, 제거 등의 작업을 담당하는 스프링 컨테이너(IoC 컨테이너)
스프링에서는 컨테이너를 빈 팩토리 또는 애플리케이션 컨텍스트라고 부른다. 오브젝트의 생성과 오브젝트 사이의 런타임 관계를 설정하는 DI 관점으로 볼 때는 컨테이너를 빈 팩토리라고 한다. DI를 위한 빈 팩토리에 엔터프라이즈 애플리케이션을 개발하는 데 필요한 여러가지 기능을 추가한 것을 애플리케이션 컨택스트라고 부른다. IoC/DI 이상의 기능을 가졌다고 볼 수 있다.
스프링의 IoC 컨테이너는 일반적으로 ApplicationContext를 말한다. ApplicationContext 인터페이스는 BeanFactory 인터페이스를 상속한 서브인터페이스다. 스프링은 최소한 한 개 이상의 IoC 컨테이너(애플리케이션 컨텍스트 오브젝트를 갖는다.
IoC 컨테이너를 이용해 애플리케이션 만들기
IoC 컨테이너 만드는 코드
StaticApplicationContext applicationContext = new StaticApplicationContext();
컨테이너가 IoC 컨테이너로서 동작하려면 두 가지가 필요하다. POJO 클래스와 설정 메타정보다.
POJO클래스
애플리케이션의 핵심 코드를 담는 POJO 클래스를 만든다. POJO 원칙에 따라 기술과 스펙에서 독립적이고 의존관계에 있는 다른 POJO와 느슨한 결합을 갖도록 만든다.
설정 메타정보
POJO 클래스들 중에 애플리케이션에서 사용할 것을 선정하고 이를 IoC 컨테이너가 제어할 수 있도록 적절한 메타정보를 만들어 제공해야 한다.
스프링의 설정 메타 정보는 BeanDefinition 인터페이스로 표현되는 순수한 추상 정보다. 애플리케이션 컨텍스트는 BeanDefinition으로 만들어진 메타정보를 담은 오브젝트를 사용해 IoC와 DI 작업을 수행한다. 따라서 스프링의 메타정보는 파일 포맷이나 형식에 제한되거나 종속되지 않는다.
원본의 포맷과 구조, 자료의 특성에 맞게 읽어와 BeanDefinition 오브젝트로 변환해주는 BeanDefinitionReader가 있으면 된다.
- 빈 아이디, 이름, 별칭 : 빈 오브젝트를 구분할 수 있는 식별자
- 클래스 또는 클래스 이름 : 빈으로 만들 POJO 클래스 또는 서비스 클래스 정보
- 스코프 : 싱글톤, 프로토타입과 같은 빈의 생성 방식과 존재 범위
- 프로퍼티 값 또는 참조 : DI에 사용할 프로퍼티 이름과 값 또는 참조하는 빈의 이름
- 생성자(Constructor) 파라미터 값 또는 참조 : DI에 사용할 생성자 파라미터 이름과 값 또는 참조할 빈의 이름
- 지연된 로딩 여부, 우선 빈 여부, 자동와이어링 여부, 부모 빈 정보, 빈팩토리 이름 등
스프링 IoC 컨테이너는 각 빈에 대한 정보를 담은 설정 메타정보를 읽어들인 뒤에, 이를 참고해서 빈 오브젝트를 생성하고 프로퍼티나 생성자를 통해 의존 오브젝트를 주입해주는 DI 작업을 수행한다.
결국 스프링 애플리케이션이란 POJO 클래스와 설정 메타정보를 이용해 IoC 컨테이너가 만들어주는 오브젝트의 조합이라고 할 수 있다.
IoC 컨테이너의 종류와 사용법
-
StaticApplicationContext
스프링이 내부적으로 활용하는 빈 설정 메타정보를 담은 BeanDefinition오브젝트를 만들고 코드를 통해 IoC컨테이너에 등록해줘야 한다. 이 컨테이너는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다. 스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않는다.
이를 통해 스프링 IoC 컨테이너는 파일 포맷이나 리소스의 종류에 독립적이며 오브젝트로 표현되는 순수한 메타정보를 사용한다는 것을 알 수 있다.
-
GenericApplicationContext
가장 일반적인 애플리케이션 컨텍스트의 구현 클래스다. 컨테이너의 주요 기능을 DI를 통해 확장할 수 있도록 설계되어 있다. XML 파일과 같은 외부의 리소스에 있는 빈 설정 메타정보를 통해 읽어들여서 메타정보로 전환해서 사용한다.
특정 포맷의 빈 설정 메타정보를 읽어서 이를 애플리케이션 컨텍스트가 사용할 수 있는 BeanDefinition 정보로 변환하는 기능을 가진 오브젝트는 BeanDefinitionReader 인터페이스를 구현해서 만들고, 빈 설정정보 리더라고 불린다. XML로 작성된 빈 설정정보를 읽어서 컨테이너에게 전달하는 대표적인 빈 설정정보 리더는 XmlBeanDefinitionReader다.
GenericApplicationContext는 빈 설정 리더를 여러 개 사용해서 여러 리소스로부터 설정 메타정보를 읽어들이게도 할 수 있다. 모든 설정 메타정보를 가져온 후에 refresh() 메소드를 한 번 호출해서 애플리케이션 컨텐스트가 필요한 초기화 작업을 수행하게 해주면 된다.
-
GenericXmlApplicationContext
번거롭게 XmlBeanDefinitionReader를 직접 만들지 않고 GenericApplicationContext와 결합된 클래스다. 이 클래스는 XmlBeanDefinitionReader를 내장하고 있기 때문에 XML 파일을읽고 refresh()를 통해 초기화하는 것까지 한 줄로 끝낼 수 있다.
-
WebApplicationContext
스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트다. ApplicationContext를 확장한 인터페이스로 웹 환경에서 사용할 때 필요한 기능이 추가된 애플리케이션 컨텍스트다. 스프링 애플리케이션은 대부분 서블릿 기반의 독립 웹 어플리케이션(WAR)으로 만들어지기 때문이다.
가장 많이 사용되는 건, XML 설정파일을 사용하도록 만들어진 XmlWebApplication이다. 애노테이션을 이용한 설정 리소스만 사용하면 AnnotationConfigWebApplicationContext를 쓰면된다.
스프링 IoC 컨테이너는 빈 설정 메타정보를 이용해 빈 오브젝트를 만들고 DI 작업을 수행한다. 애플리케이션을 동작하기 위해 적어도 한 번은 IoC 컨테이너에 요청해서 빈 오브젝트를 가져와야 한다. IoC 컨테이너의 역할은 초기에 빈 오브젝트를 생성하고 DI 한 후에 최초로 애플리케이션을 기동할 빈 하나를 제공해주는 것 까지다.
독립형 애플리케이션이라면은 위와 같이 동작하지만, 웹 애플리케이션은 동작하는 방식이 근본적으로 다르다. 독립형 자바 프로그램은 JavaVM에게 main() 메소드를 가진 클래스를 시작시켜 달라고 요청할 수 있다. 하지만 웹에서는 main() 메소드를 호출할 방법이 없다. 게다가 사용자도 다수이다. 그래서 웹 환경에서는 main() 메소드 대신 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되어 있는 서블릿을 실행해주는 방식으로 동작한다. 서블릿이 일종의 main() 메소드와 같은 역할을 하는 셈이다.
웹 애플리케이션에서 스프링 애플리케이션을 기동하기 위해 일단 main()메소드 역할을 하는 서블릿을 만들어두고, 애플리케이션 컨택스트를 생성해둔 다음, 요청이 서블릿으로 들어올 때마다 getBean()으로 필요한 빈을 가져와 정해진 매소드를 실행해주면 된다.
main() 메소드나 테스트 메소드에서 했던 작업을 웹 애플리케이션과 그에 소속된 서블릿이 대신해줄 뿐이다. 서블릿 컨테이너는 브라우저와 같이 클라이언트로부터 들어오는 요청을 받아서 서블릿을 동작시켜주는 일을 맡는다. 서블릿은 웹 애플리케이션이 시작될 때 미리 만들어둔 웹 애플리케이션 컨텍스트에게 빈 오브젝트로 구성된 애플리케이션의 기동 역할을 해줄 빈을 요청해서 미리 받아둔다. 그리고 지정된 메소드를 호출함으로써 DI 방식으로 구성해준 애플리케이션의 기능이 시작된다.
스프링은 웹 환경에서 애플리케이션 컨텍스트를 생성하고 설정 메타정보로 초기화해주고, 클라이언트로부터 들어오는 요청마다 적절한 빈을 찾아서 이를 실행해주는 DispatcherServlet이라는 서블릿을 제공한다. 이 서블릿을 web.xml에 등록하는 것만으로 웹 환경에서 스프링 컨테이너가 만들어지고 애플리케이션을 실행하는 데 필요한 대부분의 준비는 끝난다.
WebApplicationContext는 자신이 만들어지고 동작하는 환경인 웹 모듈에 대한 정보에 접근할 수 있다. 이를 이용해 웹 환경으로부터 필요한 정보를 가져오거나, 웹 환경에 스프링 컨테이너 자신을 노출할 수 있다. 컨테이너가 웹 환경에 노출되면 같은 웹 모듈에 있는 스프링 빈이 아닌 일반 오브젝트와 연동될 수 있다.
IoC 컨테이너 계층 구조
빈을 담은 IoC컨테이너는 애플리케이션마다 하나씩이면 충분하다. 빈의 개수가 많아져서 설정파일이 커지는 게 문제라면 파일을 여러개로 쪼개서 만들고 하나의 애플리케이션 컨텍스트가 여러 개의 설정파일을 사용하게 하면 된다. 한 개 이상의 IoC 컨테이너를 만들어야 할 경우가 있다. 트리 모양의 계층구조를 만들 때다.
부모 컨텍스트를 이용한 계층구조의 효과
모든 애플리케이션 컨텍스트는 부모 애플리케이션 컨텍스트를 가질 수 있다. 이를 이용하면 트리구조의 애플리케이션 컨텍스트 계층을 만들 수 있다. 계층구조 안의 모든 컨텍스트는 각자 독립적인 설정정보를 이용해 빈을 만들고 관리한다. 여기서 자식 애플리케이션 컨텍스트는 DI를 위해 빈을 검색할 때는 부모 애플리케이션 컨텍스트까지 검색한다. 부모 애플리케이션 컨텍스트에서도 찾을 수 없다면 가장 위 루트 컨텍스트까지 재귀적으로 검색한다. 단, 부모 애플리케이션 컨텍스트는 자식 애플리케이션 컨텍스트에게는 요청하지 않는다. 형제인 같은 레벨의 컨텍스트도 제외된다.
검색 순서는 항상 자신 먼저이고, 그런 다음 직계 부모의 순서다. 따라서 부모가 같은 이름의 빈을 갖더라도 자신이 정의한 빈이 우선된다. 미리 만들어진 애플리케이션 컨텍스트의 설정을 그대로 가져다가 사용하면서 그중 일부 빈만 설정을 바꾸고 싶으면 하위 컨텍스트를 만들고 바꿀 빈을 재설정하면 된다.
계층구조를 사용하는 이유는 여러 애플리케이션 컨텍스트가 공유하는 설정을 만들기 위해서다. 하나의 EJB 모듈을 여러 개의 웹 모듈이 공유해서 사용하는 경우가 있기 때문이다. 각자 용도와 성격이 달라 웹 모듈을 여러 개로 분리해였지만 핵심 로직을 담은 코드는 공유하고 싶을 때 이런 식으로 구성한다. 애플리케이션 안에 성격이 다른 설정을 분리해서 두 개 이상의 컨텍스트를 구성하면서 각 컨텍스트가 공유하고 싶은 게 있을 때 계층구조를 이용한다.
AOP처럼 컨텍스트 안의 많은 빈에 적용되는 기능은 대부분 해당 컨텍스트로 제한된다는 점에 주의한다.
기본적으로 스프링 웹 애플리케이션은 부모/자식 관계를 가진 두 개의 애플리케이션 컨텍스트로 구성된 계층구조로 만들어진다.
루트 컨텍스트는 반드시 스스로 완전한 빈 의존관계를 보장해야 한다. 왜냐하면 자산 외에는 다른 데서 필요한 빈을 찾을 수 없기 때문이다.
웹 애플리케이션의 IoC 컨테이너 구성
서버에서 동작하는 웹 애플리케이션에서 스프링 IoC 컨테이너를 사용하는 방법은 크게 세가지로 구분할 수 있다. 두 가지 방법은 웹 모듈안에 컨테이너를 두는 것이고, 나머지 하나는 엔터프라이즈 애플리케이션 레벨에 두는 방법이다.
웹 애플리케이션 안에 WebApplicationContext 타입의 IoC 컨테이너를 두는 방법을 알아보자. 자바 서버에는 하나 이상의 웹 모듈을 배치해서 사용할 수 있다. 스프링을 사용한다면 보통 독립적으로 배치 가능한 웹 모듈(WAR) 형태로 애플리케이션을 배포한다. 하나의 웹 애플리케이션은 여러 개의 서블릿을 가질 수 있다. 많은 웹 요청을 한 번에 받을 수 있는 대표 서블릿(디스패처 서블릿)을 등록해두고, 공통적인 선행 작을 수행하게 한 후에, 각 요청의 기능을 담당하는 핸들러라고 불리는 클래스를 호출하는 방식으로 개발하는 경우가 일반적이다. 몇 개의 서블릿이 중앙집중식으로 모든 요청을 받아서 처리하는 방식을 프론트 컨트롤러 패턴이라고 한다. 스프링은 이 패턴을 많이 사용하므로 스프링 애플리케이션에 사용되는 서블릿의 숫자는 많아야 두셋 정도다.
웹 애플리케이션 안에서 동작하는 IoC컨테이너는 두 가지 방법으로 만들어진다. 하나는 스프링 애플리케이션의 요청을 처리하는 서블릿 안에서 만들어지는 것이고, 다른 하나는 웹 애플리케이션 레벨에서 만들어지는 것이다. 일반적으로 이 두 가지 방식을 모두 사용해 컨테이너를 만든다. 그래서 스프링 웹 애플리케이션에는 두 개의 컨테이너, 즉 WebApplicationContext가 만들어진다. 스프링 애플리케이션의 진입창구 역할을 하는 프론트 컨트롤러 서블릿이 한 개 이상 등록된다면, 그만큼 컨테이너 개수는 늘어난다.
웹 애플리케이션의 컨텍스트 계층구조
웹 애플리케이션 레벨에 등록되는 컨테이너는 보통 루트 웹 애플리케이션 컨텍스트이라고 부른다. 이 컨텍스트는 서블릿 레벨에 등록되는 컨테이너들의 부모 컨테이너가 되고, 일반적으로 전체 계층구조 내에서 가낭 최상단에 위치한 루트 컨텍스트가 되기 때문이다.
웹 애플리케이션에는 하나 이상의 스프링 애플리케이션의 프론트 컨트롤러 역할을 하는 서블릿이 등록될 수 있다.
일반적으로는 스프링의 애플리케이션 컨텍스트를 가지면서 프론트 컨트롤러 역할을 하는 서블릿은 하나만 만들어 사용한다. 전체 애플리케이션에서 웹 기술에 의존적인 부분과 그렇지 않은 부분을 구분하기 위해 부모(루트) 애플리케이션 컨텍스트와 자식 애플리케이션 컨텍스트로 나누어 놓았다. 스프링을 이용하는 애플리케이션이라고 해서 반드시 스프링이 제공하는 웹 기술을 사용해야 하는 건 아니다. 데이터 액세스 계층과 서비스 계층은 스프링 기술을 사용하고 프레젠테이션 계층은 스프링 외의 기술을 사용하는 경우가 종종 있기 때문이다.
스프링은 웹 애플리케이션마다 하나씩 존재하는 서블릿 컨텍스트를 통해 루트 애플리케이션 컨텍스트에 접근할 수 있는 방법을 제공한다. ServletContext는 웹 애플리케이션마다 하나씩 만들어지는 것으로, 서블릿 런타임 환경정보를 가지고 있다.
애플리케이션 계층 - 루트 애플리케이션 컨택스트 (1개)
서블릿 안 - 서블릿 컨택스트 (웹 애플리케이션 당 1개)
웹 애플리케이션의 컨텍스트 구성 방법
-
서블릿 컨텍스트와 루트 애플리케이션 컨텍스트 계층 구조
가장 많이 사용되는 기본적인 구성 방법이다. 스프링 웹 기술을 사용하는 경우 웹 관련 빈들은 서블릿의 컨텍스트에 두고 나머지는 루트 애플리케이션 컨텍스트에 등록한다. 루트 애플리케이션 컨텍스트는 모든 서블릿 레벨 컨텍스트의 부모 컨텍스트가 된다. 스프링 웹 외에도 기타 웹 프레임워크나 HTTP 요청을 통해 작동하는 다양한 서비스와 함께 사용할 수도 있다.
-
루트 애플리케이션 컨텍스트 단일구조
스프링의 웹 기술을 사용하지 않고 서드파티 웹 프레임워크나 서비스 엔진만을 사용해 프레젠테이션 계층을 만든다면 스프링 서블릿을 둘 이유가 없다. 따라서 서블릿의 애플리케이션 컨텍스트도 사용하지 않게 된다. 루트 애플리케이션 컨텍스트만 등록하면 된다.
-
서블릿 컨텍스트 단일 구조
스프링 웹 기술을 사용하면서 스프링 외의 프레임워크나 서비스 엔진에서 스프링의 빈을 사용할 생각이 없다면 루트 애플리케이션을 생략할 수 있다. 대신 서블릿에서 만들어지는 컨텍스트에 모든 빈을 등록하면 된다.
출처
토비의 스프링(책)
댓글남기기