IoC容器6——自定義bean屬性

自定義bean屬性

1 生命週期回掉函數

爲了與容器管理bean的生命週期相互做用,能夠實現Spring的InitializingBean和DisposableBean接口。容器經過調用前者的afterPropertiesSet()函數和後者的destroy()函數,容許bean在初始化和銷燬時執行某些操做。java

JSR-250的@PostConstruct和@PreDestory註解通常被認爲在現代Spring應用中是接受生命週期回掉的最佳實踐。使用這些註解意味着不須要與Spring的特定接口耦合。web

若是不想使用JSR-250,可是仍想消除耦合,可使用init-method和destroy-method對象定義元數據。spring

在內部,Spring Framework使用BeanPostProcessor的實現來處理它能夠找到的任何回調接口,並調用適當的方法。若是須要自定義的特徵或其它生命週期行爲,Spring沒有提供開箱即用的實現,能夠本身實現BeanPostProcessor。編程

做爲實例化和銷燬回調函數的補充,Spring管理的對象也能夠實現Lifecycle接口,以便這些對象能夠加入容器本身的生命週期驅動的啓動和關閉進程。app

初始化回調函數

實現org.springframework.beans.factory.InitializingBean接口容許bean在所用必需的屬性被容器設置完成後執行初始化工做。InitializingBean接口定義了一個方法:異步

void afterPropertiesSet() throws Exception;

並不推薦使用InitializingBean接口由於沒用必要將代碼與Spring耦合。做爲替代,使用@PostConstruct註解或者指定一個POJO初始化方法。在XML格式餓配置元數據中,使用init-method屬性指定一個無參數、返回類型爲void簽名的方法名。使用Java配置,可使用@Bean的initMethod屬性。函數

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
    public void init() {
        // do some initialization work
    }
}

上例等價於下面的例子,可是代碼並不與Spring耦合。this

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
    public void afterPropertiesSet(){
        // do some initialization work
    }
}

銷燬回調函數

實現org.springframework.beans.factory.DisposableBean接口容許容器一個bean在被容器銷燬時得到回調。DisposableBean接口定義了一個方法:代理

void destroy() throws Exception;

並不推薦使用DisposableBean回調接口,由於沒有必要將代碼與Spring耦合。做爲替代,可使用@PreDestory註解或者指定一個bean定義支持的範型方法。使用XML格式的配置元數據,可使用<bean/>元素的destroy-method屬性。使用Java配置,可使用@Bean的destroyMethdo屬性。code

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
    public void cleanup(){
        // do some destruction work (like releasing pooled connections)
    }
}

上例等價於下面的例子,可是代碼不與Spring耦合。

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
    public void destroy(){
        //do some destruction work (like releasing pooled connections)
    }
}

<bean>元素的destroy-method屬性能夠指定一個特殊的值——inferred,來指示Spring自動發現指定bean類的公有的close或者shutdown方法(所以任何實現java.lang.AutoCloseable或者java.io.Closeable的類都將匹配)。這個特殊的值(inferred)也能夠設置爲<beans>元素的default-destroy-method屬性以便將此行爲應用整個bean的集合。注意,這是Java配置的默認行爲。

默認的初始化和銷燬方法

當你不使用Spring指定的InitializingBean和DisposableBean回調接口來編寫初始化和銷燬回調函數,通常的能夠將方法命名爲init(), initialize(), dispose()等等。理想狀態下,這些生命週期回調函數的名字在一個工程中是標準化的,所以全部開發者使用相同的方法名字並保證一致性。

能夠配置Spring容器查找每一個bean的初始化和銷燬回調函數名。這意味着應用開發者能夠編寫應用類而且使用名爲init()的初始化回調函數,而不用在每一個bean定義配置init-method="init"屬性。這個特性也保證了初始化和銷燬回調函數的命名一致性。

假設初始化方法命名爲init()、銷燬方法命名爲destory(),那麼Class會相似於下面的例子

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }

}
<beans default-init-method="init">
    <bean id="blogService" class="com.foo.DefaultBlogService">
        <property name="blogDao" ref="blogDao"/>
    </bean>
</beans>

這個存在於最上層<beans/>元素的人default-init-method屬性致使Spring IoC容器將bean的init方法識別爲初始化回調函數。當一個bean被建立和裝配,若是bean的類有以下方法,它會在適當的時間被調用。

能夠相似的的在<beans/>元素中設置default-destroy-method屬性來配置銷燬回調函數。

