Outsider's Dev Story

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

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

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




4.13 LoadTimeWeaver 등록하기
스프링 2.5에서 도입된 context 네임스페이스는 load-time-weaver 요소를 제공한다.

<beans>

    <context:load-time-weaver/>

</beans>




XML기반 스프링 설정파일에 이 요소를 추가하면 ApplicationContext에 대한 스프링 LoadTimeWeaver가 활성화된다. 해당 ApplicationContext내의 어떤 빈도 LoadTimeWeaverAware를 구현할 수 있기 때문에 로드타임 위버(load-time weaver) 인스턴스에 대한 참조를 받는다. 이는 JPA 클래스 변환때문에 로드타임 위빙이 필요한 스프링의 JPA 지원을 함께 사용할 때 특히 유용하다. 더 자세한 내용은 LocalContainerEntityManagerFactoryBean Javadoc에 나와있다. AspectJ 로드타임 위빙에 대해서 더 알고 싶다면 Section 8.8.4, “Load-time weaving with AspectJ in the Spring Framework”를 봐라.




4.14 ApplicationContext의 추가적인 기능들
이 챕터의 도입부에서 얘기했듯이 org.springframework.beans.factory 패키지는 프로그래밍적인 방법을 포함해서 빈을 관리하고 조작하는 기본적인 기능을 제공한다. org.springframework.context 패키지는 추가적으로 더 어플리케이션 프레임워크 지향적인 스타일로 추가적인 기능을 제공하려고 다른 인터페이스를 확장하는 BeanFactory 인터페이스를 확장한 ApplicationContext를 추가한다. 완전히 선언적인 방법으로 ApplicationContext를 사용하는 많은 사람들은 프로그래밍으로는 생성조차 하지 않고 대신에 J2EE 웹 어플리케이션의 일반적인 시작과정의 일부로 ApplicationContext를 자동으로 인스턴스화 하려고 ContextLoader같은 지원 클래스에 의존한다.

좀 더 프레임워크 지향적인 스타일로 BeanFactory의 기능을 강화하기 위해 context 패키지는 다음 기능도 제공한다.

  • MessageSource 인터페이스로 i18n 스타일의 메세지 접근
  • ResourceLoader 인터페이스로 URL이나 파일같은 리소스 접근
  • ApplicationEventPublisher 인터페이스의 사용으로 ApplicationListener 인터페이스를 구현한 빈에 이벤트 발행(Event publication)
  • HierarchicalBeanFactory 인터페이스로 어플리케이션의 웹 계층같은 특정 계층에 각각 집중하는 여러 가지 (계층적인) 컨텍스트의 로딩

4.14.1 MessageSource를 사용한 국제화
ApplicationContext 인터페이스는 MessageSource라는 인터페이스를 확장하므로 국제화 (i18n) 기능을 제공한다. 스프링은 메시지를 계층적으로 처리할 수 있는 HierarchicalMessageSource 인터페이스도 제공한다. 이 인터페이스들은 스프링이 메세지를 처리한 결과에 기반한 기초를 제공한다. 이 인터페이스들에서 정의된 메서드들은 다음을 포함한다.

  • String getMessage(String code, Object[] args, String default, Locale loc): MessageSource에서 메시지를 획득하기 위해 사용하는 기본 메서드. 지정한 로케일(locale)에 대한 메시지를 찾지 못하면 기본 메시지를 사용한다. 전달한 아규먼트는 표준 라이브러리에서 제공하는 MessageFormat 기능을 사용해서 대체값이 된다.
  • String getMessage(String code, Object[] args, Locale loc): 본질적으로는 앞의 메서드와 같지만 한가지가 다르다: 기본 메시지를 지정할 수 없다. 메시지를 찾을 수 없다면 NoSuchMessageException를 던진다.
  • String getMessage(MessageSourceResolvable resolvable, Locale locale): 앞의 메서드에서 사용했던 모든 프로퍼티들은 이 메서드에서 사용할 수 있는 MessageSourceResolvable라는 클래스로도 감싸진다.
