특정 객체가 속성에 다른 객체를 필요로 하여 생성자나 setter 방식으로 주입시켜 함께 작동할 때, 다른 객체를 의존성(dependency) 이라 하고 이를 주입시키는 행위를 의존성 주입(DI: Dependency Injection) 이라고 한다. Spring 에서는 컨테이너가 bean 을 생성할 때 의존성을 주입하게 된다. 의존성 주입의 원리를 사용하면 객체는 의존성의 위치나 클래스와 완벽하게 분리될 수 있다. 인터페이스 또는 추상 클래스에 의존성이 있으면, 단위 테스트에서 스텁(stub) 이나 모의(mock) 구현을 쉽게 사용할 수도 있다.
ApplicationContext 는 자신이 관리하는 bean 에 대해 생성자(Constructor) 및 세터(Setter) 기반의 의존성 주입을 지원한다. 필수 의존성에 대해서는 생성자 기반의 의존성 주입을 사용하고, 선택적 의존성에 대해서는 세터 기반의 의존성 주입을 사용하는 것이 좋다. 생성자 방식은 별도로 세터 메소드 같은 코드가 필요하지 않지만, 너무 많은 생성자 인수는 클래스가 너무 많은 책임을 가지게 되므로 좋은 코드로 볼 수 없다. 세터 기반의 의존성 주입은 해당 객체를 재구성 및 재주입 할 수 있으며, @Required 어노테이션을 사용하면 속성을 필수 의존성으로 만들 수도 있다. 이러한 점들을 감안하여 특정 클래스에 대해 가장 적합한 의존성 주입을 사용해야 한다.
생성자(Constructor) 기반 의존성 주입
생성자 기반의 의존성 주입은 컨테이너가 의존성을 나타내는 하나 이상의 객체를 인수로 하여 생성자를 호출함으로써 수행된다.
아래는 생성자 인수로 의존성이 주입되는 예제이다.
public class SimpleMovieLister { // SimpleMovieLister 는 MovieFinder 에 의존성이 있다. private MovieFinder movieFinder; // Spring 컨테이너가 MovieFinder 의존성을 주입할 수 있도록 하는 생성자 public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } ... } | cs |
<beans> <bean id="SimpleMovieLister" class="x.y.SimpleMovieLister"> <constructor-arg ref="movieFinder"/> </bean> <bean id="movieFinder" class="x.y.MovieFinder"/> </beans> | cs |
위 예제와 달리 여러 생성자 인수를 Java 내장형 타입으로 정의할 경우, <constructor-arg/> 태그의 속성으로 인수와 매칭해 줄 index, name, type 중 하나를 함께 정의해야 한다. 이 때 설정하는 value 값은 기본적으로 String 형태로 전달하지만, 실제 소스에 정의된 int, long, String, boolean 등과 같은 모든 내장 type 으로 자동 변환된다.
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean> | cs |
세터(Setter) 기반 의존성 주입
세터 기반의 의존성 주입은 인수가 없는 생성자나 인수가 없는 static factory 메소드를 호출한 후, bean 에서 setter 메소드를 호출함으로써 수행된다.
아래는 세터 방식으로 의존성이 주입되는 예제이다.
public class SimpleMovieLister { // SimpleMovieLister 는 MovieFinder 에 의존성이 있다. private MovieFinder movieFinder; // Spring 컨테이너가 MovieFinder 의존성을 주입할 수 있도록 하는 세터 메소드 public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } ... } | cs |
<beans> <bean id="SimpleMovieLister" class="x.y.SimpleMovieLister"> <property name="movieFinder" ref="movieFinder"/> </bean> <bean id="movieFinder" class="x.y.MovieFinder"/> </beans> | cs |
위 예제처럼, Spring 의 XML 기반 설정 메타데이터는 bean 의 속성 및 생성자 인수로서, <property /> 와 <constructor-arg /> 요소 등을 내부에 가질 수 있다. 이 하부 요소에 value 속성을 가질 경우 문자열 표현("")으로 정의하지만, ConversionService API 에 의해 실제 소스에 정의된 type 으로 자동 변환된다. 여러 <property /> 요소 대신 p-namespace 를 사용하여 간략하게 설정할 수 있으며, 마찬가지로 <constructor-arg /> 요소 대신 c-namespace 를 사용할 수 있다.
아래와 같이 <property /> 와 <constructor-arg /> 요소 안에 또 다른 하위 요소를 지정할 수 있다.
- <ref /> : bean 속성에 참조할 bean 의 id 나 name 을 지정하는 방식으로 가장 일반적인 방식이다.
- <idref /> : 다른 bean 을 참조할 때 오타 등으로 인한 런타임 에러를 방지할 수 있다.
- <bean /> : 내부 bean 이라 부르며, 이는 항상 익명 bean 이 된다.
- List, Set, Map, Properties 타입의 속성과 인수를 각각 설정하기 위해, <list/>, <set/>, <map/>, <props/> 요소를 사용할 수 있다.
- value 값이 "" 로 설정되었다면 빈 문자열로 취급되며, null 로 설정해야 할 경우 <null/> 요소를 사용한다.
<bean /> 요소에 depends-on 속성으로 의존성을 설정하면 <ref /> 요소로 설정한 의존성 보다 먼저 해당 bean 을 초기화 한다. 예를 들면 데이터베이스 드라이버 등록과 같은 것이 이에 해당한다. ApplicationContext 구현은 singleton bean 이 사전 인스턴스화 하는데, 사전 인스턴스화를 원하지 않는 bean 이 있다면 <bean /> 요소에 lazy-init 속성을 true 로 설정하여 해당 bean 의 요청이 있을 때 bean 인스턴스를 생성하도록 할 수 있다. <beans /> 요소의 default-lazy-init 속성을 사용하여 해당 컨테이너의 모든 bean 의 사전 인스턴스화를 제어할 수 있다.
Properties
다음과 같이 java.util.Properties 인스턴스를 구성할 수도 있다.
<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property> </bean> | cs |
Spring 컨테이너는 JavaBeans 의 PropertyEditor 메커니즘을 사용하여 <value /> 요소 내부의 텍스트를 java.util.Properties 인스턴스로 변환한다.
Autowiring
autowiring 을 사용하여 Spring 컨테이너가 bean 사이의 의존 관계를 자동으로 연결하게 할 수도 있으며, 이를 사용하면 속성이나 생성자의 인자의 설정을 줄일 수 있다. 사용 방법은 <bean /> 요소의 autowire 속성을 사용하여 no, byName, byType, constructor 모드 중 하나를 지정한다. byType 이나 constructor 모드를 사용하면, 배열과 형식화된 콜렉션도 연결할 수 있다.
- no : autowiring 없음. (default)
- byName : 속성의 name 과 일치하는 bean 을 찾아 autowiring.
- byType : 속성의 type 과 일치하는 bean 을 찾아 autowiring. (둘 이상의 bean 이 존재할 경우 치명적인 예외 발생)
- constructor : byType 과 유사하지만 생성자 인수와 일치하는 bean 을 찾아 autowiring. 컨테이너에 생성자 인수 타입의 bean 이 정확하게 하나가 아니면 치명적인 오류가 발생한다.
<property/> 와 <constructor-arg/> 요소로 설정된 의존성은 항상 autowiring 보다 우선하며, 단순한 속성들(primitives, Strings, Classes) 은 autowire 할 수 없다. autowiring 은 의존성을 명시적으로 지정한 것보다는 정확하지 않으므로, 자동 연결이 모호한 경우를 피하도록 주의해야 한다. 이 경우 autowiring 을 포기할 수도 있고, type 기반의 autowiring 이라면 <bean/> 요소의 autowire-candidate 속성을 false 로 설정하여 해당 bean 은 autowiring 을 사용하지 않게 할 수도 있다. 또는 <bean /> 요소의 primary 속성을 true 로 설정하여 단일 bean 정의를 기본 후보로 지정하거나, 어노테이션 기반의 세밀한 구성으로 해결할 수 있다.
Bean 상속 정의
자식 bean 은 부모 bean 에서 구성된 데이터를 상속하며, scope, 생성자 인수 값, 속성 값 및 메소드 등을 부모로부터 상속, 무시 및 재정의를 할 수 있다. 부모와 자식 bean 정의를 템플릿의 한 형태로 사용하면 많은 타이핑을 줄일 수 있다. 다음 설정은 항상 자식 bean 의 정의에서 가져온다 : depends on, autowire mode, dependency check, singleton, lazy init.
대부분의 사용자는 ClassPathXmlApplicationContext 로 bean 정의를 구성하여 사용하겠지만 프로그래밍 방식의 ApplicationContext 인터페이스로 작업하는 경우, 자식 bean 정의는 ChildBeanDefinition 클래스로 표현된다. XML 메타데이터를 사용할 때, 부모 bean 을 가리키는 parent 속성을 사용하여 자식 bean 을 정의한다.
<bean id="inheritedTestBean" abstract="true" class="org.springframework.beans.TestBean"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean" parent="inheritedTestBean" init-method="initialize"> <property name="name" value="override"/> <!-- age 속성 값 1 은 parent 에서 상속될 것이다. --> </bean> | cs |
앞의 예는 abstract 속성을 사용하여 명시적으로 부모 bean 을 추상으로 정의하였다. 부모 bean 정의에서 class 가 지정되지 않으면, 반드시 위와 같이 명시적으로 abstract 를 정의해야 한다. 이 경우 부모 bean 은 자체적으로 인스턴스화 될 수 없으며, 순수 템플릿 bean 정의로만 사용할 수 있다. 마찬가지로 컨테이너 내부의 preInstantiateSingletons() 메소드는 추상으로 정의된 bean 정의를 무시한다.