Bean과 Container의 확장

Bean Scope

Spring Framework에서 지원하는 5가지 Scope에 따라 Bean의 인스턴스 생성 메커니즘이 결정된다. 서비스 개발 시에는 web-based application에서 사용되는 scope인 request, session, global session은 제외하고 서비스 Scope은 서비스의 설계, 개발 단계에서 결정하기 어려우므로, 기본적으로는 Default Scope인 Singleton으로 개발하는 것이 좋다. 또한 추후 해당 서비스의 성격에 따라 Scope을 정의하는 것이 좋다.
  • <bean/> 요소의 scope 속성 설정
  • 속성
    설명
    singleton [기본 설정] Spring IoC Container 내에서 Bean 정의 당 하나의 Bean 객체 생성
    prototype 매번 같은 Type의 새로운 Bean 객체 생성
    request WebApplicationContext 유형의 Container 사용 시, Http request 당 하나의 Bean 객체 생성
    session WebApplicationContext 유형의 Container 사용 시, Http session 당 하나의 Bean 객체 생성
    globalSession WebApplicationContext 유형의 Container 사용 시, portlet context 내에서만 유효하며 global Http session 당 하나의 Bean 객체 생성
    - 신규로 custom scope 정의가 가능하다.

    Singleton


    Singleton Scope은 기본 Scope으로 여러 개의 요청에 대해 하나의 Bean 인스턴스를 생성하여 제공한다. 단, Client Request마다 유지해야 하는 Data가 있다면, Singleton Scope의 서비스는 적합하지 않다. 다음은 Singleton Scope의 속성 정의 예시이다.
    <bean id="userService" class="com.sds.emp.user.services.impl.UserServiceImpl" scope="singleton”>
         <property name="userDAO" ref="userDAO" />
    </bean>
    <bean id="userDAO" class="com.sds.emp.user.services.impl.UserDAO”>
    중략…
    </bean>
    위와 같이 singleton scope을 정의 할 수 있지만 scope기본 설정값이 singleton이므로 작성할 필요가 없다.

    Prototype

    Prototype Scope은 요청시마다 Bean 인스턴스를 생성하여 제공한다. 또한 여러 Client가 동시에 한 Bean 인스턴스에 접근 할 수 없다. 다음은 Prototype Scope의 속성 정의 예시이다.
    <bean id="userService" class="com.sds.emp.user.services.impl.UserServiceImpl" scope="prototype”>
         <property name="userDAO" ref="userDAO" />
    </bean>

    ※ 일반적으로 인스턴스의 Singleton 여부를 판단하기 위해서 전역변수가 존재하지 않은 인스턴스의 경우에는 Singleton, 전역변수가 존재하는 경우에는 Prototype이 사용된다. 그러나 앞에서도 보았듯이 해당 변수가 read-only인지 writable 가능한지에 따라서 이 같은 구분은 변경될 수 있다. 따라서 인스턴스를 Singleton으로 생성할지 Prototype으로 생성할지에 대한 여부에 대해서는 개발자들이 자바의 인스턴스가 메모리에서 어떻게 사용되는지를 이해하는 것이 가장 좋다.
    • Singleton

    • - Shared objects with no state
      - Shared object with read-only state
      - Shared object with shared state : 이 경우에는 Synchronization을 적절하게 사용하여 동시성을 제어하도록 해야 한다.
      - High throughput objects with writable state : 일반적으로 Object Pooling과 같은 기능을 사용하는 것을 예로 들 수 있다. 인스턴스를 생성하는데 많은 비용이 발생하거나 무수히 많은 인스턴스를 관리할 필요가 있는 경우에는 Object Pooling을 사용하고 이 Pooling을 사용하는 인스턴스는 Singleton으로 사용할 수 있다. 이 경우에도 Writable State에 변경이 발생할 때 Synchronization을 적절하게 사용해야 한다.
    • Prototype

    • - Objects with writable state
      - Objects with private state

    Other Scopes

    request, session, globalSession Scope 사용 시 주의 사항은 다음과 같다.
    • Web 기반의 ApplicationContext를 사용 시에만 이 Scope들을 사용할 수 있으며 그 외의 경우 사용하게 되면 IllegalStateException 발생한다.
    • web.xml에 ContextLoaderListener 혹은 ContextLoaderServlet 등록한다. 단, ContextLoaderListener는 Servlet 2.3을 지원하지 않으므로 Servlet 2.2를 지원하는 Servlet Container 사용 시에는 반드시 ContextLoaderServlet을 사용하여야 한다.
    • Bean 정의 시 <aop:scoped-proxy/> 함께 작성해야 한다.(아래의 예시 참고)
      userPreferences 빈은 scope이 session이지만 userManager의 scope이 singleton(default가 singleton)이기 때문에 문제가 발생한다. 매 세션 마다 새로운 객체를 만들어 줘야 하지만 저 세션 객체를 사용하는 빈의 생성을 한 번 밖에 안 하기 때문에 원하던 대로 동작하지 못한다. 따라서 매 세션 마다 새로운 객체를 만들어서 줄 Proxy를 만들기 위해서<aop:scoped-proxy/>를 사용한다.
     <!-- a HTTP Session-scoped bean exposed as a proxy -->
        <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
              
              <!-- this next element effects the proxying of the surrounding bean -->
              <aop:scoped-proxy/>
        </bean>
     <!-- a singleton-scoped bean injected with a proxy to the above bean -->
        <bean id="userService" class="com.foo.SimpleUserService">
        
            <!-- a reference to the proxied 'userPreferences' bean -->
            <property name="userPreferences" ref="userPreferences"/>
    
        </bean>

    Custom

    신규 Scope 정의를 위한 클래스를 정의하고, org.springframework.beans.factory.config.Scope 인터페이스를 implement한다. CustomScopeConfigurer를 이용하여 Custom Scope을 등록하여 Custom Scope를 사용할 수 있도록 한다.

    해당 프로젝트에 적합한 Scope을 아래의 예시와 같이 직접 정의할 수 있다.
    <!-- 신규 Scope 정의를 위한 클래스를 정의하고, 
    org.springframework.beans.factory.config.Scope 인터페이스를 implement한다.-->
    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    	<!-- CustomScopeConfigurer를 이용하여 Custom Scope 등록  -->
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="com.foo.ThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>
    <!-- Custom Scope 사용 -->
    <bean id="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>
    