ApplicationContext가 로드되면 자동으로 컨텍스트에서 정의된 MessageSource 빈을 찾는다. 해당 빈을 찾으면 메시지 소스에 위임한 앞의 메서드들을 모두 호출한다. 메세지 소스가 없다면 ApplicationContext는 부모가 같은 이름의 빈을 가지고 있는지 찾아본다. 부모가 같은 이름의 빈을 가지고 있으면 그 빈을 MessageSource로 사용한다. ApplicationContext가 어떤 메시지 소스도 찾을 수 없다면 위에서 정의한 메서스에 대한 호출을 받을 수 있도록 비어있는 DelegatingMessageSource를 인스턴스화 한다.

스프링은 2개의 MessageSource 구현체를 제공하는데 ResourceBundleMessageSource와 StaticMessageSource이다. 둘다 중첩된 메세지를 처리하기 위해 HierarchicalMessageSource를 구현한다. StaticMessageSource는 드물게 사용하지만 프로그래밍적인 방법으로 소스에 메시지를 추가하는 방법을 제공한다. ResourceBundleMessageSource는 다음 예제에 나와있다.

<beans>
<bean id="messageSource"
     class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basenames">
   <list>
     <value>format</value>
     <value>exceptions</value>
     <value>windows</value>
   </list>
  </property>
</bean>
</beans>

예제에서 클래스패스에 format, exceptions, windows라는 3개의 리소스 번들이 정의되어 있다고 가정한다. 메시지를 처리하는 어떤 요청이라도 리소스번들을 통해 메시지를 처리하는 JDK 표준방법으로 다룰 것이다. 예제의 의도를 위해 위의 리소스 번들 파일 중 2개의 내용이 다음과 같다고 가정한다.

# in format.properties
message=Alligators rock!





# in exceptions.properties
argument.required=The '{0}' argument is required.



MessageSource 기능을 실행하는 프로그램은 다음 예제에 나와있다. 모든 ApplicationContext 구현체는 MessageSource의 구현체이기고 하므로 MessageSource 인터페이스로 캐스팅 될 수 있다는 것을 기억해라.

public static void main(String[] args) {
  MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
  String message = resources.getMessage("message", null, "Default", null);
  System.out.println(message);
}



위 프로그램은 다음과 같은 아웃풋이 될 것이다.

Alligators rock!



그래서 요약하자면 MessageSource는 클래스패스 루트경로에 있는 beans.xml 파일에 정의되어 있다. messageSource 빈 정의는 basenames 프로퍼티에서 다수의 리소스 번들을 참조한다. basenames 프로퍼티의 리스트로 전달한 3개의 파일은 클래스패스 루트경로에 있는 파일들이고 각각 format.properties, exceptions.properties, windows.properties라는 이름이 된다.

다음 예제는 메시지 검색을 위해 아규먼트를 전달하는 것을 보여준다. 이러한 아규먼트들을 문자열로 변환해서 검색 메시지의 플레이스홀더에 삽입할 것이다.

<beans>

  <!-- 이 MessageSource는 웹 어플리케이션에서 익숙하다 -->
  <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
     <property name="basename" value="test-messages"/>
  </bean>

  <!-- 위의 MessageSource를 이 POJO에 주입한다 -->
  <bean id="example" class="com.foo.Example">
     <property name="messages" ref="messageSource"/>
  </bean>

</beans>


public class Example {

  private MessageSource messages;

  public void setMessages(MessageSource messages) {
      this.messages = messages;
  }

  public void execute() {
      String message = this.messages.getMessage("argument.required",
          new Object [] {"userDao"}, "Required", null);
      System.out.println(message);
  }

}



execute()를 호출의 결과는 다음과 같은 아웃풋이 될 것이다.

The userDao argument is required.



국제화 (i18n)와 관련해서 스프링의 여러가지 MessageResource 구현체는 표준 JDK ResourceBundle과 같은 로케일(locale) 처리방법과 장애복구 규칙을 따른다. 간단히 말해서 앞에서 정의한 messageSource 예제에서 영국 (en-GB) 로케일로 메시지를 처리하려면 format_en_GB.properties, exceptions_en_GB.properties, windows_en_GB.properties파일을 각각 생성해야 한다.