當bean的類中已經存在與約定的名稱不一樣的回調函數時,可使用<bean/>元素自身的init-method和destroy-method屬性來覆蓋默認的屬性。

Spring容器保證被配置的初始化回調函數會在bean的依賴關係裝配完成後當即被調用。所以是在生的bean引用上調用初始化回調函數,這意味着AOP攔截器等等還沒有做用於bean。一個目標bean首先被完整建立,而後應用一個具備攔截器鏈的AOP代理。若是目標bean和代理被分別定義,代碼甚至能夠繞過代理與原始目標bean進行交互。所以將攔截器應用於初始化方法是不一致的,由於這麼作會將目標bean的生命週期和代理/攔截器耦合在一塊兒同時當代碼直接於目標bean進行交互時會產生奇怪的語義。

聯合使用生命週期機制

在Spring 2.5中有三種控制bean生命週期的行爲:InitializingBean和DisposableBean的回調接口;用戶自定義的init()和destroy()方法;和@PostConstruct、@PreDestroy註解。能夠聯合使用這三種機制來控制個bean。

若是一個bean被配置了多個生命週期機制,而且每一個機制配置了不一樣的方法名,那麼每一個配置的方法都會按下述的順序執行。然而,若是多種機制配置了相同的方法名——例如,init()初始化方法—,那麼方法會被執行一次。

同一個bean配置了多種生命週期機制,不一樣名稱的方法會按下面的順序執行:

  • 標記了@PostConstruct註解的方法;
  • IntializingBean接口中定義的afterPropertiesSet()方法;
  • 用戶配置的init()方法。

銷燬方法按以下順序調用:

  • 標記了@PreDestroy註解的方法;
  • DisposableBean接口定義的destroy()方法;
  • 用戶配置的destroy()方法。

啓動和關閉回調函數

Lifecycle接口爲任何具備本身生命週期要求的對象定義必要的方法(例如開始和中止一些後臺進程):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

任何Spring管理的對象能夠實現這個接口。而後,當ApplicationContext自身接收到開始和中止信號,例如在運行時的中止/重啓場景,它會級聯的調用定義在其中的所用Lifecycle實現。它經過委託給LifecycleProcessor來完成這項工做:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

請注意,LifeCycleProcessor自身就是Lifecycle接口的擴展。它也添加了兩個接口來響應上下文被刷新和關閉的狀況。

請注意,常規的org.springframework.context.Lifecycle接口只是明確的啓動/中止通知的簡單契約,並不意味着在上下文刷新時自動啓動。考慮實現org.springframework.context.SmartLifecycle,以便對特定bean的自動啓動進行細粒度的控制(包括啓動階段)。另請注意,中止通知不能保證在銷燬以前:在常規關閉時,全部Lifecycle bean將在傳播給通常銷燬回調以前首先收到中止通知;然而,在上下文生命週期的熱刷新或停止的刷新嘗試時,只會調用destroy方法。

啓動和關閉調用的順序很重要。若是兩個對象之間有依賴關係,依賴的一方將會比它的依賴關係晚啓動,而且早關閉。然而,許多時候直接的依賴關係是未知的。您也許只知道一些特定類型的對象要比另外一些類型的對象優先啓動。在這種狀況下,SmartLifecycle接口定義了另外一種選擇,在它的父接口Phased中定義的getPhase()方法。

public interface Phased {

    int getPhase();

}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}

在啓動時,具備低phase的對象首先啓動,而且在關閉時順序相反。所以,實現了SmartLifecycle接口而且getPhase()方法的返回值是Integer.MIN_VALUE的對象將對一個被啓動並最後一個被結束。在另外一端,phase的值是Integer.MAX_VALUE代表對象將被最晚啓動、最先結束(有可能它須要其它的處理來運行)。任何普通的沒有實現SmartLifecycle接口的Lifecycle對象的phase值爲0。所以任何負的phase值代表對象早於普通組件啓動,晚於它們關閉。任何正相位值反之亦然。