Bean Life Cycle

    Initialization


    Spring Framework에서 지원하는 Life Cycle 메소드를 그대로 사용할 경우 해당 코드가 Spring Framework에 의존적일 수 있게 되므로 특정한 인터페이스를 구현함으로써 Spring Framework의 일부인 것처럼 작동하도록 만드는 것이다. Bean Attribute(init-method, destroy-method) 정의를 통해 별도 Life Cycle 메소드를 정의할 수 있도록 지원한다. 다음은 init-method 속성의 정의된 applicationContext-user-bean.xml 의 일부이다.
    <bean id="com.sds.emp.user.services.UserServiceWithIoCExtended"
    	class="com.sds.emp.user.services.impl.UserServiceImplWithIoCExtended" 
    	init-method="userInitialize" destroy-method="userDestroy" parent="parent">
    </bean>

    Destruction


    BeanFactory와 ApplicationContext가 동일하게 동작한다. 모든 Bean의 초기화 method 설정은 <beans>의 default-init-method 속성을 이용하고 모든 Bean의 소멸자 method 설정은 <beans>의 default-destroy-method 속성을 이용한다. 다음은 destroy-method 속성의 정의된 applicationContext-user-bean.xml 의 일부이다.
    <bean id="com.sds.emp.user.services.UserServiceWithIoCExtended"
    	class="com.sds.emp.user.services.impl.UserServiceImplWithIoCExtended" 
    	init-method="userInitialize" destroy-method="userDestroy" parent="parent">
    </bean>

Bean 상속

Bean 정의는 여러 속성 정보들과 생성자 인자와 Property 값을 포함하는 많은 양의 설정 정보를 포함한다. 자식 Bean 정의는 부모 정의로부터 설정 정보를 상속하는 Bean정의이다. 그러므로 값을 오버라이드하거나 다른 것을 추가할 수 있다. 부모와 자식 관계의 상속 Bean정의를 사용하는 것은 XML 파일의 양을 줄일 수 있으므로 템플릿 형태의 부모 Bean 정의는 유용하다. 또한 XML-기반의 설정 메타데이터를 사용할 때, Bean 정의에서 자식 Bean 정의는 부모 Bean을 명시하는 'parent' 속성을 사용하여 간단하게 표시될 수 있다.
  • 부모 Bean 정의
  • 특수 설정 없이 부모 Bean으로 사용이 가능하며 class 속성 값을 설정 안 한 경우, 반드시 abstract 속성 값을 "true”로 설정한다. abstract 또한 속성 값이 "true”인 경우 Bean의 인스턴스화가 불가능하다.
  • 자식 Bean 정의
  • parent 속성 값에 부모 Bean의 id 혹은 name을 설정한다.
다음은 Bean상속 속성 정의가 되어있는 applicationContext-user-bean.xml 의 일부이다.
<!-- register parent bean that has a dependency with userDAO bean -->
<bean id="parent" abstract="true">
	<property name="userDAO" ref="userDAO" />	
</bean>

<bean id="com.sds.emp.user.services.UserServiceWithIoCExtended"
	class="com.sds.emp.user.services.impl.UserServiceImplWithIoCExtended" 
	init-method="userInitialize" destroy-method="userDestroy" parent="parent">