보통 로케일 처리는 어플리케이션 환경에 의해 관리된다. 이 예제에서 (영국) 메시지가 처리되어야 하는 로케일은 수정으로 지정한다.

# exceptions_en_GB.properties 파일
argument.required=Ebagum lad, the '{0}' argument is required, I say, required.


public static void main(final String[] args) {
  MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
  String message = resources.getMessage("argument.required",
      new Object [] {"userDao"}, "Required", Locale.UK);
  System.out.println(message);
}



위의 프로그램을 실행하면 다음과 같은 아웃풋이 될 것이다.

Ebagum lad, the 'userDao' argument is required, I say, required.


정의된 MessageSource에 대한 참조를 얻으려고 MessageSourceAware 인터페이스를 사용할 수도 있다. MessageSourceAware 인터페이스를 구현한 ApplicationContext에서 정의된 어떤 빈이라도 빈이 생성되고 설정될 때 어플리케이션 컨텍스트의 MessageSource와 함께 주입된다.

Note
ResourceBundleMessageSource에 대한 대안으로 스프링은 ReloadableResourceBundleMessageSource 클래스를 제공한다. 이 클래스는 같은 번들 파일 형식을 지원하지만 ResourceBundleMessageSource 구현체에 기반한 표준 JDK보다 더 유연하다. 특히 이 클래스는 어떤 스프링 리소스 위치(단지 클래스 패스가 아니라)에서도 파일을 읽을 수 있고 번들 프로퍼티파일의 핫 리로딩을 지원한다.(동시에 중간에서 이들을 캐싱한다.) 더 자세한 내용은 ReloadableResourceBundleMessageSource 자바독을 확인해 봐라.


4.14.2 표준 이벤트와 커스텀 이벤트
ApplicationContext의 이벤트 핸들링은 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 통해서 제공한다. ApplicationListener인터페이스를 구현한 빈이 컨텍스트에 배포되면 ApplicationContext에 퍼블리싱되는 것을 ApplicationEvent가 얻을 때마다 빈에 통지한다. 본질적으로 이는 표준 Observer 디자인 패턴이다. 스프링은 다음과 같은 표준 이벤트를 제공한다.

Table 4.7. 내장 이벤트
이벤트설명
ContextRefreshedEventApplicationContext가 초기화되거나 갱신될 때 퍼블리싱된다. 예를 들어 ConfigurableApplicationContext 인터페이스의 refresh() 메서드를 사용하는 경우다. 여기서 "초기화"는 모든 빈이 로드되고 후처리자(post-processor) 빈을 탐지해서 활성화하고 싱글톤이 미리 인스턴스화되고 ApplicationContext 객체를 사용할 준비가 되었음을 의미한다. 컨텍스트가 닫히지 않는 한 여러 번 갱신될 수 있고 선택된 ApplicationContext는 사실 "핫" 리프레시등을 지원한다. 예를 들면 XmlWebApplicationContext 핫 리프레시를 지원하지만 GenericApplicationContext는 지원하지 않는다.
ContextStartedEventConfigurableApplicationContext 인터페이스의 start() 메서드를 사용해서 ApplicationContext이 시작될 때 퍼블리싱된다. 여기서 "시작한다"는 의미는 모든 Lifecycle 빈이 명시적으로 시작 신호를 받는다는 말이다. 이 신호는 보통 명시적인 멈춤 후에 빈을 재시작하는 데 사용하지만 자동 시작이 설정되지 않은 컴포넌트들을 시작하는 데도 사용할 수 있다. 초기화 때 시작되지 않은 컴포넌트들이 그 예이다.
ContextStoppedEventConfigurableApplicationContext 인터페이스의 stop() 메서드를 사용해서 ApplicationContext이 멈췄을 때 퍼블리싱된다. 여기서 "멈춘다"는 의미는 모든 Lifecycle 빈이 명시적인 멈춤 신호를 받는다는 뜻이다. 멈춰진 컨텍스트는 start() 호출로 재시작할 수 있다.
ContextClosedEventConfigurableApplicationContext의 close() 메서드를 사용해서 ApplicationContext이 닫혔을 때 퍼블리싱된다. 여기서 "닫힌다"는 의미는 모든 싱글톤 빈이 파괴되었다는 의미이다. 닫혀진 컨텍스트는 생명의 끝에 도달한다. 이 컨텍스트는 갱신하거나 재시작할 수 없다.
RequestHandledEventHTTP 요청이 서비스되는 모든 빈에 전하는 웹에 특정화된(web-specific) 이벤트. 이 이벤트는 요청이 완료된 후에 퍼블리싱된다. 이 이벤트는 스프링의 DispatcherServlet을 사용하는 웹 어플리케이션에만 적용할 수 있다.






























