Outsider's Dev Story

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

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

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




4.4.3 depends-on의 사용

빈 이 다른 빈의 의존성이 있다면 보통 빈은 다른 빈의 프로퍼티로 설정된다는 것을 의미한다. 일반적으로 이는 XML 기반의 설정 메타데이터에서 <ref/> 요소로 설정한다. 하지만 종종 빈들 사이의 의존성은 직접적이지 않을 수 있다. 예를 들어 데이터베이스 드라이버 등록같은 클래스의 정적 초기화(static initializer)는 실행될 필요가 있다. depends-on 속성은 <ref/> 요소로 빈을 초기화하기 전에 명시적으로 하나 이상의 빈을 강제적으로 초기화한다. 다음 예제는 하나의 빈의 의존성을 나타내려고 depends-on 속성을 사용한다.

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>

<bean id="manager" class="ManagerBean" />




여러 빈의 의존성을 나타내려면 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" />




Note
빈 정의의 depends-on 속성은 초기화할 때의 의존성과 대응되는 소멸될 때의 의존성을 모두 지정할 수 있다. 소멸될 때의 의존성은 싱글톤 빈에서만 가능하다. 주어진 빈으로 depends-on의 관계를 정의한 의존하는 빈이 주어진 빈 자체가 소멸되는 것보다 먼저 소멸된다. 그래서 depends-on는 셧다운하는 순서를 제어할 수도 있다.


4.4.4 지연 초기화(Lazy-initialized)되는 빈

기 본적으로 ApplicationContext 구현체는 초기화 과정에서 모든 싱글톤 빈을 열심히 생성하고 설정한다. 보통 이러한 미리 인스턴스화하는 과정은 설정이나 주변 환경의 오류를 즉시 발견할 수 있기 때문에 몇 시간 혹은 며칠 뒤에 발견하는 것보다 바람직하다. 이 미리 인스턴스화하는 동작이 바람직하지 않다면 빈 정의를 지연초기화로 표시함으로써 싱글톤 빈의 미리 인스턴스화하는 행동을 막을 수 있다. 지연 초기화되는 빈은 IoC 컨테이너가 시작할 때가 아닌 빈 인스턴스가 최초로 요청되었을 때 생성하도록 한다.

예를 들면 XML에서 <bean/> 요소의 lazy-init 속성으로 지연 초기화를 제어한다.



<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>



<bean name="not.lazy" class="com.foo.AnotherBean"/>




ApplicationContext가 앞의 설정을 사용할 때 lazy라고 이름 붙은 빈은 ApplicationContext이 시작할 때 미리 인스턴스화하지 않는다. 반면에 not.lazy빈은 미리 인스턴스화된다.

하지만 지연 초기화되는 빈이 지연 초기화되지 않는 싱글톤 빈의 의존성이라면 싱글톤 빈의 의존성을 반드시 만족시켜야 하기 때문에 ApplicationContext는 지연 초기화되는 빈을 시작하면서 생성한다. 지연 초기화되는 빈은 지연 초기화되지 않는 다른 싱글톤 빈에 주입된다.

<beans/> 요소의 default-lazy-init 속성을 사용해서 컨테이너 레벨에서 지연 초기화를 제어할 수도 있다. 예를 들면 다음과 같다.



<beans default-lazy-init="true">

  <!-- 어떤 빈도 미리 인스턴스화되지 않는다... -->

</beans>





4.4.5 협력객체의 자동연결(Autowiring)

