[스프링] 애플리케이션 컨텍스트

애플리케이션 컨텍스트

  • 용어: 스프링 컨테이너 = IoC 컨테이너 = 애플리케이션 컨텍스트 = 빈 팩토리

스프링에서는 객체 생성 및 객체 간 의존 관계 설정을 코드로 수행하는 대신 스프링 컨테이너가 수행하며 이를 제어의 역전(Inversion of Control, IoC)라고 한다. IoC의 주 목적은 컴포넌트 간 의존성을 보다 편리하게 관리하도록 하는 것이다. 일반적으로 IoC는 의존성 주입(Dependency Injection, DI)과 의존성 룩업(Dependency Lookup, DL) 두 종류로 나뉜다. 의존성 주입은 의존성 룩업보다 보다 발전된 형태의 IoC 기법이며 더 유용하고 유연한 장점이 있다.

빈 팩토리(bean factory)는 객체 생성 및 객체 간 의존 관계 설정인 의존성 주입(dependency injection) 역할을 수행한다. 해당되는 스프링의 인터페이스는 BeanFactory이다. 애플리케이션 컨텍스트는 의존성 주입 뿐만 아니라 기타 스프링 컨테이너가 제공하는 기능을 수행한다. 해당되는 인터페이스는 ApplicationContext이다. ApplicationContext 인터페이스는 BeanFactory 인터페이스를 상속한 서브 인터페이스이며 BeanFactory 보다 훨씬 더 강력한 기능들을 제공한다. ApplicationContext 인터페이스는 ListableBeanFactory라는 인터페이스를 상속해 스프링이 관리하는 모든 빈 인스턴스에 대한 공급자 역할을 한다. 스프링 컨테이너란 ApplicationContext 인터페이스 구현 객체를 말한다.

스프링의 ApplicationContext가 초기화되고 나면 getBean() 메서드를 통해 특정 인스턴스를 가져올 수 있다. getBean() 메서드는 에러 위험이 없는 타입 안전한(type-safe) 메서드이며, 반환받고자 하는 빈의 ID와 타입을 인자로 받아 해당 빈을 반환한다. 스프링은 해당 인스턴스(인터페이스인 경우 구현체, 상속인 경우 서브클래스)를 생성해 의존성을 주입한다.

스프링 애플리케이션은 최소한 하나 이상의 스프링 컨테이너(애플리케이션 컨텍스트 구현 객체)를 갖고 있다. 구성에 따라 한 개 이상의 애플리케이션 컨텍스트 구현 객체를 가질 수도 있다.

스프링 컨테이너가 동작을 하기 위해서는 스프링 컨테이너가 관리할 객체(POJO 객체)를 정의하고 이를 스프링 컨테이너 관리 객체인 빈(bean)으로 만들어야 한다. 객체를 빈으로 만들고 이를 스프링 컨테이너가 관리하게 하기 위해서는 설정 메타정보도 필요하다. 설정 메타정보는 빈을 어떻게 만들고 어떻게 동작하게 할 것인지에 대한 정보이다. 빈 설정 메타정보에 해당되는 인터페이스는 BeanDefinition이다. 빈 설정 메타정보에는 빈으로 만들 클래스, 빈 식별자, 빈 생성 방식 및 존재 범위(스코프), 의존성 주입 정보 등이 있다.

스프링 컨테이너는 각 빈에 대한 정보를 담은 설정 메타정보를 읽어들인 뒤에, 이를 참고해서 빈 객체를 생성하고 프로퍼티나 생성자를 통해 해당 객체에 대한 의존 객체를 주입하는 의존성 주입 작업을 수행한다.

설정 메타정보를 토대로 POJO 클래스를 빈 객체로 생성하여 애플리케이션 컴포넌트로 등록하고, 의존성 주입을 통해 빈 객체와 해당 빈 객체가 의존하는 또다른 빈 객체 간 의존관계를 설정하면 관련된 객체들이 모여서 하나의 애플리케이션을 구성하고 동작하게 된다.


애플리케이션 컨텍스트 컴포넌트 스캔 대상 패키지 설정

애플리케이션의 컨텍스트의 컴포넌트 스캔 시 모든 서브 패키지의 클래스들이 탐색되도록 하기 위해 @SpringBootApplication이 지정된 클래스(보통 메인 클래스이다)가 존재하는 패키지는 루트 패키지에 위치시키는 것이 권장된다. 스프링의 컴포넌트 스캔 대상 패키지 기본 설정은 구성 클래스가 속한 패키지 및 모든 서브 패키지이기 때문에 @Configuration 어노테이션이 지정된 구성 클래스가 존재하는 패키지가 @SpringBootApplication 클래스가 위치한 패키지의 서브 패키지가 아니거나 동일한 수준의 서로 다른 패키지인 경우 애플리케이션 컨텍스트 구성이 올바로 수행되지 않는다. 이 경우 해당 구성 파일에 정의된 빈 객체가 애플리케이션 컨텍스트에 컴포넌트로 등록되지 않게 되고 스프링 컨테이너의 컴포넌트 스캔이 제대로 수행되지 않는다.

인터페이스와 인터페이스 구현체의 패키지도 @SpringBootApplication가 위치한 패키지와 서로 동일한 패키지이거나 서브 패키지에 위치하여야 컴포넌트 스캔이 올바르게 동작한다. 멀티 모듈(멀티 프로젝트) 구성의 경우 모듈 별로 패키지 경로가 다를 수 있으므로 기본적인 스프링 구성에 의해 설정되어 있는 패키지 경로에 신경 써야 한다.

애플리케이션 컨텍스트의 컴포넌트 스캔 대상 패키지를 직접 설정하는 방법도 있다. 구성 클래스에 @ComponentScan 어노테이션을 설정하고 basePackages 파라미터로 애플리케이션 컨텍스트에 등록할 스캔 대상 컴포넌트들이 위치한 베이스 패키지명을 지정하면 된다. @SpringBootApplication가 위치한 패키지와 동일한 패키지나 서브 패키지에 위치시키지 않은 컴포넌트를 스캔하려면 이 방법을 사용하면 된다. 예를 들어, 멀티 모듈 구성에서 한 서브 모듈의 테스트 소스셋의 패키지에서 다른 서브 모듈의 자바 소스셋의 패키지의 빈을 사용하려는 경우 해당 서브 모듈의 패키지를 컴포넌트 스캔 대상으로 등록하면 된다.

패키지에 속하지 않은 클래스는 기본(default) 패키지에 속하게 된다. 구성 클래스를 기본 패키지에 위치시키는 경우 모든 jar 파일의 모든 클래스를 스캔하기 때문에 @ComponentScan, @ConfigurationPropertiesScan, @EntityScan, @SpringBootApplication 어노테이션을 사용하는 경우 문제가 될 수 있다. 따라서 구성 클래스는 기본 패키지에 위치시키지 않는 것이 좋다.


참고

Comments