자신만의 커스텀 이벤트를 생성해서 퍼블리싱할 수도 있다. 이 예제는 스프링의 ApplicationEvent 기반 클래스를 확장하는 간단한 클래스를 보여준다.

public class BlackListEvent extends ApplicationEvent {
  private final String address;
  private final String test;

  public BlackListEvent(Object source, String address, String test) {
      super(source);
      this.address = address;
      this.test = test;
  }

  // 접근자와 그 외 메서드들...
}



커스텀 ApplicationEvent를 퍼블리싱하려면 ApplicationEventPublisher의 publishEvent() 메서드를 호출해라. 보통 이는 ApplicationEventPublisherAware를 구현한 클래스를 생성하고 스프링 빈으로 등록함으로써 완료된다. 다음 예제는 이러한 클래스를 보여준다.

public class EmailService implements ApplicationEventPublisherAware {

  private List<String> blackList;
  private ApplicationEventPublisher publisher;

  public void setBlackList(List<String> blackList) {
      this.blackList = blackList;
  }

  public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
      this.publisher = publisher;
  }

  public void sendEmail(String address, String text) {
      if (blackList.contains(address)) {
          BlackListEvent event = new BlackListEvent(this, address, text);
          publisher.publishEvent(event);
          return;
      }
      // 이메일 보내기...
  }
}


설정타임시에 스프링 컨테이너는 ApplicationEventPublisherAware를 구현한 EmailService를 탐지할 것이고 자동적으로 setApplicationEventPublisher()를 호출할 것이다. 실제로는 전달한 파라미터는 스프링 컨테이너 자체가 될 것이다. ApplicationEventPublisher 인터페이스를 통해서 어플리케이션 컨텍스트와 간단히 상호작용한다.

커스텀 ApplicationEvent를 받으려면 ApplicationListener를 구현한 클래스를 생성하고 스프링 빈으로 등록해라. 다음 에제는 이러한 클래스를 보여준다.

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

  private String notificationAddress;

  public void setNotificationAddress(String notificationAddress) {
      this.notificationAddress = notificationAddress;
  }

  public void onApplicationEvent(BlackListEvent event) {
        // notificationAddress를 통해서 적절한 같은 그룹에 알린다...
  }
}


ApplicationListener는 일반적으로 커스텀 이벤트의 타입인 BlackListEvent으로 파라미터화된다. 이는 onApplicationEvent() 메서드가 타입세이프할 수 있고 다운캐스팅이 필요한 경우를 피한다는 의미이다. 필요한 만큼의 많은 이벤트 리스터를 등록할 수 있지만 기본적으로 이벤트 리스너는 동기적으로 이벤트를 받는다. 이는 모든 리스너가 이벤트 처리를 완료할 때까지 publishEvent()가 블락된다는 의미이다. 이 동기방식과 싱글쓰레드 접근의 한가지 이점은 리스너가 이벤트를 받을 때 트랜잭션 컨텍스트를 사용할 수 있으면 퍼블리셔의 트랜젝션 컨텍스트내에서 수행된다는 것이다. 이벤트 발행에 대한 또다른 전략이 필요하다면 스프링의 ApplicationEventMulticaster 인터페이스에 대한 JavaDoc을 참고해라.