스프링 컨테이너는 협력 빈들 사이의 관계를 자동연결(autowire) 할 수 있다. 스프링이 ApplicationContext의 내용을 검사해서 빈에 대한 협력 객체(다른 빈들)를 자동으로 처리하도록 할 수 있다. 자동연결(Autowiring)은 다음과 같은 장점이 있다.

  • 자동연결(Autowiring)은 프로퍼티나 생성자 아규먼트를 지정하는 일을 현저하게 줄일 수 있다. (이 점에 관해서 이 챕터의 다른 부분에서 얘기 할 빈 템플릿(bean template)같은 다른 메카니즘도 역시 유용하다.)
  • 자동연결(Autowiring)은 객체의 발전처럼 설정을 수정할 수 있다. 예를 들어 클래스에 의존성을 추가해야 한다면 의존성은 설정을 수정할 필요없이 자동으로 만족될 수 있다. 그러므로 자동연결은 개발할 때 특히 유용하다. 코드베이스가 더 안정적이 되었을 때 명시적으로 연결하기 위해 옵션을 변경할 필요가 없다.
XML 기반의 설정 메타데이터를 사용할 때[2], <bean/> 요소의 autowire 속성으로 빈 정의에 대한 autowire 모드를 지정한다. 자동연결(autowiring) 기능에는 5 가지 모드가 있다. 빈 마다 자동연결을 지정하기 때문에 자동연결할 빈을 선택할 수 있다.

Table 4.2. 자동연결 모드

모드설명
no

(기본값) 자동연결하지 않는다. 빈 참조는 반드시 ref 요소로 정의되어야 한다. 협력객체들을 명시적으로 지정하면 더 많은 제어와 투명성을 제공하기 때문에 더 큰 배포를 위해 기본설정을 변경하는 것은 권하지 않는다. 어떤 면에서 이는 시스템 구조의 문서화이다.

byName

프로퍼티 이름에 의한 자동연결(Autowiring). 스프링은 자동 연결이 필요한 프로퍼티와 같은 이름의 빈을 찾는다. 예를 들어 빈 정의가 이름에 의한 자동연결로 설정되어있고 master 프로퍼티가 있다면 (setMaster(..) 메서드가 있다는 의미이다.) 스프링은 master라는 이름의 빈 정의를 찾아서 프로퍼티를 설정하는 데 사용한다.

byType

컨테이너 내에서 프로퍼티 타입과 정확히 일치하는 하나의 빈이 있다면 프로퍼티를 자동연결한다. 하나 이상의 빈이 존재한다면 해당 빈에 대해서는 byType 자동연결을 사용하지 말아야 한다는 것을 말해주는 fatal 예외가 던져진다. 일치하는 빈이 하나도 없다면 아무 일도 일어나지 않는다. 프로퍼티를 설정되지 않는다.

constructor

byType과 유사하지만 생성자 아규먼트에 적용된다. 컨테이너 내에서 생성자 아규먼트의 타입과 정확히 일치하는 빈이 없다면 fatal 오류가 발생한다.


byType 자동연결 모드나 constructor 자동연결 모드를 사용해서 배열이나 타입이 있는 컬렉션을 연결할 수 있다. 이러한 경우에 컨테이너 내에서 타입이 일치할 것으로 기대되는 모든 자동연결 후보들은 의존성을 만족하기 위해 제공된다. 기대하는 키 타입이 String이라면 강타입의 맵과 자동연결할 수 있다. 자동연결된 맵의 값들은 일치하기를 기대하는 타입의 모든 빈 인스턴스로 이루어질 것이고 맵의 키들에는 대응되는 빈의 이름이 담겨있다.

자동 연결이 수행된 후에 자동연결과 의존성 확인을 합칠 수 있다.


4.4.5.1 자동연결의 한계와 단점들

자동연결은 프로젝트 전체적으로 사용할 때 최상으로 잘 동작한다. 자동연결을 일반적으로 사용하지 않는다면 한두 개의 빈 정의만을 연결하려고 자동연결을 사용하는 개발자에게 혼동을 줄 것이다.

