Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Spring 레퍼런스] 4장 IoC 컨테이너 #4

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.




4.4.2 의존성과 설정에 대한 자세한 내용

이전 섹션에서 얘기했듯이 빈 프로퍼티와 생성자 아규먼트를 다른 곳에서 관리되는 빈(협력 객체)에 대한 참조나 인라인으로 정의된 값으로써 정의할 수 있다. 스프링의 XML기반의 설정 메타데이터는 이를 위해 <property/>와 <constructor-arg/> 요소내에서 서브 엘리먼트 타입을 지원한다.


4.4.2.1 스트레이트(Straight) 값 (프리미티브(primitive), Strings 등등)

<property/> 요소의 value 속성은 사람이 읽을 수 있는 문자열로 프로퍼티나 생성자 아규먼트를 지정한다. 앞에서 얘기했듯이 이러한 문자열 값을 String에서 프로퍼티나 아규먼트의 실제 타입으로 변환하기 위해 JavaBeans PropertyEditors를 사용한다.


<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<!-- setDriverClassName(String) 호출의 결과 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>

다음 예제는 훨씬 간결한 XML 설정을 위해 p-namespace를 사용한다


<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close"
      p:driverClassName="com.mysql.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/mydb"
      p:username="root"
      p:password="masterkaoli"/>

</beans>


앞의 XML이 훨씬 간결하다. 하지만 빈 정의를 생성할 때 프로퍼티 자동완성을 지원하는 IntelliJ IDEASpringSource Tool Suite (STS) 같은 IDE를 사용하지 않는다면 오타는 설계할 때가 아닌 런타임시에 발견된다. 이러한 IDE의 지원을 사용하는 것을 적극 추천한다.

다음과 같이 java.util.Properties를 설정할 수도 있다.


<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

 <!-- java.util.Properties처럽 입력한다 -->
 <property name="properties">
    <value>
       jdbc.driver.className=com.mysql.jdbc.Driver
       jdbc.url=jdbc:mysql://localhost:3306/mydb
    </value>
 </property>
</bean>


스프링 컨테이터는 <value/>안의 텍스트를 JavaBeans PropertyEditor의 메카니즘을 이용해서 java.util.Properties 인스턴스로 변환한다. 이는 괜찮은 단축키이고 스프링 팀이 value 속성 스타일보다 중첩된 <value/> 요소를 사용하기를 좋아하는 몇 안되는 곳 중 하나이다.

idref 요소

idref요소는 컨테이너 내의 다른 빈의 id (문자열 값 - 참조가 아니다)를 <constructor-arg/> 나 <property/> 요소에 전달해서 간단히 오류를 검증할 수 있는(error-proof) 방법이다.


<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
  <property name="targetName">
      <idref bean="theTargetBean" />
  </property>
</bean>


위의 빈 정의 코드는 다음 코드와 완전히 같다.(런타임에서)


<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
  <property name="targetName" value="theTargetBean" />
</bean>



idref를 사용하면 배포할 때 컨테이너가 참조되거나 이름 지어진 빈이 실제로 존재하는지 검사할 수 있기 때문에 두 번째 보다는 첫 번째 형식을 더 선호한다. 두 번째 형식에서 client 빈의 targetName 프로퍼티에 전달한 값에 대해 유효성 체크를 하지 않는다. client 빈이 실제로 인스턴스화 될 때만 오타를 발견할 수 있다.(대부분은 fatal이 발생하면서) client 빈이 prototype 빈이라면 컨테이너를 배포하고 한참이 지난 후에야 오타를 발견하고 예외가 발생한다.

추가적으로 참조된 빈이 같은 XML에 있고 빈의 이름이 빈 id 이라면 XML 파서 스스로 XML 문서가 파싱될 때 빈의 id의 유효성을 먼저 검사하도록 local 속성을 사용할 수 있다.


<property name="targetName">
 <!-- 'theTargetBean'이라는 id를 가진 빈은 반드시 존재해야 한다. 존재하지 않는다면 예외가 던져질 것이다. -->
 <idref local="theTargetBean"/>
</property>



