Dependencies

전형적인 기업용 어플리케이션은 한 개의 객체(또는 Spring내 Bean)로 만들어지지는 않는다. 가장 간단한 어플리케이션조차도 함께 작동하는 소량의 객체를 가진다는 것을 의심할 필요가 없을 것이다. 이 장에서는 독립적인 많은 수의 Bean들이 객체가 몇 가지 목표(대개 최종사용자가 원하는 것을 수행하는 어플리케이션)를 달성하기 위해 함께 작동하는 방법에 대해 알아보기로 한다.

Dependency Injection(DI)

각 클래스 사이의 의존관계를 빈 설정(Bean Definition)정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것을 말한다. 컨테이너가 의존관계를 자동적으로 연결시켜주기 때문에 개발자들이 컨테이너 API를 이용하여 의존관계에 관여할 필요가 없게 되어 컨테이너 API에 종속되는 것을 줄일 수 있고 개발자들은 단지 Bean 설정파일(저장소 관리 파일)에서 의존 관계가 필요하다는 정보를 추가하기만 하면 된다. 이는 Setter Injection과 Constructor Injection 형태로 구분한다.

Setter Injection

setter 메소드 구현을 통해 초기화 시 Container로부터 의존 관계에 놓인 특정 리소스를 할당받는 방법으로 인자가 없는 생성자나 인자가 없는 static factory 메소드가 Bean을 인스턴스화하기 위해 호출된 후 Bean의 setter 메소드를 호출하여 실제화된다.
다음은 구현 클래스인 UserServiceImplWithIoCBasic.java 의 Setter Injection 부분이다.
public class UserServiceImplWithIoCBasic implements UserServiceWithIoCBasic {
    public void setUserDAO(UserDAOWithIoCBasic userDAO) {
        this.userDAO = userDAO;
	}
    //중략 
}
다음은 Setter Injcetion 속성 정의 파일인 applicationContext-user-setter.xml 의 일부이다.
<bean id="com.sds.emp.user.services.UserServiceWithIoCBasic"
		class="com.sds.emp.user.services.impl.UserServiceImplWithIoCBasic">
	<property name="userDAO" ref="userDAO" />
</bean>
	
<bean id="userDAO" class="com.sds.emp.user.services.impl.UserDAOWithIoCBasic"/>

Constructor Injection

Constructor 구현을 통해 초기화 시 Container로부터 의존 관계에 놓인 특정 리소스를 할당받는 방법으로 각각의 협력자를 표시하는 다수의 인자를 가진 생성자를 호출하여 실제화된다. 추가적으로, Bean을 생성하기 위한 특정 인자를 가진 static factory 메소드를 호출하는 것은 대부분 동등하게 간주될 수 있다.
다음은 구현 클래스인 UserServiceImplWithIoCBasic.java 의 Constructor Injection 부분이다.
public class UserServiceImplWithIoCBasic implements UserServiceWithIoCBasic {
	public UserServiceImplWithIoCBasic(UserDAOWithIoCBasic userDAO){
		this.userDAO = userDAO;
	}
	//중략 
}
다음은 Constructor Injcetion 속성정의 파일인 applicationContext-user-constructor.xml 의 일부이다.
<bean id="com.sds.emp.user.services.UserServiceWithIoCBasic"
		class="com.sds.emp.user.services.impl.UserServiceImplWithIoCBasic">
    <constructor-arg ref="userDAO"/>
    
    <bean id="userDAO" class="com.sds.emp.user.services.impl.UserDAOWithIoCBasic"/>
</bean>
type 속성 정의를 이용하면, Constructor의 argument에 대한 클래스 타입을 명시적으로 정의할 수도 있다.
<bean id="com.sds.emp.user.services.UserServiceWithIoCBasic" 
  class="com.sds.emp.user.services.UserServiceWithIoCBasic">
  <constructor-arg type="com.sds.emp.user.services.BeanA" ref="beanA"/>
  <constructor-arg type="com.sds.emp.user.services.BeanB" ref="beanB"/>
</bean>
Constructor의 argument 개수가 2개 이상이고, 동일한 클래스 타입의 argument가 존재할 경우 모호함을 없애기 위해, index 속성 정의를 통해 argument의 순서대로 할당할 값을 정의할 수 있다.
<bean id="com.sds.emp.user.services.UserServiceWithIoCBasic"
		class="com.sds.emp.user.services.impl.UserServiceImplWithIoCBasic">
    <constructor-arg index="0" ref="beanA" /> 
    <constructor-arg index="1" ref="beanB" /> 