자동연결의 한계와 단점들을 보자.
  • property와 constructor-arg 설정의 명시적인 의존성은 항상 자동연결을 오버라이드한다. 프리미티브 타입, Strings, Classes 같은 소위 간단한 프로퍼티들(간단한 프로퍼티의 배열도 포함)은 자동연결할 수 없다. 이 한계는 의도적인 디자인이다.
  • 자동연결은 명시적인 연결보다는 정확하지 않다. 앞에서 본 표처럼 스프링은 기대하지 않을 결과가 일어날 수도 있는 애매모호한 경우에 추측하는 것을 피하기 위해 조심하지만 스프링이 관리하는 객체사이의 관계는 더이상 명백하게 문서화되지 않는다.
  • 연결 정보는 스프링 컨테이너에서 문서를 생성하는 것 같은 도구에서는 이용할 수 없을 것이다.
  • 컨테이너 내에서 다수의 빈 정의들은 자동연결되는 setter 메서드나 생성자 아규먼트로 지정된 타입과 일치하는 것을 찾아낼 것이다. 배열, 컬렉션, 맵에 대해서 이는 필연적인 문제는 아니다. 하지만 단일 값을 기대하는 의존성에 대해서 이 애매모호함은 임의로 처리되지 않는다. 유일한 빈 정의를 이용할 수 없다면 예외가 던져진다.
후자의 경우에 여러 가지 선택사항이 있다.

  • 명백한 연결을 사용하고 자동연결을 사용하지 않는다.
  • 다음 섹션에서 설명할 autowire-candidate 속성에 false를 설정해서 빈 정의의 자동연결을 피한다.
  • <bean/> 요소의 primary 속성을 true로 설정함으로써 단일 빈 정의를 주요한 후보로 지정한다.
  • 자바 5 이상의 버전을 사용한다면 Section 4.9, “Annotation-based container configuration”에서 설명했듯이 어노테이션 기반의 설정으로 더 포괄적인 제어를 구현한다
.
4.4.5.2 자동연결에서 빈 제외하기

per-bean 원리에 따라 자동연결에서 빈을 제외할 수 있다. 스프링 XML 형식에서 <bean/> 요소의 autowire-candidate 속성을 false로 설정한다. 컨테이너는 자동연결 인프라(@Autowired 같은 어노테이션 설정을 포함해서)가 지정한 빈 정의를 이용할 수 없게 한다.

빈 이름에 대한 패턴매칭에 기반해서 자동연결의 후보를 제한할 수도 있다. 최상위 레벨의 <beans/> 요소는 default-autowire-candidates 속성으로 하나 이상의 패턴을 갖는다. 예를 들어 Repository로 끝나는 이름을 가진 빈을 자동연결 후보에서 제외하려면 *Repository라는 값을 지정한다. 여러 패턴을 지정하려면 콤마로 구분한 리스트로 정의한다. 빈 정의에서 autowire-candidate 속성을 true나 false로 명시적으로 지정하면 항상 우선시되고 패턴패칭 규칙이 적용되지 않는다.

이러한 기법들은 자동연결로 다른 빈에 주입하면 안되는 빈에 유용한다. 이는 제외된 빈이 스스로 자동연결을 사용해서 설정할 수 없다는 것을 의미하지는 않는다. 더 적절히 말하면 빈 자신은 다른 빈에 자동연결하기 위한 후보가 아니다.


4.4.6 메서드 주입

대부분의 어플리케이션 시나리오에서 컨테이너 내의 대부분의 빈은 싱글톤이다. 싱글톤 빈이 다른 싱글톤 빈과 협력해야 하거나 싱글톤이 아닌 빈이 싱글톤이 아닌 다른 빈과 협력해야 할 때 보통 빈은 다른 빈의 프로퍼티로 정의해서 의존성을 다룬다. 빈의 라이프 사이클이 다를 때 한가지 문제점이 생긴다. 싱글톤 빈 A가 싱글톤이 아닌(프로토타입) 빈 B를 사용해야 한다고 가정해 보자. 아마 각 메서듣 호출은 A에서 발생한다. 컨테이너는 싱글톤 빈 A를 딱 한번만 생성하므로 프로퍼티를 설정할 기회도 딱 한번만 있다. 컨테이너는 빈 B가 필요할 때마다 빈 B의 새로운 인스턴스를 가진 빈 A를 제공할 수 없다.