<idref/> 요소가 값을 가져오는 일반적인 위치(최소한 스프링 2.0 이전 버전에서는)는 ProxyFactoryBean 빈 정의안의 AOP 인터셉터의 설정이다. 인터셉터의 이름을 명시했을 때 <idref/> 요소를 사용하면 인터셉터의 id의 오타를 방지할 수 있다.


4.4.2.2 다른 빈에 대한 참조 (협력 객체)

ref 요소는 <constructor-arg/> 나 <property/> 정의 요소안의 마지막 요소이다. 이 요소에 컨테이너가 관리하는 다른 빈(협력 객체)을 참조하는 빈에 지정된 프로퍼티의 값을 설정한다. 참조된 빈은 프로퍼티의 빈의 의존성이고 프로퍼티가 설정되기 전에 필요에 따라 초기화된다. (협력객체가 싱글톤 빈이라면 컨테이너가 이미 초기화했을 것이다.) 모든 참조는 결국 다른 객체에 대한 참조가 된다. 범위(scope)와 유효성검사는 bean,local, , parent 속성으로 다른 빈의 id/name을 명시한 여부에 달려있다.

<ref/> 태그의 bean 속성으로 타겟 빈을 명시하는 것이 가장 일반적이고 이는 같은 XML 파일안에 존재하는지 여부에 관계 없이 같은 컨테이너나 부모 컨테이너의 어떤 빈에 대한 참조도 생성할 수 있다. bean 속성의 값은 타겟 빈의 id 속성이나 타겟 빈의 name 속성의 값 중 하나와 같을 것이다.


<ref bean="someBean"/>


local 속성으로 타겟빈을 명시하면 XML parser 기능을 사용해서 같은 파일안의 XML id 참조의 유효성을 검사한다. local 속성의 값은 타겟 빈의 id 속성과 반드시 일치해야 한다. 같은 파일에서 일치하는 요소를 찾지 못한다면 XML 파서는 오류가 난다. 이처럼 타겟 빈이 같은 XML 파일안에 있다면 local을 사용하는 것이 가장 좋은 선택이다.(가능한한 빨리 오류를 발견할 수 있도록)


<ref local="someBean"/>



parent 속성으로 타겟 빈을 지정하면 현재 컨테이너의 부모 컨테이너에 있는 빈에 대한 참조를 만든다. parent의 값은 타겟 빈의 id 속성이나 name 속성의 값들 중 하나와 같아야 할 것이다. 그리고 타겟 빈은 반드시 현재 컨테이너의 부모 컨테이너에 존재해야 한다. 이러한 형태의 빈 참조는 컨테이너가 계층형으로 되어 있을 때나 부모 빈과 같은 이름의 프록시를 가지는 부모 컨테이너에 존재하는 빈을 감싸고 싶을 때 주로 사용한다.


<!-- 부모 컨텍스트 -->
<bean id="accountService" class="com.foo.SimpleAccountService">
  <!-- 여기에 필요한 의존성을 추가한다 -->
</bean>




<!-- 자식(후손) 컨텍스트 -->
<bean id="accountService"  <-- 빈 이름은 부모 빈과 같다 -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/>  <!-- 어떻게 부모 빈을 참조하는지 봐라 -->
    </property>
  <!-- 여기에 필요한 다른 설정과 의존성을 추가한다 -->
</bean>





4.4.2.3 이너 빈(Inner beans)

<property/> 나 <constructor-arg/> 요소안에 있는 <bean/> 요소로 정의한 빈을 inner bean이라고 부른다.

<bean id="outer" class="...">
<!-- 타겟 빈에 대한 참조를 사용하는 대신에 간단히 타겟빈을 인라인으로 정의한다 -->
<property name="target">
  <bean class="com.example.Person"> <!-- 이너 빈이다 -->
    <property name="name" value="Fiona Apple"/>
    <property name="age" value="25"/>
  </bean>
</property>
</bean>



이너 빈의 정의는 id나 name을 정의할 필요가 없어서 컨테이너는 이러한 값들을 무시한다. 심지어 scope 플래그도 무시한다. 이너 빈은 항상 익명이면서 prototype의 범위를 가진다. 이너빈은 감싸진 빈외의 다른 협력 빈으로 주입하는 것을 불가능하다.


4.4.2.4 컬렉션(Collection)

