Spring IoC容器之自定义bean的生命周期及定义继承

一、自定义bean的生命周期

通过实现spring的InitializingBean和DisposableBean接口,可以让容器来管理bean的生命周期。容器在调用afterPropertiesSet()方法后和调用destroy()方法前会允许bean在初始化和销毁bean时执行以下操作,但使用这些接口就与springAPI产生了耦合。

解耦合的处理方式有两种:

\bullet 使用JSR-250的@PostConstruct和@PreDestroy注解是spring应用生命周期回调的最佳实践。

\bullet 使用init-method和destroy-method的定义来解耦spring接口。

spring框架使用BeanPostProcessor接口的实现来处理接口的回调,BeanPostProcessor能找到并调用合适的方法。如果需要定制spring不直接提供的生命周期行为,可自行实现一个BeanPostProcessor。

除初始化回调和销毁回调外,spring管理的对象也实现了Lifecycle接口,让管理的对象在容器的生命周期内启动或关闭。

1、初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean在所有必要依赖配置完成后执行初始化bean。

接口定义

不建议使用InitializingBean接口,否则会将代码耦合到spring特定接口上。使用@PostConstruct注解或指定一个POJO的实现方法的方式更好。在基于XML的配置元数据时可以使用init-method属性来指定一个没有参数的方法。使用Java配置时也可以使用@Bean中的init-method属性初始化回调。示例如下:

<bean id="initBean" class="xx.InitBean" init-method="init" />

public class InitBean {

    public void init(){...}

}

以下方式等效于以上方式,但会耦合到spring中:

<bean id="initBean" class="xx.InitBean" />

public class InitBean implements InitializingBean {

    public void afterPropertiesSet(){...}

}

2、销毁回调

实现org.springframework.beans.factory.DisposableBean接口就可以让容器通过回调来销毁bean锁引用的资源。

接口定义

与InitializingBean接口类似,同样不推荐使用。使用@PreDestroy注解或指定一个bean支持的配置方法或在基于XML的配置元数据中,在bean标签上指定destroy-method属性。在基于Java的配置中可以配置@Bean中的destroy-method属性实现销毁回调。示例如下:

<bean id="desBean" class="xx.DesBean" destroy-method="cleanup" />

public class DesBean {

    public void cleanup(){...}

}

以下方式等效于以上方式,但会耦合到spring中:

<bean id="desBean" class="xx.DesBean" />

public class DesBean implements DisposableBean {

    public void destroy(){...}

}

3、结合生命周期机制

spring2.5之后,有3种方式来控制bean的生命周期行为:

\bullet InitializingBean和DisposableBean回调接口

\bullet 自定义的init()和destroy()方法

\bullet 使用@PostConstruct和@PreDestroy注解

如果一个bean配置了多个生命周期机制,并且含有不同的方法名,执行顺序如下:

初始化时:

1)包含@PostConstruct注解的方法

2)在InitializingBean接口中的afterPropertiesSet()方法

3)自定义的init()方法

销毁时:

1)包含@PreDestroy注解的方法

2)在DisposableBean接口中的destroy()方法

3)自定义的destroy()方法

4、启动和关闭回调

Lifecycle接口中为任何有生命周期需求的对象定义了一些基本方法,spring管理的任何对象都可以实现其接口。当ApplicationContext接收到了启动或停止信号时,它会通知所有上下文包含的生命周期对象,通过LifecycleProcessor接口来串联上下文中的Lifecycle来实现对象。

Lifecycle接口定义
LifecycleProcessor接口定义

LifecycleProcessor是对Lifecycle的扩展,它增加了2个方法来对上下文的刷新和关闭作出反应。

如果不同bean之间存在depends-on的关系,被依赖的一方需要更早的启动或关闭,有时直接的依赖是未知的,开发者可能并不值得哪些类型需要更早的进行初始化。SmartLifecycle接口定义了另外一种选项,即其父接口Phased中的getPhase()方法。

SmartLifecycle接口定义  
Phased接口定义

当启动时,拥有最低phased的对象优先启动,当关闭时,按相反的顺序。如果一个对象实现了SmartLifecycle接口,其getPhase()返回了Integer.MIN_VALUE,就会让该对象最早启动,最晚销毁。如果getPhase()返回了Integer.MAX_VALUE,就会让该对象最晚启动,最早销毁。当使用phased的值时,需要知道正常没有实现SmartLifecycle的Lifecycle对象的默认值,此值为0。任何负值都会将标明对象在标准组件启动前启动,在标准组件销毁后销毁。

SmartLifecycle定义了一个stop()回调函数,任何实现了其接口的方法都必须在关闭流程完成后调用回调中的run()方法,这样可以使其异步关闭。LifecycleProcessor的默认实现DefaultLifecycleProcessor会等到配置的时间超时后再调用回调,每阶段的超时默认30S。可以通过定义一个名为lifecycleProcessor的bean来覆盖默认的生命周期处理器,若需要配置超时时间,可如下配置:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">

    <!-- 超时时间,单位毫秒 -->

    <property name="timeoutPerShutdownPhase" value="10000" />

</bean>