해결책은 제어의 역전(inversion of control)보다 선행하는 것이다. ApplicationContextAware 인터페이스를 구현하거나 빈 A가 빈 B를 필요로 할 때마다 컨테이너가 빈 B의 인스턴스(보통은 new)를 요청하도록 getBean("B")을 호출하게 만듦으로써 빈 A가 컨테이너를 인지하도록 할 수 있다. 다음은 이러한 방법의 예이다.


// 어떤 프로세싱을 수행하기 위해 상태가 있는 커맨드 스타일의 클래스를 사용하는 클래스
package fiona.apple;

// 스프링 API 임포트
import org.springframework.beans.BeansException;
import org.springframework.context.Applicationcontext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

 private ApplicationContext applicationContext;

 public Object process(Map commandState) {
    // 적절한 Command의 새로운 인스턴스를 붙잡는다
    Command command = createCommand();
    // (원컨대 완전히 새로운) Command 인스턴스에 상태를 설정한다
    command.setState(commandState);
    return command.execute();
 }

 protected Command createCommand() {
    // 스프링 API 의존성에 주의해라!
    return this.applicationContext.getBean("command", Command.class);
 }

 public void setApplicationContext(ApplicationContext applicationContext)
                                                                  throws BeansException {
    this.applicationContext = applicationContext;
 }
}




앞의 예제는 비즈니스 코드가 스프링 프레임워크를 인지하고 있고 결합되어 있기 때문에 바람직하지 않다. 스프링 IoC 컨테이너에서 약간 고급 기능 중 하나인 메서드 주입은 깔끔한 방법으로 이러한 유즈케이스를 다룰 수 있다.

이 블로그 글에서 메서드 주입을 만든 동기에 대해서 더 자세히 읽을 수 있다.


4.4.6.1 메서드 주입 검색

메서드 주입 검색은 컨테이너에서 또 다른 이름있는 빈에 대해 검색 결과를 리턴하도록 컨테이너가 컨테이너가 관리하는 빈의메서드를 오버라이드한다. 이전 섹션에서 설명한 시나리오처럼 검색은 보통 프로토타입 빈과 관계된다. 스프링 프레임워크는 메서드를 오버라이드 하는 서브클래스를 동적으로 생성하기 위해 CGLIB 라이브러리에서 바이트코드 생성을 사용해서 이 메서드 주입을 구현한다.

Note
이 동적으로 서브클래스를 만드는 작업을 하려면 클래스패스에 CGLIB jar파일을 두어야 한다. 스프링 프레임워크가 서브클래스를 생성하는 클래스는 final이 될 수 없고 오버라이드 되는 메서드도 final이 될 수 없다. 또한 abstract 메서드가 있는 클래스를 테스트하려면 개발자가 직접 클래스의 서브클래스를 만들고 abstract 메서드의 스텁(stub) 구현을 제공해야 한다. 마지막으로 메서드 주입의 타겟이 되는 객체들은 직렬화 될 수 없다.


이전 코드 예제에서 CommandManager 클래스를 보면 스프링 컨테이너가 createCommand() 메서드의 구현을 동적으로 오버라이드한다는 것을 알 수 있다. 수정한 예제에서 볼 수 있듯이 CommandManager 클래스는 스프링에 대한 어떤 의존성도 갖지 않을 것이다.


package fiona.apple;

// 더는 스프링을 임포트하지 않는다! 

public abstract class CommandManager {

 public Object process(Object commandState) {
    // 적절한 Command의 새로운 인스턴스르 붙잡는다
    Command command = createCommand();
    // (원컨대 완전히 새로운) Command 인스턴스에 상태를 설정한다
    command.setState(commandState);
    return command.execute();
 }

  // 괜찮지만... 이 메서드의 구현은 어디에 있는가?
 protected abstract Command createCommand();
}




주입되는 메서드가 있는는 클라이언트 클래스에서(이 경우에는 CommandManager) 주입되는 메서드는 다음과 같은 형식의 시그니처여야 한다.


<public|protected> [abstract] <return-type> theMethodName(no-arguments);




