IoC容器6——自定义bean属性

自定义bean属性

1 生命周期回掉函数

为了与容器管理bean的生命周期相互作用,可以实现Spring的InitializingBean和DisposableBean接口。容器通过调用前者的afterPropertiesSet()函数和后者的destroy()函数,允许bean在初始化和销毁时执行某些操作。

JSR-250的@PostConstruct和@PreDestory注解一般被认为在现代Spring应用中是接受生命周期回掉的最佳实践。使用这些注解意味着不需要与Spring的特定接口耦合。

如果不想使用JSR-250,但是仍想消除耦合,可以使用init-method和destroy-method对象定义元数据。

在内部,Spring Framework使用BeanPostProcessor的实现来处理它可以找到的任何回调接口,并调用适当的方法。如果需要自定义的特征或其它生命周期行为,Spring没有提供开箱即用的实现,可以自己实现BeanPostProcessor。

作为实例化和销毁回调函数的补充,Spring管理的对象也可以实现Lifecycle接口,以便这些对象可以加入容器自己的生命周期驱动的启动和关闭进程。

初始化回调函数

实现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耦合。

<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属性。

<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
ApplicationEventPlulisherAwareApplicationContext中的事件发布器
BeanClassLoaderAware加载Bean使用的类加载器
BeanFactoryAware声明的BeanFactory
BeanNameAwareBean的名字
BootstrapContextAware容器运行的资源适配器BootstrapContext,通常仅在JCA环境下有效
LoadTimeWeaverAware加载期间处理类定义的weaver
MessageSourceAware解析消息的配置策略
NotificationPublisherAwareSpring 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建议使用编程的访问方式。

转载于:https://my.oschina.net/u/2453016/blog/1068541

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值