<list/>, <set/>, <map/>, <props/> 요소에서 자바 Collection 타입인 List , Set, Map , Properties의 프로퍼티와 아규먼트를 각각 설정한다.


<bean id="moreComplexObject" class="example.ComplexObject">
<!-- setAdminEmails(java.util.Properties) 호출의 결과 -->
<property name="adminEmails">
  <props>
      <prop key="administrator">administrator@example.org</prop>
      <prop key="support">support@example.org</prop>
      <prop key="development">development@example.org</prop>
  </props>
</property>
<!-- setSomeList(java.util.List) 호출의 결과 -->
<property name="someList">
  <list>
      <value>a list element followed by a reference</value>
      <ref bean="myDataSource" />
  </list>
</property>
<!-- setSomeMap(java.util.Map) 호출의 결과 -->
<property name="someMap">
  <map>
      <entry key="an entry" value="just some string"/>
      <entry key ="a ref" value-ref="myDataSource"/>
  </map>
</property>
<!-- setSomeSet(java.util.Set) 호출의 결과 -->
<property name="someSet">
  <set>
      <value>just some string</value>
      <ref bean="myDataSource" />
  </set>
</property>
</bean>


map의 key나 value, set의 value의 값은 다음의 요소들 중 어떤 것이라도 될 수 있다.


bean | ref | idref | list | set | map | props | value | null



컬렉션 병합(merging)

스프링 2.0처럼 컨테이너는 컬렉션의 병합을 지원한다. 애플리케이션 개발자는 부모형식의 <list/>, <map/>, <set/>, <props/> 요소를 정의할 수 있고 부모 컬렉션을 상속받고 값을 오버라이드한 자식형식의 <list/>, <map/>, <set/>, <props/> 요소가 있을 수 있다. 자식 컬렉션의 값들은 부모와 자식 컬렉션의 요소를 합친 결과이고 자식의 컬렉션 요소는 부모 컬렉션의 값을 오버라이드한다.

병합에 대한 이번 섹션에서 부모-자식 빈의 메카니즘에 대해서 얘기한다. 부모와 자식 빈 정의에 익숙하지 않다면 계속 읽기 전에 관련 섹션을 읽어보기 바란다.

다음 예제는 컬렉션 병합을 보여준다.


<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
  <property name="adminEmails">
      <props>
          <prop key="administrator">administrator@example.com</prop>
          <prop key="support">support@example.com</prop>
      </props>
  </property>
</bean>
<bean id="child" parent="parent">
  <property name="adminEmails">
      <!-- 병합은 *자식* 컬렉션 정의에서 지정한다 -->
      <props merge="true">
          <prop key="sales">sales@example.com</prop>
          <prop key="support">support@example.co.uk</prop>
      </props>
  </property>
</bean>
<beans>



child 빈 정의에서 adminEmails 프로퍼티의 <props/> 요소에서 merge=true 속성을 사용했다. 컨테이너가 child 빈을 처리하고 인스턴스화했을 때 생성된 인스턴스는 부모의 adminEmails 컬렉션과 자식의 adminEmails 컬렉션을 합친 결과가 포함된 adminEmails Properties 컬렉션을 가진다.


administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk


자식 Properties 컬렉션의 값은 부모 <props/>의 모든 프로퍼티 요소를 상속받아 설정된다. support 값에 대한 자식 컬렉션의 값은 부모 컬렉션의 값을 오버라이드한다.

이 병합 작업은 <list/>, <map/>, <set/> 컬렉션타입에도 비슷하게 적용된다. <list/> 요소의 경우에서 List 컬렉션 타입과 결합한 의미는 유지된다. 이는 값의 ordered 컬렉션의 개념을 말한다. 부모의 값은 자식 리스트의 모든 값들보다 우선시된다. Map, Set, Properties 컬렉션 타입에서는 순서가 존재하지 않는다. 따라서 순서가 없다는 개념은 컨테이너가 내부적으로 사용하는 연관된 Map, Set, Properties를 구현한 타입에 기반한 컬렉션 타입에서 유효하다.

컬렉션 병합의 한계