다음 예제는 위의 클래스 각각을 등록하고 설정하는 빈 정의를 보여준다.

<bean id="emailService" class="example.EmailService">
  <property name="blackList">
      <list>
          <value>known.spammer@example.org</value>
          <value>known.hacker@example.org</value>
          <value>john.doe@example.org</value>
      </list>
  </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
  <property name="notificationAddress" value="blacklist@example.org"/>
</bean>



emailService빈의 sendEmail()를 호출할 때 블랙리스트에 있는 이메일이 있다면 BlackListEvent 타입의 커스텀 이벤트가 퍼블리시된다. blackListNotifier 빈이 ApplicationListener으로 등록되므로 BlackListEvent를 받아서 적당한 같은 그룹에게 알릴 수 있다.

Note
스프링의 이벤트 메카니즘은 같은 어플리케이션 컨텍스트내의 스프링 빈간의 간단한 통신을 위해 디자인되었다. 하지만 더 복잡한 엔터프리이즈 통합이 필요하면 따로 관리되는 Spring Integration 프로젝트가 잘 알려진 스프링 프로그래밍 모델에 기반해서 경량이고 패턴지향(pattern-oriented)이고 이벤트기반의 아키텍쳐를 구성하는데 대한 완전한 지원을 제공한다.


4.14.3 저수준 리소스에 편리하게 접근하기
어플리케이션 컨텍스트의 가장 바람직한 사용과 이해를 위해서는 Chapter 5, Resources 챕터에서 설명했듯이 사용자들은 보통 스프링의 Resource 추상화에 스스로 익숙해져야 한다.

어플리케이션 컨텍스트는 Resource를 로드하는데 사용할 수 있는 ResourceLoader이다. Resource는 본질적으로 JDK java.net.URL 클래스의 더 많은 기능이 있는 리치(rich) 버전이다. 사실 Resource 구현체는 적절한 위치에서 java.net.URL의 인스턴스를 감싼다. Resource는 투명한 방법으로 클래스패스, 파일시스템 위치, 표준 URL로 나타낼 수 있는 위치 등 거의 대부분의 위치에서 저수준 리소스들을 획득할 수 있다. 리소스 위치 문자열이 어떤 특별한 접두사가 없는 간단한 경로라면 이라한 리소스의 위치는 실제 어플리케이션 타입을 지정하고 사용한다.

어플리케이션 컨텍스트에 배포한 빈이 특별한 콜백 인터페이스인 ResourceLoaderAware를 구현하도록 설정할 수 있다. 이렇게 함으로써 ResourceLoader처럼 어플리케이션 컨텍스트 자체에 전달된 것을 초기화 시에 자동적으로 다시 호출하게 한다. 정적 리소스에 접근하도록 Resource 타입의 프로퍼티를 노출할 수도 있다. 정적 리소스들은 다른 프로퍼티처럼 주입될 것이다. 이러한 Resource 프로퍼티들을 간단한 경로 문자열로 지정할 수도 있고 빈이 배포될 때 해당 문자열을 실제 Resource 객체로 변환하도록 하기 위해 컨텍스트가 자동으로 등록하는 특별한 JavaBean PropertyEditor에 의존할 수도 있다.

ApplicationContext 생성자에 전달한 위치 경로(location path)는 실제로 리소스 문자열이며 간단한 형식으로 적절하게 특정정한 컨텍스트 구현체처럼 다뤄진다. ClassPathXmlApplicationContext는 간단한 위치 경로를 클래스패스 위치처럼 다룬다. 실제 컨텍스트 타입에 상관없이 클래스패스나 URL에서 정의를 강제로 로드하도록 특별한 접두사를 가진 위치 경로(리소스 문자열)를 사용할 수도 있다.