</bean>

Container 확장

    Bean 후처리

    Bean의 LifeCycle 중 Initialization 단계 내에서 Bean 초기화 시점 전후에 수행되는 것을 Bean 후처리라고 하며, BeanPostProcessor를 구현하여 기능을 확장할 수 있다. 이때 ApplicationContext 유형의 Container 사용 시에는 XML 파일에 BeanPostProcessor 인터페이스를 구현한 클래스를 등록만 시키면 Container가 BeanPostProcessor로 인식하여 각각의 Bean이 초기화하기 전과 후에 후처리 메소드를 호출해준다. 그러나 BeanFactory 유형의 Container를 사용하고 있다면 BeanFactory의 addBeanPostProcessor() 메소드를 이용하여 프로그램 상에서 등록해야 한다. 예시는 다음과 같다.
    public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
      // simply return the instantiated bean as-is
      public Object postProcessBeforeInitialization(Object bean, String beanName) 
                                                                         throws BeansException {
         return bean; // we could potentially return any object reference here...
      }
      public Object postProcessAfterInitialization(Object bean, String beanName) 
                                                                         throws BeansException {
         System.out.println("Bean '" + beanName + "' created : " + bean.toString());
         return bean;
      }
    }
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

    BeanFactory 후처리

    Bean 정의 로딩 후 Bean이 인스턴스화 되기 전에 수행되며 주 적용 대상은 설정 메타데이터이다. BeanFactoryPostProcessor 인터페이스를 구현한 클래스 내에서 postProcessBeanFactory 메소드를 작성하여 Bean으로 정의하여 구현한다. 예시는 다음과 같다.
    public class BeanCounterBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) 
                                                         throws BeansException {
         …중략
    }
    <bean class="test.BeanCounterBeanFactoryPostProcessor"/>
    BeanFactoryPostProcessor를 구현하여 기능을 확장할 수 있다. 모든 Bean에 대한 정의가 로딩된 후, BeanPostProcessor Bean을 포함한 어떤 Bean이라도 인스턴스화되기 이전에 Spring Container에 의해 postProcessBeanFactory() 메소드가 호출된다. BeanFactoryPostProcessor는 BeanFactory 유형의 Container와 함께 사용될 수 없다. BeanFactoryPostProcessor의 유용한 구현 클래스는 PropertyPlaceholderConfigurer와 CustomEditorConfigurer이다.
    • 설정 정보의 외부화
    • PropertyPlaceholderConfigurer를 사용하여 하나 이상의 외부 프로퍼티 파일로부터 특성들을 로딩하고 그 특성들을 이용하여 Bean 정의 XML 파일에서의 위치소유자(placeholder) 변수들을 채운다. PropertyPlaceholderConfigurer 클래스를 설정 메타데이터에 등록 후 사용한다. 다음은 applicationContext-user-container.xml 의 설정 정보 외부화의 속성 정의 부분이다.
      <!-- set file locations --> 
      <bean id="configurer" class="org.springframework.beans.factory.config
                                                .PropertyPlaceholderConfigurer">
      	<property name="locations">
      		<list>
              		<value>userConfigurer.txt</value>
      		</list>
      	</property>
      </bean>
       
      <bean id="com.sds.emp.user.services.UserServiceWithIoCExtended"
      	class="com.sds.emp.user.services.impl.UserServiceImplWithIoCExtended">
      	<property name="userDAO" ref="userDAO" />
      	<!-- set userCompany value using key name in properties file -->
      	<property name="userCompany" value="${user.company}"></property>
      </bean>
      위에서 외부 파일로 정의된 userConfigurer.txt 의 내용은 다음과 같다.
      user.company=SamsungSDS
    • PropertyEditor 확장
    • CustomEditorConfigurer를 사용하여 java.beans.PropertyEditor의 커스텀 구현 클래스를 등록하여 특성 값을 다른 특성 타입으로 번역할 수 있도록 한다. 여기서 확장한 PropertyEditor 클래스를 설정 메타데이터에 등록 후 PropertyEditor로 사용한다.
      <bean id="customEditorConfigurer"          
      	class="org.springframework.beans.factory.config.CustomEditorConfigurer">
      	<property name="customEditors">
      	  <map>
      	    <entry key="com.springinaction.knight.PhoneNumber">
      	      <bean id="phoneEditor"          
      	         class="com.springinaction.springcleaning.PhoneNumberEditor" />
      	    </entry>
      	  </map>
      	</property>
      </bean>
      <bean id="knight" class="com.springinaction.knight.KnightOnCall">
         <property name="url" value="http://www.knightoncall.com" />
         <property name="phoneNumber" value="940-555-1234" />
      </bean>

