博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IoC容器6——自定义bean属性
阅读量:6893 次
发布时间:2019-06-27

本文共 8539 字,大约阅读时间需要 28 分钟。

hot3.png

自定义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属性。

public class ExampleBean {    public void init() {        // do some initialization work    }}

上例等价于下面的例子,但是代码并不与Spring耦合。

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

public class ExampleBean {    public void cleanup(){        // do some destruction work (like releasing pooled connections)    }}

上例等价于下面的例子,但是代码不与Spring耦合。

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属性导致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。如果仅仅想修改超时时间,下面的定义就足够了:

如上所述,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建议使用编程的访问方式。

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

你可能感兴趣的文章
JSX
查看>>
LeetCode 之 JavaScript 解答第239题 —— 滑动窗口最大值(Sliding Window Maximum)
查看>>
一个项目带你走进产品经理的世界(2)需求分析
查看>>
css经典布局——圣杯布局
查看>>
Java基础系列五
查看>>
css3常用属性总结(1)
查看>>
SQLServer之创建索引视图
查看>>
面试集锦(六)数据结构(2)
查看>>
VimWiki的一些技巧
查看>>
GMQT全球通用积分重磅推出
查看>>
spring cloud构建互联网分布式微服务云平台-路由网关(zuul)
查看>>
Parasoft dotTEST(10.4.1)更新亮点——在.NET应用程序中构建安全性
查看>>
混沌工程究竟用来解决什么问题?
查看>>
如何写好一片文章
查看>>
vue项目前后端实现
查看>>
BCH升级日期将至,社区组织开始为11月“硬分叉”做准备
查看>>
2018最新版直播系统源码:功能和步骤详解
查看>>
没错,我就是要吹爆Angular
查看>>
Andoid屏幕适配终极手段(小编用过最得劲的dp适配)
查看>>
一张图带你了解Aspose 2019年的产品线
查看>>