4.14.4 웹 어플리케이션에 펀리한 ApplicationContext 인스턴스화
ContextLoader등을 사용해서 선언적으로 ApplicationContext 인스턴스를 생성할 수 있다. 물론 ApplicationContext 구현체중 하나를 사용해서 프로그래밍적으로 생성할 수도 있다.

ContextLoader 메카니즘은 ContextLoaderListener와 ContextLoaderServlet 두 가지에서 왔다. 이 둘은 같은 기능이지만 서블릿 2.3 컨테이너에서 신뢰할 수 없는 리스너 버전이 다르다. 서블릿 2.4 명세에서 서블릿 컨텍스트 리스너는 웹 어플리케이션의 서블릿 컨텍스트가 생성되자 마자 즉시 실행되어야 하고 첫번째 요청을 서비스할 수 있어야 한다.(그리고 서블릿 컨텍스트가 셧다운되어야 한다는 부분도 있다.) 이러한 서블릿 컨텍스트 리스너는 스프링 ApplicationContext를 초기화하기에 이상인 곳이다. 모든 것이 같다면 아마도 ContextLoaderListener이 더 나을 것이다. 호환성에 대한 더 자세한 정보는 ContextLoaderServlet 자바독을 봐라.

다음과 같이 ContextLoaderListener를 사용해서 ApplicationContext를 등록할 수 있다.

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 또는 위의 리스너 대신 ContextLoaderServlet를 사용한다
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->



리스너는 contextConfigLocation 파라미터를 검사한다. 파라미터가 존재하지 않으면 리스너는 기본값인 /WEB-INF/applicationContext.xml를 사용한다. 파라미터가 존재하면 리스너는 미리 정의된 구분자 (콤마, 세미콜론, 공백)로 문자열을 분리하고 어플리케이션 컨텍스트를 검색해야하는 위치로 이 값을 사용한다. Ant 스타일 경로 패턴도 잘 지원한다. 예제들은 모두 "Context.xml"로 끝나는 파일명을 가지고 "WEB-INF" 디렉토리에 있는 /WEB-INF/*Context.xml와 "WEB-INF"의 하위디렉토리에 있는 이러한 모든 파일인 /WEB-INF/**/*Context.xml 이다.

ContextLoaderListener 대신에 ContextLoaderServlet를 사용할 수 있다. 서블릿은 마치 리스너가 하는 것처럼 contextConfigLocation 파라미터를 사용한다.


4.14.5 J2EE RAR 파일처럼 스프링 ApplicationContext 배포하기
스프링 2.5부터는 컨텍스트와 필요한 모든 빈 클래스와 라이브러리 JAR 파일들을 J2EE RAR 배포유닛으로 은닉화해서 RAR 파일로 스프링 ApplicationContext를 배포하는 것이 가능하다. 이는 단독으로 어클리케이션 컨텍스트를 실행한 것과 동일하며 단지 J2EE 환경에 호스팅해서 J2EE 서버 시설들에 접근할 수 있게 된 것이다. RAR 배포는 헤드없이 WAR 파일을 배포하는 시나리오보다 더 자연스러운 대안이다. 헤드없는 WAR 파일이라는 것은 J2EE 환경에서 스프링 어플리케이션컨텍스트를 시작하는데만 사용하는 HTTP 진입점이 없는 WAR 파일을 말한다.

RAR 배포는 HTTP 진입점이 필요없는 어플리케이션 컨텍스트의 이상형이라기 보다는 메시지 앤드포인트와 스케쥴된 잡들로만 이루어져있다. 이러한 컨텍스트의 빈들은 JTA 트랜잭션 매니저와 JNDI에 바인딩된 JDBC DataSource와 JMS ConnectionFactory 인스턴스같은 어플리케이션 서버 리소스들을 사용할 수 있고 플랫폼의 JMX서버와 함께 등록할 수도 있다. - 모든 것은 스프링의 표준 트랜잭션 관리와 JNDI, JMS 지원 부분을 통해서 이루어진다. 어플리케이션 컴포넌트는 스프링의 TaskExecutor 추상화로 어플리케이션 서버의 JCA WorkManager와 상호작용도 할 수 있다.