</bean> 

Setter Injection vs. Constructor Injection

Setter Injection 장점
Constructor Injection 장점
- 생성자 Parameter 목록이 길어 지는 것 방지
- 생성자의 수가 많아 지는 것 방지
- Circular dependencies 방지
- 강한 의존성 계약 강제
- Setter 메소드 과다 사용 억제
- 불필요한 Setter 메소드를 제거함으로써 실수로 속성 값을 변경하는 일을 사전에 방지

  • Circular dependencies
  • Constructor Injection 사용 시 주의해야 한다. 다음과 같이 두 개의 서로 다른 Bean이 생성자 Argument로 서로의 Bean을 참조하는 경우가 그 예이다.
    <bean id="beanFirst" class="test.BeanFirst">
              <constructor-arg ref="beanSecond" />
    </bean>
    
    <bean id="beanSecond" class="test.BeanSecond">
              <constructor-arg ref="beanFirst" />
    </bean>

생성자 인자 분석

생성자 인자 분석 시 사용되는 방법에는 타입 대응과 인덱스가 있다.
  • 생성자의 인자 타입 대응(match)
  • 'type' 속성을 사용하여 생성자의 인자 타입을 명확하게 명시함으로써 간단한 타입으로의 타입 매치를 사용할 수 있다.
    <bean id="exampleBean" class="examples.ExampleBean">
    	<constructor-arg type="int"><value>7500000</value></constructor-arg>
    	<constructor-arg type="java.lang.String"><value>42</value></constructor-arg>
    </bean>
  • 생성자의 인자 인덱스
  • 생성자의 인자는 index 속성을 사용하여 명확하게 명시된 인덱스를 가질 수 있다. 또한 인덱스를 명시하는 것은 생성자의 인자들이 같은 타입을 가질 경우 발생하는 모호함의 문제도 해결한다. (인덱스는 0 부터 시작된다는 것에 주의하여야 한다.)
    <bean id="exampleBean" class="examples.ExampleBean">
    	<constructor-arg index="0" value="7500000"/>
    	<constructor-arg index="1" value="42"/>
    </bean>

Bean Property와 생성자 인자