다른 켈렉션 타입은 병합할 수 없고(Map List) 이러한 병합을 시도한다면 적절한 Exception가 던져진다. merge 속성은 하위에 있고 상속받는 자식 정의에서 지정되어야 한다. 부모 컬렉션 정의에서 merge 속성을 지정해도 사용되지 않으며 원하는 병합이 일어나지 않을 것이다. 병합 기능은 스프링 2.0 이후의 버전에서만 사용할 수 있다.

강타입(Strongly-typed)의 컬렉션(Java 5+에서만 사용 가능)

자바 5 이상에서 강타입(strongly typed)의 컬렉션을 사용할 수 있다.(제너릭 타입을 사용해서) 예를 들면 String 엘리먼트만 이루어진 Collection 타입을 선언할 수 있다. 스프링으로 빈에 강타입의 Collection을 의존성-주입한다면 스프링의 타입변환 지원의 이점을 얻을 수 있다. 스프링의 타입변환은 강타입의 Collection 인스턴스의 엘리먼트를 Collection에 추가하기 전에 적절한 타입으로 변환한다.


public class Foo {

  private Map<String, Float> accounts;

  public void setAccounts(Map<String, Float> accounts) {
      this.accounts = accounts;
  }
}




<beans>
  <bean id="foo" class="x.y.Foo">
      <property name="accounts">
          <map>
              <entry key="one" value="9.99"/>
              <entry key="two" value="2.75"/>
              <entry key="six" value="3.99"/>
          </map>
      </property>
  </bean>
</beans>



foo 빈의 accounts 프로퍼티가 주입을 위한 준비되었을 때 강타입의 Map<String, Float> 엘리먼트 타입에 대한 제너릭 정보는 리플렉션(reflection)으로 사용할 수 있다. 그러므로 스프링의 타입변환 인프라는 강한 값인 9.99, 2.75, 3.99이 실제 Float 타입으로 변환되는 것처럼 다양한 값 요소를 인지할 수 있다.


4.4.2.5 Null과 비어있는 문자열 값

스프링은 프로퍼티에 대한 비어있는 아규먼트를 비어있는 Strings처럼 다룬다. 다음 XML 기반 설정 메타데이터 코드는 email 프로퍼티에 비어있는 String 값 ("")을 설정한다.


<bean class="ExampleBean">
<property name="email" value=""/>
</bean>



앞의 예제는 다음 자바코드 exampleBean.setEmail("") 와 같다. <null/> 요소는 null 값을 다룬다. 예를 들면 다음과 같다.


<bean class="ExampleBean">
<property name="email"><null/></property>
</bean>




위의 설정은 다음 자바코드 exampleBean.setEmail(null) 와 같다.


4.4.2.6 p-namespace를 사용하는 XML 단축키(shortcut)

p-namespace는 프로퍼티 값과 협력 객체를 나타내기 위해 중첩된 <property/> 요소 대신 bean 요소의 속성을 사용 가능케 한다.

스프링 2.0부터는 XML 스키마 정의에 기반해서 네임스페이스를 사용하는 확장가능한 설정형식을 지원한다. 이번 챕터에서 얘기할 beans 설정 형식은 XML 스키나 문서에 정의한다. 하지만 p-namespace는 XSD 파일에 정의하지 않고 스프링 코어에만 존재한다.

다음 예제는 같은 결과를 처리하는 두 가지 XML 코드를 보여준다. 첫 번째는 표준 XML형식을 사용하고 두 번째는 p-namespace를 사용한다.


<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean name="classic" class="com.example.ExampleBean">
      <property name="email" value="foo@bar.com"/>
  </bean>

  <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>




이 예제는 빈 정의에서 email이라고 부르는 p-namespace의 속성을 보여준다. 이는 스프링이 프로퍼티 선언을 포함하도록 한다. 앞에서 언급했듯이 p-namespace는 스키마 정의가 없으므로 프로퍼티 이름에 속성의 이름을 설정할 수 있다.

다음 예제는 다른 빈에 대한 참조를 가진 두 개 이상의 빈 정의를 포함한다.


<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean name="john-classic" class="com.example.Person">
      <property name="name" value="John Doe"/>
      <property name="spouse" ref="jane"/>
  </bean>

  <bean name="john-modern"
      class="com.example.Person"
      p:name="John Doe"
      p:spouse-ref="jane"/>

  <bean name="jane" class="com.example.Person">
      <property name="name" value="Jane Doe"/>
  </bean>