SmartLifecycle的stop方法接收一個回調。任何實現必須在自身的關閉過程完成後調用該回調的run()方法。這將在須要時啓用異步關閉,由於LifecycleProcessor接口的默認實現DefaultLifecycleProcessor將等待每一個階段內的對象組的超時值來調用該回調。每一個階段的默認超時值是30秒。能夠覆蓋默認的生命週期處理器,經過在上下文中定義名爲lifecycleProcessor的bean。若是僅僅想修改超時時間,下面的定義就足夠了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如上所述,LifecycleProcessor接口定義了用於刷新和關閉上下文的回調方法。後者將簡單地驅動關閉過程,就像已經明確調用了stop()同樣,可是當上下文關閉時會發生。另外一方面,「刷新」回調啓用了SmartLifecycle bean的另外一個功能。當上下文被刷新(全部對象被實例化和初始化以後)時,該回調將被調用。而且此時默認生命週期處理器將檢查每一個SmartLifecycle對象的isAutoStartup()方法返回的布爾值。若是爲真,則該對象將在此時啓動,而不是等待顯式調用上下文或其本身的start()方法(與上下文刷新不一樣,對於標準上下文實現,上下文啓動不會自動發生)。phase值以及任何依賴關係將以與上述相同的方式肯定啓動順序。

在非web應用中優雅的關閉Spring IoC容器

這部分僅應用於非web應用。Spring基於web的ApplicationContext實現已經包含在相關的web應用關閉時優雅的關閉Spring IoC容器的相關代碼。

若是在非web應用環境中使用Spring IoC容器;例如在一個桌面客戶端環境;能夠註冊一個JVM關閉回調。這麼作保證了優雅的關閉而且會調用singleton bean的相關銷燬方法,以保證全部資源會被釋放。固然,必須正確的配置和實現這些銷燬方法。

爲了註冊關閉回調,須要調用在ConfigurableApplicationContext接口中定義的registerShutdownHook()方法:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {

        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String []{"beans.xml"});

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...

    }
}

2 ApplicationContextAware 和 BeanNameAware

當一個ApplicationContext建立一個實現了org.springframework.context.ApplicationContextAware接口的對象實例,這個實例被提供一個ApplicationContext的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

所以這個bean能夠經過編程的方法操做建立它的ApplicationContext,經過ApplicationContext接口或者將其引用轉換成暴露額外功能的已知的接口子類,例如ConfigurableApplicationContext。一個做用時經過編程的方法獲取其它bean。有時候這種能力頗有用;可是,通常狀況下你須要避免它,由於它將代碼和Spring耦合在一塊兒而且沒有遵循控制反轉的風格——協做者做爲屬性提供給bean。ApplicationContext的其它方法提供獲取文件資源、發佈應用事件和獲取MessageSource的能力。

在Spring 2.5中,自動裝配是另外一個獲取ApplicationContext引用的選擇。傳統的構造函數和根據類型的自動裝配模式能夠爲構造函數參數或setter方法參數提供ApplicationContext類型的依賴。爲了更多的靈活性,包括自動裝配字段的能力和多種參數方法,可使用你的基於註解的自動裝配功能。若是這樣作,ApplicationContext將自動裝配那些標註了@Autowired註解的類型爲ApplicationtContext的字段、構造函數參數或者方法參數。

當ApplicationContext建立了一個類實現了org.springframework.beans.factory.BeanNameAware接口,該類被提供其定義的名稱的引用。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;

}

這個方法的調用發生在普通屬性設置以後在初始化回調(例如InitializingBean的afterPropertiesSet方法或者用戶自定義的初始化方法)調用以前。

3 其它感知接口

除了上文所述的ApplicationContextAware和BeanNameAware,Spring還提供了一批感知接口容許bean向容器指明它們須要的明確基礎設施依賴。最重要的感知接口整理以下,做爲通常規則,名稱是依賴關係類型的良好指示:

名字 注入的依賴
ApplicationContextAware 聲明的ApplicationContext
ApplicationEventPlulisherAware ApplicationContext中的事件發佈器
BeanClassLoaderAware 加載Bean使用的類加載器
BeanFactoryAware 聲明的BeanFactory
BeanNameAware Bean的名字
BootstrapContextAware 容器運行的資源適配器BootstrapContext,一般僅在JCA環境下有效
LoadTimeWeaverAware 加載期間處理類定義的weaver
MessageSourceAware 解析消息的配置策略
NotificationPublisherAware Spring JMX通知發佈器
PortletConfigAware 容器當前運行的PortletConfig,僅在web下的Spring ApplicationContext中可見
PortletContextAware 容器當前運行的PortletContext,僅在web下的Spring ApplicationContext中可見
ResourceLoaderAware 配置的資源加載器
ServletConfigAware 容器當前運行的ServletConfig,僅在web下的Spring ApplicationContext中可見
ServletContextAware 容器當前運行的ServletContext,僅在web下的Spring ApplicationContext中可見

再次注意,使用這些接口將代碼和Spring API綁定,而且不遵循控制反轉樣式。所以,對於基礎設施bean建議使用編程的訪問方式。

相關文章
相關標籤/搜索