Bean Property와 생성자의 인자는 다른 관리 Bean(협력자), 또는 인라인으로 정의된 값을 참조할 수 있다. Spring의 XML-기반의 설정 메타데이터는 이러한 목적을 위한 <property /> 와 <constructor-arg /> 내에서 많은 수의 하위 태그를 지원한다.
  • Primitive Type ? 순수값 지원
  • <value />는 사람이 읽을 수 있는 문자열 표현처럼 Property나 생성자의 인자를 명시한다.
    <bean id="myDataSource" destroy-method="close">
      <property name="driverClassName">
        <value>com.mysql.jdbc.Driver</value>
      </property>
    </bean>
  • <idref> 요소
  • 참조하는 Bean에 대한 유효성 체크 (error-proof): <idref>는 Container 내의 다른 bean의 id를 참조하기 전에 미리 참조 Bean의 존재 여부를 검사 (error-proof)한다.

    • 배포 시 참조하는 Bean에 대한 유효성 체크 (error-proof)
    • <bean id="theTargetBean" class="..."/>
      <bean id="theClientBean" class="...">
          <property name="targetName">
              <idref bean="theTargetBean" />
          </property>
      </bean>
      위코드는 Runtime시 아래의 코드와 동일하다.
      <bean id="theTargetBean" class="..."/>
      <bean id="theClientBean" class="...">
          <property name="targetName">
              <value>theTargetBean</value>
          </property>
      </bean>
    XML Parsing시 참조하는 Bean id 체크 : 만약 참조되는 Bean이 같은 XML 파일 내에 정의되어 있고 local 속성이 정의되었다면, XML 문서를 파싱하는 시점에 좀더 일찍 XML Parser가 참조 Bean의 이름이 존재하는지 자체적으로 체크한다.
    • XML Parsing 시 참조하는 Bean id 체크 (동일 XML 내에 존재하는 경우)
    • <property name="targetName">
         <!-- a bean with an id of 'theTargetBean' must exist, 
                                       else an XML exception will be thrown -->
         <idref local="theTargetBean"/>
      </property>
  • <ref> 요소
  • 다른 bean에 대한 참조인 <ref>는 <constructor-arg/> 또는 <property/> 내부에 허용되는 마지막 요소이다. 이것은 Container에 의해 관리되는 다른 Bean을 참조하기 위해 Property의 값을 셋팅하는데 사용된다. 모든 참조는 궁극적으로 다른 객체에 대한 참조이지만 다른 객체의 id/name을 명시하는 방법은 3가지가 있다. <ref/>의 bean 속성을 사용하여 대상 bean을 명시하는 것이 가장 일반적인 형태이고 같은 Container(같은 XML파일이든 아니든)나 부모 Container 내에서 어떠한 Bean에 대한 참조를 생성하는 것을 허용할 것이다. 'bean' 속성의 값은 대상 bean의 'id' 속성이나 'name' 속성의 값 중 하나가 될 것이다.
    • 타 Bean 참조
    • <!-- ‘bean’ 속성 값은 타 Bean의 ‘id’ 속성 혹은 ‘name’ 속성이다. -->
      <ref bean="someBean"/>
      <!-- ‘local’ 속성 값은 동일 XML 파일 내 타 Bean의 ‘id’ 속성이다. -->
      <ref local="someBean"/> 
    • parent context에 존재하는 타 Bean 참조(parent 속성 사용)
    • <!-- in the parent context -->
      <bean id="accountService" class="com.foo.SimpleAccountService">
          <!-- insert dependencies as required as here -->	
      </bean>
      <!-- in the child (descendant) context -->
      <bean id="userService" class="com.foo.SimpleUserService">
      	<ref parent="accountService"/>
      </bean>
  • inner Bean
  • <property /> 나 <constructor-arg /> 내부의 <bean />은 inner bean이라 불리는 것을 정의하기 위해 사용된다. inner bean 정의시 언급된 id나 name, scope값은 Container에 의해 무시되기 때문에 id나 name값을 명시하지 않는 것이 가장 좋다. inner bean은 언제나 익명이고 prototype 형태로 동작한다.
    <bean id="outer" class="...">
      <!-- instead of using a reference to a target bean, simply define the target inline -->
      <property name="target">
        <bean class="com.mycompany.Person"> 
        <!-- this is the inner bean -->
          <property name="name" value="Fiona Apple"/>
          <property name="age" value="25"/>
        </bean>
      </property>
    </bean>
  • Collection
  • <list> , <set> , <map>과 <props>은 Java Collection의 List, Set, Map and Properties의 타입으로 매핑된다. 또한 객체 Array 타입의 경우에도 콤마(,)를 이용하여 값을 설정할 수 있다(ex. String[]).
    <bean id="moreComplexObject" class="example.ComplexObject">
      <!-- results in a setAdminEmails(java.util.Properties) call -->
      <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@somecompany.org</prop>
        </props>
      </property>
    
      <!-- results in a setSomeList(java.util.List) call -->
      <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
      </property>
    
      <!-- results in a setSomeMap(java.util.Map) call -->
      <property name="someMap">
        <map>
            <entry>
                <key>
                    <value>entry key</value>
                </key>
                <value>entry value</value>
            </entry>
        </map>
      </property>
    
      <!-- results in a setSomeSet(java.util.Set) call -->
      <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
      </property>
    
      <!-- results in a setSomeArray(String[]) call -->
      <property name="someArray" value="str1,str2,str3,str4"/>  
      
    </bean>
  • Collection 병합
  • 부모 역할을 하는 <list> , <map > , <set> 또는 <props>를 정의하고 이를 상속받는 <list> , <map> , <set> 또는 <props>를 정의하는 것이 가능하다. 예를 들면, 자식 collection의 값은 부모 collection내 명시된 값과 자식 collection내 명시된 값을 병합하여 얻어진다.

    예제 설명) child Bean의 adminEmails Property의 <props>에서 merge=true속성을 사용하면, child Bean이 Container에 의해 실질적으로 분석되고 인스턴스화 될때, 부모의 adminEmails collection과 자식의 adminEmails collection이 병합된 형태의 adminEmails collection을 가지게 된다. 이 병합 행위는 <list> , <map>, 그리고 <set> collection 타입에 유사하게 적용된다. 단, <list>의 경우, 이 의미는 List collection 타입과 관련된다. 이를테면, value의 ordered collection의 개념은 유지관리된다. 부모값은 모든 자식 목록의 값에 선행한다. Map, Set, Properties collection 타입의 경우, Container에 의해 내부적으로 사용되는 Map, Set 그리고 Properties 객체 타입에 관련된 collection 타입의 영향을 받는다.
    <beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
         <property name="adminEmails">
           <props>
              <prop key="administrator">administrator@somecompany.com</prop>
              <prop key="support">support@somecompany.com</prop>
           </props>
          </property>
    </bean>
    <bean id="child" parent="parent">
         <property name="adminEmails">
           <!-- the merge is specified on the *child* collection definition -->
           <props merge="true">
             <prop key="sales">sales@somecompany.com</prop>
             <prop key="support">support@somecompany.co.uk</prop>
           </props>
          </property>
    </bean>
    <beans>
    위 설정 결과 adminEmails Collection은 다음과 같이 구성된다.
    administrator=administrator@somecompany.com
    sales=sales@somecompany.com
    support=support@somecompany.co.uk
  • <null> 요소
  • <null>은 null 값을 다루기 위해 사용된다.
    <bean class="ExampleBean">
      <property name="email"><null/></property>
    </bean>
    위코드는 Java Code의 exampleBean.setEmail(null)과 동일하다. 다음과 같이 정의한 경우에는 Java Code의 exampleBean.setEmail("")과 동일하다.
    <bean class="ExampleBean">
      <property name="email"><value></value></property>
    </bean>

    XML 기반의 설정 메타데이터 간략화

    value나 Bean 참조를 위해 필요한 공통사항이다. 완전한 형태의 <value> 와 <ref>를 사용하는 것보다 간략화한 몇 가지 형태를 사용할 수 있다. <property> , <constructor-arg> , 그리고 <entry> 모두 완전한 형태의 <value> 요소 대신에 'value' 속성을 지원한다. 예를 들어 코드1이 코드2의 형태로 간략화 될 수 있다.
    <!-- 코드 1 -->
    <property name="myProperty"><value>hello</value></property>
    <!-- 코드 2 -->
    <property name="myProperty" value="hello"/> 

    혼합된 Property 명(Compound Property) ? shortcut 기능 제공

    복합적인 형태의 Property 정의가 가능하다. 마지막 Property명을 제외한 나머지 Property는 null이 아니어야 함에 유의하도록 한다.
    <bean id="foo" class="foo.Bar">
      <property name="fred.bob.sammy" value="123" />
    </bean>
    위 예제에서 foo bean은 bob Property를 가지는 fred Property를 가진다. 그리고 bob Property는 sammy Property를 가지고 마지막 sammy Property는 123값으로 셋팅된다. 이렇게 되도록 하기 위해서는 foo의 fred Property, 그리고 fred의 bob Property는 bean이 생성된 후에 null이 아니어야만 한다. 그렇지 않으면 NullPointerException이 던져질 것이다.

    depends-on 속성 사용

    'depends-on' 속성은 Bean 이전에 초기화되어야 하는 하나 이상의 Bean을 명시적으로 강제하기 위해 사용된다. 다음은 depends-on 속성이 설정되어 있는 applicationContext-user-dependencies.xml 파일의 일부이다.
    <bean id="com.sds.emp.user.services.UserServiceWithIoCBasic"
    	class="com.sds.emp.user.services.impl.UserServiceImplWithIoCBasic" 
    	                           autowire="byType" depends-on="userSample">
    </bean>
    다중 bean에 의존성을 표시할 필요가 있다면 아래의 예와 같이 콤마, 공백 그리고 세미콜론과 같은 모든 유효한 구분자를 사용하여 'depends-on'속성의 값으로 bean 이름 목록을 정의할 수 있다. 그러나 이 ‘depends-on’ 속성을 사용하게 될 상황은 매우 드물다.
    <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
      <property name="manager" ref="manager" />
    </bean>
    
    <bean id="manager" class="ManagerBean" />
    <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
    위의 예제는 beanOne Bean이 생성되기 이전에 manager Bean이 생성되어 특정 서버를 구동시켜놓거나 특정 리소스에 대한 작업을 수행해놓고 있어야 beanOne Bean이 정상적으로 동작하므로 강제적으로 manager Bean을 초기화시킨다.

    Lazy Instantiation

    기본적으로 Spring IoC Container가 Start될 때 singleton Bean에 대해서는 모두 인스턴스화한다.
    - 특정 singleton Bean을 Container가 Start될 때 인스턴스화 시키지 않고 처음 Bean 요청이 들어왔을 때 인스턴스화 시키고자 하면 ‘lazy-init’ 속성을 설정한다. 다음은 Lazy Instantiation 속성이 설정되어 있는 파일인 applicationContext-user-dependencies.xml 파일의 일부이다.
    <bean id="userDAO" class="com.sds.emp.user.services.impl.UserDAOWithIoCBasic" lazy-init="true">
    <bean id="userSample" class="com.sds.emp.user.services.impl.UserSample"/>
    - 모든 Bean들에 대해서 기본적으로 Lazy 인스턴스화 시키고자 하면 ‘default-lazy-init’ 속성을 설정하면 된다.
    <beans default-lazy-init="true">
        <!-- no beans will be eagerly pre-instantiated... -->
    </beans>

    Autowiring

    Spring IoC Container는 Bean들 사이의 관계를 autowire 할 수 있다. 이것은 BeanFactory의 내용을 조사함으로써 Spring이 자동적으로 협력자(다른 bean)를 분석하는 것이 가능하다는 것을 의미한다. autowiring을 사용하면 명백하게 많은 양의 타이핑을 줄이고 Property나 생성자의 인자를 명시할 필요를 줄이거나 제거하는 것이 가능해진다. XML-기반의 설정 메타데이터를 사용할 때, Bean정의를 위한 autowire 모드는 <bean>의 autowire 속성을 사용하면 된다. <bean>의 autowire 속성에 정의할 수 있는 값은 다음과 같다.
    속성
    설명
    no [기본 설정] Autowiring 기능 사용 안 함
    byName Property 명과 동일한 id나 name을 가진 Bean을 찾아 Autowiring 기능 적용
    byType 해당 Property 타입의 Bean이 하나 존재한다면 Autowiring되나 하나 이상 존재 시 UnsatisfiedDependencyException 발생됨. 만약 대응되는 Bean이 없다면 Property 셋팅 안됨
    constructor 이것은 byType과 유사하지만 생성자의 인자에 적용됨. BeanFactory내 생성자의 인자 타입과 맞는 Bean이 정확하게 하나가 아닐 경우 UnsatisfiedDependencyException 발생됨
    autodetect constructor 모드 수행 후 byType 모드가 수행됨
    default <beans>의 default-autowire 속성에 설정한 autowire 모드가 해당 Bean에 적용됨
    다음은 Autowiring 속성이 설정되어 있는 applicationContext-user-dependencies.xml 파일의 일부이다.
    <bean id="com.sds.emp.user.services.UserServiceWithIoCBasic"
    	class="com.sds.emp.user.services.impl.UserServiceImplWithIoCBasic" 
    	autowire="byType" depends-on="userSample">
    </bean>

    장점

    • Property나 생성자의 인자를 XML에 설정할 필요 없음
    • XML 파일 크기 줄어듬
    • 참조 관계에 있는 타 Bean들의 변경 및 추가 시 XML 파일의 변경이 최소화됨
    • 동일한 이름의 Bean을 XML에 중복 정의하여 사용하는 혼동을 없애 줌

    단점

    • Bean들의 관계가 명시적으로 문서화되지 않음으로써 기대되지 않는 결과를 가지지 않게 주의해야 함
    • 타입에 의한 Autowiring은 잠재적인 모호함을 가져올 수 있음
    * Autowiring 대상에서 특정 Bean을 제외하려면 autowire-candidate 속성을 false로 설정해주어야 한다.
    <bean id="bean" class="example.TestBean” autowire-candidate="false" />

    Dependency Check

    해당 Bean에 설정된 모든 Property들(Primitive Type/Collection 및 Bean 참조)이 제대로 설정되었는지 확인한다.
  • <bean>의 dependency-check 속성 설정
  • 모드
    설명
    none [기본 설정] 의존성 확인 안 함. 참조관계의 Bean이 존재하지 않는 경우 Property 설정 안 함
    simple Primitive Type과 collection을 위해 의존성 확인 수행
    object 참조관계의 Bean을 위해 의존성 확인 수행
    all simple과 object 모드를 모두 수행

    다음은 Dependency Check의 속성 정의 예시이다.
    <bean id="userService" class="com.sds….UserServiceImpl" dependency-check="object”>
    	<property name="userDAO" ref="userDAO" />
    </bean>
    또한 다음과 같은 방법으로 모든 Bean들에 대해서 동일하게 Dependency Check 여부를 설정할 수 있다.
    <beans default-dependency-check="none" >
        <!-- no beans will be eagerly pre-instantiated... -->
    </beans>

    Resources

    • 다운로드
    • 샘플 테스트 코드를 포함하고 있는 anyframe-iocbasictest-src.zip 파일을 다운받은 후, 테스트 환경 설정 을 참조하여 위에서 제시한 예제 코드를 실행해 볼 수 있다.
      Name
      Download
      anyframe-iocbasictest-src.zip
      Download


    • 참고자료