</beans>



여기서 보듯이 이 예제는 p-namespace를 사용한 프로퍼티 값을 포함하고 있을 뿐 아니라 프로퍼티 참조를 선언하는 특별한 형식을 사용한다. 첫 번째 빈 정의가 john 빈에서 jane 빈으로의 참조를 생성하는 <property name="spouse" ref="jane"/>를 사용하지만 두번찌 빈 정의는 완전히 같은 일을 하는 속성으로 p:spouse-ref="jane"를 사용한다. 이 경우에 spouse는 프로퍼티 이름이지만 -ref부분은 직접적인 값이 아니라 다른 빈에 대한 참조라는 것을 나타낸다.

Note
p-namespace는 표준 XML 형식처럼 유연하지는 않다. 예를 들어 프로퍼티 참조를 선언하는 형식은 Ref로 끝나는 프로퍼티와 충돌이 나지만 표준 XML은 충돌나지 않는다. 동시에 세가지 방법을 사용하는 XML 문서가 생기는 문제를 피하기 위해 어떤 방법을 사용할지 신중하게 선택하고 이에 대해서 팀 멤버들과 협의하기를 추천한다.


4.4.2.7 c-namespace를 사용하는 XML 단축키(shortcut)

Section 4.4.2.6, “p-namespace를 사용하는 XML 단축키(shortcut)”와 스프링 3.1에 도입된 c-namespace는 생성자 아규먼트 설정을 위해서 중첩된 constructor-arg 요소 대신에 인라인 속성을 사용할 수 있게 한다.

c 네임스페이스를 사용하는 Section 4.4.1.1, “생성자 기반의 의존성 주입”의 예제를 보자.


<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:c="http://www.springframework.org/schema/c"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">
      
  <bean id="bar" class="x.y.Bar"/>
  <bean id="baz" class="x.y.Baz"/>

  <-- 'traditional' declaration -->      
  <bean id="foo" class="x.y.Foo">
      <constructor-arg ref="bar"/>
      <constructor-arg ref="baz"/>
      <constructor-arg value="foo@bar.com"/>
  </bean>

  <-- 'c-namespace' declaration -->
  <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com">

</beans>




생성자 아규먼트를 그 이름으로 설정하기 위해서 c: 네임스페이스는 p: 네임스페이스와 같은 관례를 사용한다.(빈 참조에는 -ref가 뒤에 붙는다.) 그리고 마찬가지로 XSD 스키마에 정의되어 있지 않더라도(스프링 코어 안에 존재한다.) 선언될 필요가 있다.

생성자 아규먼트 이름을 이용할 수 없는 드문 경우(보통 바이트코드가 디버깅 정보 없이 컴파일된다면)에는 아규먼트 인덱스를 대신 폴백(fallback)으로 사용한다


<-- 'c-namespace' 인덱스 선언 -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz">




Note
XML 문법때문에 인덱스 노테이션은 _로 시작해야 한다. XML 속성의 이름은 숫자로 시작할 수 없다.(IDE가 이를 허락한다고 하더라도)

실제로 생성자 결정 메카니즘은 실제로 필요한 것 없이도 적합한 아규먼트를 꽤 잘 찾아내기 때문에 설정 전체에 이름 노테이션을 사용하기를 추천한다.


4.4.2.8 프로퍼티 이름 혼합

빈 프로퍼티를 설정할 때 마지막 프로퍼티 이름을 제외한 경로의 모든 컴퍼넌트가 null이 아니라면 혼합된 프로퍼티 이름이나 중첩된 프로퍼티 이름을 사용할 수 있다. 다음 빈 정의를 보자.


<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>




foo 빈에는 fred 프로퍼티가 있다. fred 프로퍼티에는 bob 프로퍼티가 있고 bob 프로퍼티에는 sammy 프로퍼티가 있다. 마지막 sammy 프로터티에는 123 이라는 값이 설정된다. 이것이 동작하게 하려면 foo의 fred 프로퍼티와 fred의 bob 프로퍼티가 빈이 생성된 후에 반드시 null이 아니어야 한다. 그렇지 않으면 NullPointerException 예외가 던져진다.
2012/03/03 02:09 2012/03/03 02:09