2011-02-16

Spring IoC Container 3.0.5 之二

  • Bean scopes
    • Spring 支援五種 scope(singleton, prototype),其中有三種只能用在 web app 裡(request, session, global session),另外 Spring 支援自訂 scope。
    • 在非 web 環境使用  web 專屬的 scope 會出錯。
    • Spring 3.0 提供 thread scope(SimpleThreadScope),但是預設沒有註冊,可以在「自訂 scope」裡找到註冊的方式。
    • singleton
      • 一個 Spring Ioc container 只會有一個 instance,為預設值。
      • 不同於 GoF 的 Singleton pattern,該 pattern 是指定在一個 ClassLoader 裡只會有一個 class 的 instance,而 Spring 的 singleton 是指一個 Ioc container 只會有一個 bean 的 instance,不是一個 class,所以同一個 class 定義多個 bean id 時,同一個 class 是可以有多個 instance 的,唯一的是指 bean id。
    • prototype
      • 每次呼叫時就會產生新的 instance,不管是 inject 或者是 getBean()。
      • 所以 prototype bean 適用於 stateful 的情況,而 singleton bean 則適用於 stateless。
      • 與其他 scope 的 bean 不同,Spring 只養 prototype bean 到 18 歲,也就是說 Spring 不管 prototype bean 在 inject 或 getBean 以後的 lifecycle callback,得到該 bean 的 client 要負責。
      • 可以實做 BeanPostProcessor 來負責 prototype bean 的老年生活。
      • 簡單講,Spring 的 prototype 僅僅就是 Java 的 new 而已。

    • 如果在設定檔裡,將 prototype bean 塞到 singleton bean 裡,那麼在啟動初始化該 singleton bean 時,也會同時產生並初始化該 prototype bean,而且該 singleton bean 這一輩子只會有這麼一個 prototype bean,不會因為不同的呼叫而得到新的 prototype bean;如果想要每次呼叫得到新的 prototype bean,可以使用 Method injection(參考 The IoC Container in Spring 3.0.5 之一 最後一部分)。
    • request
      • 因為是 Http Request level,所以在每個 request 裡,request bean 成為 stateful。
      • Request bean 死於 Http Request 結束。
    • session
      •  因為是 Http Session level,所以在每個 session 裡,session bean 成為 stateful。
      • Session bean 死於 Http session 結束。
    • gobal session
      • 與 session scope 相同,唯一差別在於 global session 用於 portlet 環境。
      • 如果在一般非 portlet 的 web 環境使用 global session scope,視為 session scope,不會出錯。
    • Spring 不允許直接將短命的 bean 塞到長壽的 bean 裡,例如將 session bean 塞到 singleton bean 裡,因為 singleton bean 出生於系統啟動時,而此時 session bean 還沒出生,也無法出生,更何況 sinleton bean 是大家共用的,而 session bean 是私家的。
      • 解決方法就是塞 session bean 的分身 proxy bean 到 singleton bean 裡,然後在某人呼叫會用到 session bean 的 sinleton bean method 時,此時這個某人一定有 session,所以 當初那個 proxy bean 就可以奉上真正的 session bean。
        <bean ... scope="session">
        <aop:scoped-proxy/>
        </bean>
      • aop:scoped-proxy 只能用在 request、session 或 global session bean,不可以用在 singleton 或 prototype bean,不然會有報應。
      • aop:scoped-proxy 是使用 CGLIB 的 class proxy,這代表兩件事,第一是系統必須提供 CGLIB libray ,第二就是 CGLIB 只會 proxy public method,所以非 public method 是沒有 proxy 的。
      • 除了 CGLIB proxy,也可以使用 JDK interface proxy,差異如下:
        <aop:scoped-proxy proxy-target-class="false"/>
        • 不需要其他 library。
        • 至少實做一個 interface。
        • 必須透過 interface 來 inject。
    • 自訂 scope
      • Spring 2.0 以後可以自訂 scope,也可以修改內建 web 相關的 scope,但不建議。
      • 自訂 scope 的第一步就是實做 org.springframework.beans.factory.config.Scope,可以參考既有的實做,然後呼叫 ConfigurableBeanFactory.registerScope(String scopeName, Scope scope) 註冊到 Spring container 就完成了。
        Scope threadScope = new SimpleThreadScope();
        beanFactory.registerScope("thread", threadScope);
      • 也可以使用宣告式的註冊方式:
        <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
        <map>
        <entry key="thread">
        <bean class="org.springframework.context.support.SimpleThreadScope"/>
        </entry>
        </map>
        </property>
        </bean>
  • Lifecycle callbacks
    • 初始
      • 實做 org.springframework.beans.factory.InitializingBean,Spring 將在所有 property 設定完成後呼叫 afterPropertiesSet(),一般不建議這種 hard code 的方式,因為這樣就會和 Spring 綁在一起。
      • 可以透過設定的方式,在 init-method 指定一個 void no-argument 的 method 名稱。
        <bean ... init-method="init"/>
    • 拆除
      • 實做 org.springframework.beans.factory.DisposableBean,Spring 將在回收該 bean 前呼叫 destroy(),一般不建議這種 hard code 的方式,因為這樣就會和 Spring 綁在一起。
      • 可以透過設定的方式,在 destroy-method 指定一個 void no-argument 的 method 名稱。
        <bean ... destroy-method="cleanup"/>
    • 可以在 constainer level 設定初始與拆除的設定,好處是整個系統的初始與拆除 method 命名可以一致,甚至沒設定該 method 也不會出錯,最後可以用 bean level 的設定覆蓋 beans 的設定。
      <beans default-init-method="init" default-destroy-method="destroy">...</beans>
    • 在 Spring 2.5 以後,又多了 @PostConstruct 和 @PreDestroy 可以用,但是如果三種方法同時使用,且各指定不同的 method 名稱時,三種方法都會執行,執行順序如下:
      • @PostConstruct / @PreDestroy
      •  InitializingBean / DisposableBean
      •  init-method / destroy-method
      •  如果名稱相同,只會執行一次。
    • 為了正確的退出非 web Spring container,必須在產生 ctx 物件後,呼叫 AbstractApplicationContext.registerShutdownHook(),這樣 destroy 才會被呼叫;web app 則會自動被呼叫。
    • ApplicationContextAware
      • 實做 ApplicationContextAware 以取得 ctx 物件。
      • 在 Spring 2.5 以後,可以使用 constructor 與 byType 這兩個 autowire 屬性來取得 ctx 物件。
    • BeanNameAware
      • 實做 BeanNameAware 以取得 bean id。
      • 在設定所有 properties 之後,呼叫初始 callback 之前呼叫 BeanNameAware。
    • 其他 Aware:
      • ApplicationEventPublisherAware
      • BeanClassLoaderAware
      • BeanFactoryAware
      • BootstrapContextAware
      • LoadTimeWeaverAware
      • MessageSourceAware
      • NotificationPublisherAware
      • PortletConfigAware
      • PortletContextAware
      • ResourceLoaderAware
      • ServletConfigAware
      • ServletContextAware
  • Bean 定義範本
    • 因為 bean 的定義資訊非常多,如果有些 bean 的定義資料雷同時,可以定義範本並使用繼承的方式來改寫不同的部份。
      <bean id="parentBean" abstract="true" ...>...</bean>
      <bean id="childBean" parent="parentBean" ...>...</bean>
    • 使用 abstract="true" 的 bean 可以不用設定 class 屬性。
    • 有些資訊是無法繼承的,必須自行指定:
      • depends on
      • autowire
      • dependency check
      • scope
      • lazy init
    • 因為 Spring 預設會初始化所有的 singleton bean,所以當 parent bean 僅僅當作範本時,一定要加上 abstract="true",不管有沒有設定 class 屬性。
  • 擴充 Spring container
    • 正常的狀況下,不需要繼承並改寫 ApplicationContext,因為 Spring 已經提供很多擴充機制。
    • BeanPostProcessor
      • 擴充或改寫 bean 初始化的過程,也就是說 Spring container 產生一個 bean 並設定所有 properties,然後依序呼叫 BeanPostProcessor.postProcessBeforeInitialization()、init method、BeanPostProcessor.postProcessAfterInitialization()。
      • 如果是要在 bean 產生之前修改 bean 的設定值,應該使用 BeanFactoryPostProcessor。
      • 可以同時提供多個 BeanPostProcessor 實做,建議同時實做 Ordered 以決定 BeanPostProcessor 實做的執行順序。
      • AOP auto-proxy 就是一種 BeanPostProcessor 實做,也就是因為這樣,所以其他的 BeanPostProcessor 實做是無法使用 AOP auto-proxy 的。
      • BeanPostProcessor 實做的 bean 不用定義 id 或 name 屬性,因為用不到。
      • BeanPostProcessor 為 container scope 的。
      • 千萬不要將 BeanPostProcessor 實做設定為 lazy-init,否則他們永遠不會醒過來,如果在 beans 上設定了 default-lazy-init,記得在 BeanPostProcessor 實做 bean 上設定 lazy-init="false"。
      • 參考 RequiredAnnotationBeanPostProcessor。
    • BeanFactoryPostProcessor
      • Spring 在初始化任何除了 BeanFactoryPostProcessor 以外的 bean 之前 ,會先執行 BeanFactoryPostProcessor 實做,允許其讀取 bean 的設定值且修改之。
      • 如果只是要修改 bean 產生後的 instance,應該使用 BeanPostProcessor。
      • 可以同時提供多個 BeanFactoryPostProcessor 實做,建議同時實做 Ordered 以決定 BeanFactoryPostProcessor 實做的執行順序。
      • BeanFactoryPostProcessor 為 container scope 的。
      • 如果只是要修改設定值,Spring 提供一些內建的 BeanFactoryPostProcessor 實做,如 PropertyOverrideConfigurer 與 PropertyPlaceholderConfigurer。
      • PropertyPlaceholderConfigurer
        <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:com/.../jdbc.properties"/>
        </bean>
        • 在 Spring 2.5 提供 context namespace 之後,設定 placeholder 變得較簡單了,可以在 location 裡使用逗號指定多筆 properties 檔。
          <context:property-placeholder location="classpath:com/.../jdbc.properties"/>
        • PropertyPlaceholderConfigurer 不只會去指定的 properties 檔找,也會去 Java system properties 裡找,可以使用 systemPropertiesMode 屬性設定其行為。
        • bean 的 class 屬性也可以使用 placeholder。
      • PropertyOverrideConfigurer  
        • 可以定義新的 properties 檔覆蓋舊的,順序由 Ordered 決定。
          <context:property-override location="classpath:override.properties"/>
      • 如果是要註冊自訂的 property editor 的話,就得自行實做 BeanFactoryPostProcessor。
      • 千萬不要將 BeanFactoryPostProcessor 實做設定為 lazy-init,否則他們永遠不會醒過來,如果在 beans 上設定了 default-lazy-init,記得在 BeanFactoryPostProcessor 實做 bean 上設定 lazy-init="false"。
    • FactoryBean
      • 自訂初始化過程,太進階了,略。

沒有留言:

張貼留言