SmartLifecycle接口定义回调方法来刷新和关闭上下文,如果关闭则说明stop()已被调用,就会驱动关闭流程;如果上下文正在关闭,就不会发生这种情况。刷新的回调会使用SmartLifecycle的另一特性:当上下文刷新完毕就会调用回调,默认的生命周期处理器会检查每一个SmartLifecycle对象的isAutoStartup()返回值。若为true,对象将会自动启动而非等待明确的上下文调用,或调用自己的start()。phased的值及depends-on会决定对象启动和销毁的顺序。

5、在非web应用中关闭Spring IoC容器

在桌面客户端等非web环境下使用Spring IoC容器,需要在JVM上注册一个关闭的钩子,确保在关闭IoC容器时能够调用相关的销毁方法来释放引用的资源,同时也需要正确的配置和实现销毁回调。

在ConfigurableApplicationContext接口调用registerShutdownHook()方法来注册销毁的钩子。示例如下:

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");

...

ctx.registerShutdownHook();

6、ApplicationContextAware和BeanNameAware

当ApplicationContext在创建实现了org.springframework.context.ApplicationContextAware的对象时,该对象的实例会包含一个到ApplicationContext的引用。这样bean可以通过编程的方式操作和创建ApplicationContext。

ApplicationContextAware接口定义

通过ApplicationContext接口或通过将引用转换为已知接口的子类,如ConfigurableApplicationContext,其中一个用法是可以通过编程的方式来获取其他bean。但不建议这样做,这样代码会耦合到spring。ApplicationContext的其他方法可以提供资源访问、发布应用事件、进入MessageSource等功能。

自动装载也是获得ApplicationContext的一种方式,构造函数和通用类型的装载方式,是指可以通过构造函数或setter方法注入,也可以通过注解注入的方式。

当ApplicationContext创建一个实现了org.springframework.beans.factory.BeanNameAware接口的类,这个类就可以针对其名称进行配置。这个回调的调用发生在属性配置完成后,初始化回调之前,如InitializingBean.afterPropertiesSet()及自定义的初始化方法等。

BeanNameAware定义

7、其他Aware接口

spring还提供了其他Aware接口来让bean通知容器,这些bean需要一些具体的注入的依赖信息,如下:

\bullet ApplicationContextAware:声明的ApplicationContext

\bullet ApplicationEventPublisherAware:ApplicationContext中的事件发布器

\bullet BeanClassLoaderAware:加载bean使用的类加载器

\bullet BeanFactoryAware:声明的BeanFactory

\bullet BeanNameAware:bean的名称

\bullet LoadTimeWeaverAware:加载期间处理类定义的Weaver

\bullet MessageSourceAware:解析消息的配置策略

\bullet NotificationPublisherAware:spring JMX通知发布器

\bullet ResourceLoaderAware:配置的资源加载器

上面这些接口的不建议使用,因为其会违反IOC原则。

二、定义继承

bean的定义可以包含构造方法参数、属性值、容器特定的信息等。子bean定义可以从父bean定义的配置元数据来继承,它可以覆盖或添加一些所需的值。父子bean是一种典型的模板形式。

如果编程式的使用ApplicationContext接口,子bean的定义可以通过ChildBeanDefinition类来表示,但使用更多的是在类似于ClassPathXmlApplicationContext中声明式的配置bean的定义。如果使用基于XML的配置时,可以在子bean中使用parent属性,用于标识父bean。

<bean id="testBean" abstract="true" class="org.springframework.beans.TestBean">

  <property name="name" value="parent"/>

  <property name="age" value="10"/>

</bean>

<bean id="testBean1" class="org.springframework.beans.TestBean1" parent="testBean" init-method="initialize">

  <property name="name" value="override"/>

</bean>

子bean如果没有指定class,它将使用父bean定义的class或进行重载。在重载情况下子bean必须与父bean兼容,即它必须接受父bean的属性值。

子bean定义可以从父bean继承作用域、构造器参数、属性值和可重写的方法,还可以增加新值。开发者指定的任何作用域、初始化方法、销毁方法、静态工厂方法设置,都会覆盖相应的父bean设置。其余的设置总是取自子bean的定义,如depends-on、autowire mode、dependency check、singleton、scope和lazy init等。

上例中使用abstract属性明确表明父bean是抽象的,如果父bean定义没有明确指出所属类,则需要标记父bean定义为abstract。

<bean id="testBean" abstract="true">

  <property name="name" value="parent"/>

  <property name="age" value="10"/>

</bean>

<bean id="testBean1" class="org.springframework.beans.TestBean1" parent="testBean" init-method="initialize">

  <property name="name" value="override"/>

</bean>

上例中只有一个bean定义为abstract,它只能作为一个纯粹的为子bean定义的bean模板。独立使用这样一个abstract的父bean,把它作为另一个bean的引用,或根据这个父bean的id显式调用getBean(),都将返回错误。

ApplicationContext默认会预实例化所有单例bean,如果把一个bean定义仅作为模板来用,同时给它指定了class属性,就必须设置abstract=true,否则ApplicationContext就会预实例化这个abstract bean。


--参考文献《Srping5开发大全》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容