RAR 배포와 관련한 설정에 대한 자세한 내용은 SpringContextResourceAdapter 클래스의 JavaDoc을 확인해라.

J2EE RAR파일로 스프링 ApplicationContext을 간단히 배포하려면: 모든 어플리케이션 클래스들을 표준 JAR 파일과는 다른 확장자인 RAR 파일로 패키징한다. RAR 아카이브의 루트에 필요한 라이브러리의 JAR를 모두 추가한다. "META-INF/ra.xml" 배포 디스크립터(SpringContextResourceAdapter의 JavaDoc에 나온 것처럼)와 대응되는 스프링 XML 빈 정의 파일(보통 "META-INF/applicationContext.xml")을 추가하고 어플리케이션 서버의 배포 디렉토리에 최종 RAR 파일을 둔다.

Note
이러한 RAR 배포 유닛들은 보통 독립적이다. 컴포넌트를 외부로 노출하지 않으며 심지어 같은 어플리케이션의 다른 모듈에도 노출하지 않는다. RAR 기반 ApplicationContext와의 상호작용은 보통 다른 모듈과 공유하는 JMS 목적지(destination)를 통해서 발생한다. 예를 들어 RAR 기반 ApplicationContext는 잡(job)을 스케쥴링하거나 파일 시스템의 새로운 파일에 반응하는 등의 일도 할 수 있다. 외부의 동기적인 접근을 허용하려면 예를 들어 같은 머신의 다른 어플리케이션 모듈이 사용할 RMI 엔드포인트를 익스포트할 수 있다.




4.15 BeanFactory
BeanFactory는 스프링 IoC 기능의 근간을 이루는 원리를 제공하지만 다른 서드파티 프레임워크와의 통합에서만 직접적으로 사용되고 대부분의 스프링 유저들에게는 이제 전적으로 역사적인 것이다. BeanFactory와 BeanFactoryAware, InitializingBean, DisposableBean같은 관련 인터페이스들은 스프링과 통합된 많은 수의 서드파티 프레임워크들과의 하위호환성 때문에 여전히 스프링에 존재한다. 종종 JDK 1.4 호환성을 유지하거나 JSR-250에 대한 의존성을 없애기 위해 @PostConstruct나 @PreDestroy같은 더 최신 기능을 사용할 수 없는 서드파티 컴포넌트들이 있다.

이 섹션에서는 BeanFactory와 ApplicationContext의 차이점에 대한 추가적인 배경을 설명하고 전형적인 싱글톤 검색으로 직접 IoC 컨테이너에 어떻게 접근하는 지를 설명한다.


4.15.1 BeanFactory냐? ApplicationContext냐?
ApplicationContext을 사용하지 않을 좋은 이유가 있는 것이 아니라면 ApplicationContext을 사용해라.

ApplicationContext가 BeanFactory의 모든 기능을 포함하고 있기 때문에 메모리 소비가 중대한 문제가 될 수 있거나 조금 많은 킬로바이트가 차이를 만들 수 있는 Applet같은 몇가지 상황을 제외하고는 보통 BeanFactory보다 ApplicationContext를 더 권장한다. 하지만 대부분의 전형적인 엔터프라이즈 어플리케이션과 시스템에서는 사용하려는 것이 바로 ApplicationContext이다. 스프링 2.0이나 그 이상의 버전에서는 BeanPostProcessor 확장점을 많이 사용하게 된다.(프록싱하려는 등등) 평이한 BeanFactory만 사용한다면 트랜잭션이나 AOP같은 꽤 많은 지원을 사용할 수 없을 것이다. 최소한 개발자가 추가적인 어떤 작업을 해야한다. 이 상황은 설정에는 실제로 잘못된 곳이 없기 때문에 혼란스러울 수 있다.

다음 표는 BeanFactory와 ApplicationContext 인터페이스와 구현체가 제공하는 기능 목록이다.