ApplicationContext 활용

    MessageSource를 활용한 국제화(I18N) 지원

    ApplicationContext 인터페이스는 MessageSource라고 불리는 인터페이스를 확장해서 메시징(i18n또는 국제화)기능을 제공하며 HierarchicalMessageSource와 함께 구조적인 메시지를 분석하는 능력을 가진다. MessageSourceAware인터페이스를 구현하는 ApplicationContext로 정의된 Bean은 ApplicationContext의 messageSource Bean을 사용할 수 있다.
    다음은 applicationContext-user-container.xml 의 messageSource속성 정의 부분이다.
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    	<property name="basenames">
    		<list><value>userMessages</value></list>
    	</property>
    </bean>
    Resource Bundle 파일은 I18N 지원을 위해 로케일 별 파일로 구성되며 위에서 참조하는 userMessages.properties파일은 다음과 같다.
    argument.required=The "{0}" argument is required.
    또한 UserServiceImplWithIoCExtended.java 파일에 messageSource를 얻는 부분은 다음과 같이 구현되어 있다.
    new String(messageSource.getMessage("argument.required", new Object[] {"userVO"}, Locale.KOREA)
                                                                .getBytes("8859_1"), "euc-kr")
    messageSource 부분을 테스트 할수있는 ContainerTest.java 파일을 수행시키면 다음과 같은 message를 확인할 수 있다.
    "userVO" 인자는 반드시 필요하다.

    Event

    ApplicationContext는 어플리케이션이 구동하는 동안 다수의 이벤트를 발생시키며 리스너를 Bean으로 등록하게 되면, Container는 Bean이 ApplicationListener를 구현했으며 Event가 발생되면 onApplicationEvent() 메소드를 호출해야 한다는 사실을 알고 있다.
    • Built-in Events
    • 이벤트
      설명
      ContextRefreshedEvent ApplicationContext가 초기화되거나 갱신(refresh)될 때 발생하는 이벤트
      - 여기서 초기화는 모든 Bean이 로드되고 Singleton Bean들은 미리 인스턴스화되며 ApplicationContext는 사용할 준비가 된다는 것을 의미함
      ContextClosedEvent ApplicationContext의 close()메소드를 사용하여 ApplicationContext가 종료될 때 발생하는 이벤트
      - 여기서 종료는 Singleton Bean들이 소멸(destroy)되는 것을 의미함
      RequestHandledEvent HTTP Request가 처리되었을 때 WebApplicationContext 내에서 발생하는 이벤트
      - 이 이벤트는 Spring의 DispatcherServlet을 사용하는 웹 어플리케이션에서만 적용 가능함
      Event Listening - 리스너를 Bean으로 등록하게 되면, Container는 Bean이 ApplicationListener를 구현했으며 Event가 발생되면 onApplicationEvent() 메소드를 호출해야 한다는 사실을 알고 있다.

      소스 코드 예시는 다음과 같다.
      public class RefreshListener implement ApplicationListener {
          public void onApplicationEvent(ApplicationEvent evt) {
      	if (evt instanceof ContextRefreshedEvent) { 중략 …


      속성 정의 예시는 다음과 같다.
      <bean id="refreshListener" class="sample.RefreshListener"/>
      
    • Custom Event 발생
    • 사용자 정의 Event를 직접 발생시키고 해당 Event 발생 시 처리될 수 있도록 리스너를 등록 할 수 있다. Event Listening을 하기 위해서는 리스너 등록이 필요하다. 다음은 리스너 Bean을 등록하는 applicationContext-user-container.xml 파일의 일부이다.
      <bean id="userEventListener" class="com.sds.emp.user.services.impl.UserEventListener"/>
      다음은 UserEventListener.java파일의 소스 코드 이다.
      public class UserEventListener implements ApplicationListener {
        public void onApplicationEvent(ApplicationEvent evt) {
          if (evt instanceof UserEvent) {
            UserEvent event = (UserEvent)evt;
          System.out.println("Received in UserEventListener : " + event.getUserMessage());
          }
        }
      }
      다음은 UserServiceImplWithIoCExtended.java 파일의 Event 발생 구현 부분이다.
      this.ctx.publishEvent(new UserEvent(this,"new user is added successfully."));

    BeanFactory와 ApplicationContext Feature 비교

    Feature
    BeanFactory
    ApplicationContext
    Bean instantiation/wiring
    Yes
    Yes
    Automatic BeanPostProcessor registration
    No
    Yes
    Automatic BeanFactoryPostProcessor registration
    No
    Yes
    Convenient MessageSource access (for i18n)
    No
    Yes
    ApplicationEvent publication
    No
    Yes

    대부분의 전형적인 어플리케이션 구축 시에는 ApplicationContext 사용을 권장한다.

Resources

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

  • 참고자료