Spring IoC Container와 DI
IoC Container
스프링 애플리케이션에서는 오브젝트의 생성과 관계설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 독립된 컨테이너가 담당한다. 이를 컨테이너가 대신 오브젝트에 대한 제어권을 갖고 있다 하여 IoC(Inversion of Control)라 부른다. 그래서 스프링 컨테이너를 IoC 컨테이너라고도 한다.
스프링 컨테이너는 단순한 DI 작업 보다 더 많은 일을 한다. DI를 위한 빈 팩토리에 엔터프라이즈 애플리케이션을 개발하는 데 필요한 여러 가지 컨테이너 기능을 추가한 것을 애플리케이션 컨텍스트라고 부른다.
스프링의 빈 팩토리와 애플리케이션 컨텍스트는 각각 기능을 대표하는 BeanFactory와 ApplicationContext라는 두 개의 인터페이스로 정의되어 있다. ApplicationContext 인터페이스는 BeanFactory 인터페이스를 상속한 서브인터페이스다.
실제로 스프링 컨테이너 또는 IoC 컨테이너라고 말하는 것은 ApplicationContext 인터페이스를 구현한 클래스다.
IoC(Inversion of Control)이란?
IoC는 어떤 모듈이 사용할 모듈을 스스로 결정하는 것이 아니라 다른 모듈에 제어권이 있는 것을 의미한다.
DI(Dependency Injection)이란?
스프링에서의 DI는 모듈간의 의존성을 모듈의 외부(컨테이너)에서 주입시켜주는 기능으로 IoC의 한 종류다. 런타임 시 사용하게 될 의존대상과의 관계를 스프링 애플리케이션이 총체적으로 결정하고 그 결정된 의존특징을 런타임 시 부여한다.
Non-IoC/DI vs IoC/DI
IoC 컨테이너를 이용한 애플리케이션
IoC 컨테이너를 동작시키기 위한 요소는 크게 POJO 클래스, 설정 메타 정보로 이뤄진다.
POJO 클래스
각자 기능에 독립적으로 설계된 POJO 클래스를 만들고, 결합도가 낮은 유연한 관계를 가질 수 있도록 인터페이스를 사용해 연결해준다.
설정 메타 정보
POJO 클래스들 중에 애플리케이션에서 사용할 것을 선정하고 이를 IoC 컨테이너가 제어할 수 있도록 적절한 메타정보를 만들어 제공하는 작업이다.
스프링의 설정 메타 정보는 BeanDefinition 인터페이스로 표현되는 순수한 추상 정보다.
애플리케이션을 구성하는 빈 오브젝트를 생성하는 것이 IoC 컨테이너의 핵심기능이다. IoC 컨테이너는 일단 빈 오브젝트가 생성되고 관계가 만들어지면 그 뒤로는 거의 관여하지 않는다. 기본적으로 싱글톤 빈은 애플리케이션 컨텍스트의 초기화 작업 중에 모두 만들어진다.
IoC 컨테이너의 종류
StaticApplicationContext
StaticApplicationContext는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다. 테스트 목적 이외에 실전에서는 사용하면 안 된다.
GenericApplicationContext
가장 일반적인 애플리케이션 컨텍스트의 구현 클래스다. 실전에서 사용될 수 있는 모든 기능을 갖추고 있는 애플리케이션 컨텍스트다. 컨테이너의 주요 기능을 DI를 통해 확장할 수 있도록 설계되어 있다.
WebApplicationContext
ApplicationContext를 확장한 인터페이스로 스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트다. 웹 환경에서 사용할 때 필요한 기능이 추가된 애플리케이션 컨텍스트다.
IoC 컨테이너의 역할은 초기에 빈 오브젝트를 생성하고 DI 한 후에 최초로 애플리케이션을 기동할 빈 하나를 제공해주는 것까지다. 실제로 자바 애플리케이션의 실행은 main()
처럼 어디에서 특정 빈 오브젝트를 호출해야 한다.
웹 환경에서는 main()
대신 서블릿 컨테이너가 브라우저로부터 오는 HTTP 요청을 받아서 해당 요청에 매핑되어 있는 서블릿을 실행해주는 방식으로 동작한다. 서블릿이 일종의 main()
과 같은 역할을 하는 셈이다. main()
역할을 하는 서블릿을 만들어두고, 미리 애플리케이션 컨텍스트를 생성해둔 다음, 요청이 서블릿으로 들어올 때마다 getBean()
으로 필요한 빈을 가져와 정해진 메서드를 실행해주면 된다.
스프링은 이런 웹 환경에서 애플리케이션 컨텍스트를 생성하고, 설정 메타 정보로 초기화해주고, 클라이언트로부터 들어오는 요청마다 적절한 빈을 찾아서 실행해주는 DispatcherServlet을 제공한다.
WebApplicationContext는 자신이 만들어지고 동작하는 환경인 웹 모듈에 대한 정보에 접근할 수 있다. 이를 이용해 웹 환경으로부터 필요한 정보를 가져오거나, 웹 환경에 스프링 컨테이너 자신을 노출할 수 있다. 컨테이너가 웹 환경에 노출되면 같은 웹 모듈에 들어 있는 스프링 빈이 아닌 일반 오브젝트와 연동될 수 있다.
웹 애플리케이션의 Context 계층구조
ApplicationContext
- ContextLoaderListener에 의해 생성.
- 모든 WebApplicationContext들이 참조할 수 있다.
- persistance, service layer 빈들이 여기 속한다.
WebApplicationContext
- DispatcherServlet이 ServletContext에 등록된 ApplicationContext를 상속받아 생성.
- DispatcherServlet 안에서만 사용가능하다.
- DispatcherServlet은 독자적인 WebApplicationContext를 가지고 동일한 ApplicationContext를 공유한다.
- persentation layer 빈들이 여기 속한다.
IoC/DI를 위한 빈 설정 메타 정보
컨테이너는 빈 설정 메타정보를 통해 빈의 클래스와 이름을 제공받는다. 빈을 만들기 위한 설정 메타정보는 파일이나 애노태이션 같은 리소스로부터 전용 리더를 통해 읽혀서 BeanDefinition 타입의 오브젝트로 변환된다. 이 BeanDefinition 정보를 IoC 컨테이너가 활용한다.
빈 설정 메타정보 항목
종류 | 설명 |
---|---|
beanClassName | 빈 오브젝트의 클래스 이름 |
parentName | 빈 메타정보를 상속받을 부모 BeanDefinition의 이름. 빈의 메타정보는 계층구조로 상속할 수 있다. |
factoryBeanName | 팩토리 역할을 하는 빈을 이용해 빈오브젝트를 생성하는 경우에 팩토리 빈의 이름을 지정한다. |
factoryMethodName | 다른 빈 또는 클래스의 메서드를 통해 빈오브젝트를 생성하는 경우 그 메서드 이름을 지정한다. |
scope | 빈 오브젝트의 생명주기를 결정하는 스코프를 지정한다. 크게 싱글톤 여부로 구분할 수 있다. |
lazylnit | 빈 오브젝트의 생성을 최대한 지연할 것인지를 지정한다 이 값이 true이면 컨테이너는 빈 오브젝트의 생성을 꼭 필요한 시점까지 미룬다. |
dependsOn | 먼저 만들어져야 하는 빈을 지정 할 수있다. 빈 오브젝트의 생성 순서가 보장돼야 하는 경우 이용한다. |
autowireCandidate | 명시적인 설정 없이 미리 정해진 규칙을 가지고 자동으로 DI 후보를 결정하는 자동와이어링의 대상으로 포함시킬지의 여부 |
primary | 자동와이어링 작업 중에 DI 대상 후보가 여러 개가 발생하는 경우가 있다. 이때 최종 선택의 우선권을 부여할지 여부. primary가 지정된 빈이 없이 여러 개의 후보가 존재하면 자동와이어링 예외가 발생한다. |
abstract | 메타정보 상속에만 사용할 추상 빈으로 만들지의 여부. 추상 빈이 되면 그 자체는 오브젝트가 생성되지 않고 다른 빈의 부모 빈으로만 사용된다. |
autowireMode | 오토와이어링 전략. 이름, 타입, 생성자, 자동인식 등의 방법이 있다. |
dependencyCheck | 프로퍼티 값 또는 레퍼런스가 모두 설정되어 있는지를 검증하는 작업의 종류 |
initMethod | 빈이 생성되고 DI를 마친 뒤에 실행할 초기화 메서드의 이름 |
destoryMethod | 빈이 제거되기 전에 호출할 메서드의 이름 |
propertyValues | 프로퍼티의 이름과 설정 값 또는 레퍼런스. 수정자 메서드를 통한 DI 작업에서 사용한다. |
constructorArgumentValues | 생성자의 이름과 설정 값 또는 레퍼런스 생성자를 통한 DI 작업에서 사용한다. |
annotationMetadata | 빈 클래스에 담긴 애노테이션과 그 애트리뷰트 값. 애너테이션을 이용하는 설정에서 활용한다. |
빈 등록 방법
보통 XML 문서, 프로퍼티 파일, 소스코드 애너태이션과 같은 외부 리소스로 빈 메타정보를 작성하고 이를 적절한 리더나 변환기를 통해 애플리케이션 컨텍스트가 사용할 수 있는 정보로 변환해주는 방법을 사용한다.
XML
<bean>
태그 활용. 최근에는 지양하는 방식이다.
애너테이션
빈으로 사용될 클래스에 애너테이션을 부여해주면 이런 클래스를 자동으로 찾아서 빈으로 등록해준다.
종류에는 @Component, @Repository, @Service, @Controller, ...
가 있다. 이처럼 디폴트 필터에 적용되는 애너테이션을 스프링에서는 스테레오타입 애너테이션이라 부른다.
자동인식을 위한 애너테이션을 여러 가지 사용하는 이유는 계층별로 빈의 특성이나 종류를 나타내려는 목적도 있고 AOP의 적용 대상 그룹을 만들기 위해서이기도 하다.
자바 코드
자바 코드에 의한 빈 등록 기능은 하나의 클래스 안에 여러 개의 빈을 정의할 수 있다. 애너테이션을 이용해 빈 오브젝트의 메타정보를 추가하는 일도 가능하다.
@Configuration
이 달린 클래스를 작성한 후, @Bean
이 붙은 메서드를 이용해서 빈을 등록할 수 있다.
@Configuration
이 붙은 클래스가 아닌 일반 POJO 클래스에도 @Bean
을 사용할 수 있다. 하지만 @Bean
을 사용했을 때는 다른 @Bean
메서드를 호출해서 DI하는 코드에 문제가 발생한다. @Configuration
이 붙은 클래스에서는 @Bean
이 붙은 메서드가 여러 번 호출돼도 하나의 오브젝트만 리턴되는 것이 보장된다. 하지만 POJO 클래스에서 @Bean
을 사용한 경우, DI 설정을 위해 다른 @Bean
메서드를 직접 호출하면 매번 다른 오브젝트를 받게 된다. POJO 클래스에서 @Bean
을 사용할 경우 이런 위험성이 있기 때문에 함부로 남용해서는 안된다.
빈 의존관계 설정
명시적으로 구체적인 빈을 지정하는 방법과 일정한 규칙에 따라 자동으로 선정하는 방법으로 나뉜다. 보통 전자는 DI할 빈의 아이디를 직접 지정 하는 것이고 후자는 주로 타입 비교를 통해서 호환되는 타입의 빈을 DI 후보로 삼는 방법이다. 후자의 방법은 보통 자동와이어링이라고 불린다.
@Resource
주입할 빈을 아이디로 지정하는 방법이다. 만약에 빈 이름으로 참조할 빈을 찾을 수 없는 경우에는 타입을 이용해 다시 한번 빈을 찾기도 한다.
@Autowired
@Autowired
는 XML의 타입에 의한 자동와이어링 방식을 확장했다. 생성자, 필드, 수정자 메서드, 일반 메서드 등의 의존성 주입 방식이 있다.
@Qualifier
Qualifier는 타입 외의 정보를 추가해서 자동와이어링을 세밀하게 제어할 수 있는 보조적인 방법이다. @Qualifier
는 빈에 부가적인 속성을 지정해주는 효과가 있다. 그래서 단순하게 @Qualifier
에 값을 넣는 수준이 아니라, 좀 더 의미 있는 애너테이션을 만들 수도 있다.
@Target({ElementType.Field, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Database {
String value();
}
생성자와 일반 메서드의 경우에는 @Qualifier
를 부여하는 것이 의미가 없다. 각 파라미터마다 하나의 빈이 매핑되기 때문에 이때는 생성자나 메서드가 아니라 파라미터에 직접 @Qualifier
를 붙여야 한다.
자바 코드에 의한 의존관계 설정
@Configuration
과 @Bean
을 사용하는 자바 코드 설정 방식의 기본은 메서드로 정의된 다른 빈을 메서드 호출을 통해 참조하는 것이다. @Bean
이 붙은 메서드는 기본적으로 스프링의 싱글톤 방식을 따라야 하기 때문에 여러 번 호출해도 매번 인스턴스가 만들어지지 않고, 한 개의 인스턴스가 반복적으로 돌아온다. 스프링이 @Configuration
이 붙은 클래스를 조작해서 특별한 방식으로 동작하도록 조작해뒀기 때문이다.
@Configuration
public class DemoConfiguration {
@Bean
public DemoController demoController() {
return new SampleController;
}
}
Comments