메서드가 abstract이라면 동적으로 생성된 서브클래스가 메서드를 구현한다. abstract가 아니라면 동적으로 생성된 서브클래스는 원래의 클래스에서 정의된 구현 메서드를 오버라이드한다. 예를 들면 다음과 같다.


<!-- 상태가 있는 빈은 프로포타입으로 배포된다. (싱글톤이 아닙) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- 여기에 주입하는 의존성을 필요한만큼 적는다 -->
</bean>

<!-- commandProcessor는 statefulCommandHelper를 사용한다 -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>




이 빈은 command빈의 새로운 인스턴스가 필요할 때마다 자신의 createCommand() 메서드를 호출하는 commandManager이다. command 빈이 실제로 필요하다면 프로토타입으로 배포해야 한다. command 빈이 싱글톤으로 배포된다면 command 빈은 항상 같은 인스턴스가 리턴될 것이다.

Tip
호기심 많은 독자라면 ServiceLocatorFactoryBean (org.springframework.beans.factory.config 패키지에 있다)가 사용된다는 것을 찾아낼 것이다. ServiceLocatorFactoryBean에서 사용한 접근은 다른 유틸리티 클래스인 ObjectFactoryCreatingFactoryBean에서 사용한 접근과 유사하다. 하지만 ServiceLocatorFactoryBean는 스프링에 특화된 검색 인터페이스와는 반대되는 자신만의 검색 인터페이스를 지정할 수 있다. 이 클래스들에 대해서는 자바독을 참고하고 ServiceLocatorFactoryBean에 대한 추가적인 정보는 이 블로그 글을 참고해라.


4.4.6.2 임의의 메서드 교체

검색 메서드 주입보다 덜 유용한 메서드 주입의 형태는 관리하는 빈에서 임의의 메서드를 다른 메서드 구현으로 교체하는 것이다. 사용자들은 이 기능이 필요할 때까지 이 섹션의 남은 부분은 읽지 않아도 좋다.

XML 기반의 설정 메타데이터에서 배포된 빈에 대해 이미 존재하는 메서드 구현을 다른 메서드로 교체하기 위해 replaced-method 요소를 사용할 수 있다. 다음 클래스와 오버라이드 할 computeValue 메서드를 보자.


public class MyValueCalculator {

public String computeValue(String input) {
  // 다른 실제 코드...
}

// 다른 메서드들...

}




org.springframework.beans.factory.support.MethodReplacer 를 구현한 클래스는 새로운 메서드 정의를 제공한다.


/** MyValueCalculator에 존재하는 computeValue(String) 구현을 
  오버라이드하는데 사용된다는 것을 의미한다.
*/
public class ReplacementComputeValue implements MethodReplacer {

  public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
      // 입력값을 가져오고 작업을 수행하고 계산된 결과를 돌려준다
      String input = (String) args[0];
      ...
      return ...;
  }
}




원래의 클래스를 배포하고 메서드 오버라이드를 지정하는 빈 정의는 다음과 같을 것이다.


<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">

<!-- 임의의 메서드 교체 -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
  <arg-type>String</arg-type>
</replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>




오버라이드 되는 메서드의 메서드 시그니처를 가리키는 <replaced-method/> 요소내에서 다수의 <arg-type/> 요소를 사용할 수 있다. 아규먼트에 대한 시그니처는 메서드가 오버라이드 되고 클래스에 다수의 변수수가 존재할 때만 필요하다. 간편하게 아규먼트에 대한 스트링 타입은 완전히 정규화된 타입이름의 일부만 적어도 된다. 예를 들어 다음은 모든 java.lang.String과 매치된다.


java.lang.String
String
Str




대게 아규먼트의 수는 선택가능한 범위내에서 구별하기에 충분하기 때문에 이 단축키는 아규먼트의 타입과 매치되는 가장 짧은 문자열만 입력함으로써 타이핑의 양을 줄일 수 있다.
2012/03/03 02:17 2012/03/03 02:17