Table 4.8. 기능 매트릭스
기능BeanFactory
ApplicationContext
빈 인스턴스화/연결YesYes
자동 BeanPostProcessor 등록NoYes
자동 BeanFactoryPostProcessor  등록
NoYes
편리한 MessageSource 접근 (i18n에 대한)No
Yes
ApplicationEvent 발행NoYes









BeanFactory 구현체와 함께 빈 후처리자(bean post-processor)를 명시적으로 등록하려면 다음과 같은 코드를 반드시 작성해야 한다.

ConfigurableBeanFactory factory = new XmlBeanFactory(...);

// 필요한 BeanPostProcessor 인스턴스를 등록한다
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);

// 팩토리를 사용해서 시작한다


BeanFactory를 사용할 때 BeanFactoryPostProcessor를 명시적으로 등록하려면 다음과 같은 코드를 반드시 작성해야 한다.

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));

// Properties 파일에서 몇몇 프로퍼티 값들을 가져온다
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// 이제 실제로 교체한다
cfg.postProcessBeanFactory(factory);


아주 대다수의 스프링을 사용하는 어플리케이션에서 다양한 ApplicationContext 구현체들은 위의 평이한 BeanFactory 구현체를 선호하기 때문에 두 경우의 명시적인 등록과정은 편리하지 않다. 특히 BeanFactoryPostProcessors와 BeanPostProcessors를 사용할 때 더욱 그렇다. 이러한 메카니즘은 프로퍼티 플레이스홀더 교체와 AOP같은 중요한 기능을 구현한다.


4.15.2 딱 붙은 코드(Glue code)와 나쁜 싱글톤
스프링 IoC 컨테이너가 서비스하는 코드와 생성될 때 컨테이너가 의존성을 제공하는 코드, 컨테이너가 인지하지 못하는 코드는 의존성 주입(DI) 스타일로 대부분의 어플리케이션을 작성하는 것이 가장 좋다. 하지만 때로는 다른 코드와 함께 묶을 필요가 있어서 코드의 딱 붙은 작은 레이어에는 때로 스프링 IoC 컨테이너에 접근하는 싱글톤(또는 quasi-singleton) 스타일이 필요하다. 예를 들어 서드파티 코드는 스프링 IoC 컨테이너에서 새로운 객체들을 얻는 능력없이 직접 새로운 객체들을 생성하려고 할 수도 있다.(Class.forName() 스타일) 서드파티 코드가 생성한 객체가 작은 스텁이나 프록시이면서 실제 객체를 얻는 것을 위임하려고 스프링 IoC 컨테이너에 접근하는 싱글톤 스타일을 사용한다면 제어의 역전은 여전히 대부분의 코드를 획득한다. 그러므로 대부분의 코드는 여전히 컨테이너를 알지 못하거나 어떻게 접근해야 하는지 모르고 모든 이득을 보장한 채로 다른 코드와 커플링이 없다. EJB는 평이한 자바 구현 객체에 위임하고, 스프링 IoC 컨테이서 획득하려고 이 스텁/프록시 접근을 사용할 것이다. 스프링 IoC 컨테이너 자체는 이상적으로 싱글톤이 되지 않기 때문에 각 빈이 자신의 싱글톤이 아닌 스프링 IoC 컨테이너를 사용하는 빈에서는 메모리 사용이나 초기화 시간 면에서 비현실적이 될 수 있다. (하이버네이트 SessionFactory같은 스프링 IoC 컨테이너의 빈을 사용할 때)

EJB 2.1 환경에서나 WAR 파일을 넘어서 WebApplicationContexts에 부모로 단일 ApplicationContext를 공유하고 싶을 때 서비스 로케이터 스타일로 어플리케이션 컨텍스트 검색하는 것은 때로 공유된 스프링이 관리하는 컨포넌트에 접근하기 위한 유일한 선택사항이다. 이 경우에 이 스프링 소스 팀블로그 글에서 설명한 ContextSingletonBeanFactoryLocator 로케이터 유틸리티 클래스를 사용해야 한다.
2012/05/15 01:57 2012/05/15 01:57