接上篇:Spring 核心技术(6)

version 5.1.8.RELEASE

1.6 定制 Bean 的特性

Spring Framework 提供了许多可用于自定义 bean 特性的接口。本节将它们分组如下:

1.6.1 生命周期回调

要与容器的 bean 生命周期管理进行交互,可以实现 Spring InitializingBeanDisposableBean 接口。容器调用前者 afterPropertiesSet() 和后者的 destroy() 以便让 bean 在初始化和销毁 ​​bean 时执行某些操作。

JSR-250 @PostConstruct@PreDestroy 注解通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着你的 bean 不会耦合到特定的 Spring 接口。有关详细信息,请参阅使用 @PostConstruct 和 @PreDestroy

在内部,Spring Framework 使用 BeanPostProcessor 实现来处理它可以找到的任何回调接口并调用适当的方法。如果你需要自定义其他 Spring 默认不提供的功能或生命周期行为,可以自己实现 BeanPostProcessor。更多信息请参阅容器扩展点

除了初始化和销毁​​回调之外,Spring 管理的对象还可以实现 Lifecycle 接口,以便这些对象可以参与容器自身的生命周期驱动的启动和关闭过程。

本节描述了生命周期回调接口。

初始化回调

org.springframework.beans.factory.InitializingBean 接口允许 bean 在容器完全设置其所有必要属性后进行初始化工作。InitializingBean 接口规定了一个方法:

  1. void afterPropertiesSet() throws Exception;

我们建议不要使用 InitializingBean 接口,因为并不需要将代码耦合到 Spring。此外,我们建议使用 @PostConstruct 注解或指定 POJO 初始化方法。对于基于 XML 的配置元数据,可以使用 init-method 属性指定具有无返回值无参数签名的方法的名称。使用 Java 配置时,可以使用 @Bean 注解的 initMethod 属性。请参阅接收生命周期回调。请看以下示例:

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

前面的示例与以下示例几乎完全相同(包含两个列表):

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

但是,上面两个示例中,第一个示例没有将代码耦合到 Spring。

销毁回调

实现 org.springframework.beans.factory.DisposableBean 接口允许 bean 在包含它的容器被销毁时获得回调。DisposableBean 接口规定了一个方法:

  1. void destroy() throws Exception;

我们建议不要使用 DisposableBean 回调接口,因为并不需要将代码耦合到 Spring。此外,我们建议使用 @PreDestroy 注解或指定 bean 定义支持的泛型方法。使用基于 XML 的配置元数据时,可以使用 <bean/> 元素的 destroy-method 属性。使用 Java 配置时,可以使用 @BeandestroyMethod 属性。请参阅接收生命周期回调。请看以下定义:

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

前面的定义与以下定义几乎完全相同:

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

但是,上面两个示例中,第一个示例没有将代码耦合到 Spring。

你可以为 <bean> 元素的 destroy-method 属性指定一个特殊(推测)值,该值指示 Spring 自动检测特定 bean 类的公共 closeshutdown 方法。(任何实现 java.lang.AutoCloseable或java.io.Closeable 的类都可以进行匹配。)你还可以在 <beans> 元素的 default-destroy-method 属性上设置此特殊(推测)值,以将此行为应用于整组 bean(请参阅默认初始化和销毁​​方法)。请注意,这是使用 Java 配置时的默认行为。

默认初始化和销毁​​方法

当你不使用 Spring 特定的 InitializingBeanDisposableBean 回调接口编写初始化和销毁回调方法时,通常会使用 init()initialize()dispose() 等来命名方法。理想情况下,此类生命周期回调方法的名称在项目中是一致的的,以便所有开发人员使用相同的方法名称并确保一致性。

你可以将Spring容器配置为在每个 bean 上“查找”已经命名的初始化和销毁回调方法的名称。这意味着,作为应用程序开发人员,你可以编写应用程序类并使用 init() 作为初始化回调 ,而无需为每个 bean 定义配置 init-method="init" 属性。Spring IoC 容器在创建 bean 时调用该方法(根据前面描述的标准生命周期回调)。此功能还强制执行初始化和销毁​​方法回调的一致命名约定。

假设你的初始化回调方法已命名为 init() 并且您的销毁回调方法已命名为 destroy()。你的类类似于以下示例:

  1. public class DefaultBlogService implements BlogService {
  2. private BlogDao blogDao;
  3. public void setBlogDao(BlogDao blogDao) {
  4. this.blogDao = blogDao;
  5. }
  6. // this is (unsurprisingly) the initialization callback method
  7. public void init() {
  8. if (this.blogDao == null) {
  9. throw new IllegalStateException("The [blogDao] property must be set.");
  10. }
  11. }
  12. }

你可以在类似于以下内容的 bean 中使用该类:

  1. <beans default-init-method="init">
  2. <bean id="blogService" class="com.something.DefaultBlogService">
  3. <property name="blogDao" ref="blogDao" />
  4. </bean>
  5. </beans>

顶级 <beans/> 元素上存在 default-init-method 属性导致 Spring IoC 容器将 bean 类上叫做 init 的方法识别为初始化方法回调。当 bean 被创建和组装时,如果 bean 类具有这样的方法,则会在适当的时候调用它。

你可以在XML中同样通过使用顶级 <beans/> 元素上的 default-destroy-method 属性来配置销毁方法回调。

如果现有的 bean 类已经具有与约定一致的回调方法,则可以在 XML 中通过使用自身 <bean/>init-methoddestroy-method 属性指定方法名称来覆盖默认值。

Spring 容器可以保证在为 bean 提供所有依赖项后立即调用已配置的初始化回调。因此,初始化回调实在原始 bean 引用上调用的,这意味着 AOP 拦截器等尚未应用于 bean。首先完全创建目标 bean,然后应用的 AOP 代理(例如带有拦截器链)。如果目标 bean 和代理是分开定义的,那么你的代码甚至可以绕过代理与原始目标 bean 交互。因此,将拦截器应用于 init 方法是不合适的,因为这样做会将目标 bean 的生命周期耦合到其代理或拦截器,并在代码直接与原始目标 bean 交互时表现出奇怪的语义。

合并生命周期机制

从 Spring 2.5 开始,你有三个控制 bean 生命周期行为的选项:

如果为 bean 配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则每个配置的方法都按照此注释后列出的顺序执行。但是,如果为多个这些生命周期机制配置了相同的方法名称(例如,给初始化方法命名为 init()),如上 一节中所述,该方法将执行一次。

为同一个 bean 配置的多个不同的初始化方法时,执行顺序如下:

  • @PostConstruct 注解的方法
  • InitializingBean 定义的 afterPropertiesSet() 回调接口
  • 自定义配置的 init() 方法

销毁方法以相同的顺序调用:

  • @PreDestroy 注解的方法
  • DisposableBean 定义的 destroy() 回调接口
  • 自定义配置的 destroy() 方法

启动和关闭回调

Lifecycle 接口为任何具有自己的生命周期要求的对象(例如启动和停止某些后台进程)定义了基本方法:

  1. public interface Lifecycle {
  2. void start();
  3. void stop();
  4. boolean isRunning();
  5. }

任何 Spring 管理的对象都可以实现 Lifecycle 接口。然后,当 ApplicationContext 接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它将级联调用上下文中定义的 Lifecycle 的所有实现。它通过委托给 LifecycleProcessor 来实现,如下面的清单所示:

  1. public interface LifecycleProcessor extends Lifecycle {
  2. void onRefresh();
  3. void onClose();
  4. }

请注意,LifecycleProcessor 它本身是 Lifecycle 接口的扩展。它还添加了另外两种方法来响应刷新和关闭上下文。

请注意,常规的 org.springframework.context.Lifecycle 接口是显式启动和停止通知的简单协议,并不意味着在上下文刷新时自动启动。要对特定bean的自动启动(包括启动阶段)进行细粒度控制,请考虑实现 org.springframework.context.SmartLifecycle

此外,请注意,在销毁之前不保证能收到停止通知。在常规关闭时,所有 Lifecycle bean 在一般销毁回调开始之前首先收到停止通知。但是,在上下文生命周期中的热刷新或中止刷新尝试时,仅调用销毁方法。

启动和关闭调用的顺序非常重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,并且在其依赖之前停止。但是,有时,直接依赖性是未知的。你可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle 接口定义了另一个选项,即在其父级接口 Phased 上定义的方法 getPhase() 。以下内容展示了 Phased 接口的定义:

  1. public interface Phased {
  2. int getPhase();
  3. }

以下内容展示了 SmartLifecycle 接口的定义:

  1. public interface SmartLifecycle extends Lifecycle, Phased {
  2. boolean isAutoStartup();
  3. void stop(Runnable callback);
  4. }

启动时,具有最低层的对象首先开始。停止时,遵循相反的顺序。因此,实现 SmartLifecycle 的对象和 getPhase() 返回的 Integer.MIN_VALUE 将是第一个开始和最后一个停止的对象。在另外一个领域内,相位值 Integer.MAX_VALUE 将指示对象应该最后启动并首先停止(可能因为它依赖于正在运行的其他进程)。当考虑相位值,同样重要的是要知道,对于任何“正常”的没有实现 SmartLifecycleLifecycle 对象,默认值为 0。因此,任何负相位值都表示对象应该在这些标准组件之前启动(并在它们之后停止)。任何正相值都是相反的。

SmartLifecycle 定义的停止方法接受回调。任何实现必须在该实现的关闭过程完成后调用该回调的 run() 方法。这样就可以在必要时启用异步关闭,因为LifecycleProcessor 接口的默认实现 DefaultLifecycleProcessor 等待每个周期内的对象组的超时值来调用该回调。默认的每阶段超时为30秒。可以通过定义在上下文中命名为 lifecycleProcessor 的 bean 来覆盖缺省生命周期处理器实例 。如果只想修改超时时间,则定义以下内容就足够了:

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

如之前所述,LifecycleProcessor 接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程类似于显示调用 stop(),但它只在上下文关闭时发生。另一方面,'refresh' 回调启用了 SmartLifecycle bean的另一个功能 。刷新上下文时(在实例化并初始化所有对象之后),将调用该回调。此时,默认生命周期处理器检查每个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值 。如果是 true,那么对象是当时就开始的,而不是等待显式调用上下文或它自己的 start() 方法(与上下文刷新不同,上下文启动不会自动发生在标准上下文实现中)。phase 值与任何“依赖式”的关系确定了前面所述的启动顺序。

在非 Web 应用程序中优雅地关闭 Spring IoC 容器

本节仅适用于非 Web 应用程序。Spring 的基于 Web 的 ApplicationContext 实现已经具有相关的代码,可以在相关 Web 应用程序关闭时正常关闭 Spring IoC 容器。

如果在非 Web 应用程序环境中使用 Spring 的 IoC 容器(例如,在客户机桌面环境中),请使用 JVM 注册关闭钩子。这样做可确保正常关闭并在单例 bean 上调用相关的销毁方法,以便释放所有资源。你必须正确配置和实现这些销毁回调。

要注册关闭钩子,请调用 ConfigurableApplicationContext 接口上声明的 registerShutdownHook() 方法,如以下示例所示:

  1. import org.springframework.context.ConfigurableApplicationContext;
  2. import org.springframework.context.support.ClassPathXmlApplicationContext;
  3. public final class Boot {
  4. public static void main(final String[] args) throws Exception {
  5. ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
  6. // add a shutdown hook for the above context...
  7. ctx.registerShutdownHook();
  8. // app runs here...
  9. // main method exits, hook is called prior to the app shutting down...
  10. }
  11. }

1.6.2 ApplicationContextAwareBeanNameAware

ApplicationContext 创建实现 org.springframework.context.ApplicationContextAware 接口的对象实例时,将为 ApplicationContext 提供对该实例的引用。以下清单显示了 ApplicationContextAware 接口的定义:

  1. public interface ApplicationContextAware {
  2. void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
  3. }

因此,bean 可以通过 ApplicationContext 接口或通过将引用转换为此接口的已知子类(例如公开其他功能的 ConfigurableApplicationContext,)以编程方式操纵创建它们的 ApplicationContext。一种用途是对其他 bean 进行编程检索。有时这种功能很有用。但是,一般情况下应该避免使用它,因为协作者作为属性提供给 bean 时会将代码耦合到 Spring 并且不遵循控制反转风格。ApplicationContext 的其他方法提供对文件资源的访问,发布应用程序事件以及访问 MessageSource。这些附加功能在ApplicationContext 的附加功能中描述 。

从 Spring 2.5 开始,自动装配是另一种获取 ApplicationContext 引用的方法。“传统” constructorbyType 自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或 setter 方法参数提供 ApplicationContext 类型的依赖。为了获得更大的灵活性,以及自动装配字段和多参数方法的能力,请使用基于注释的新自动装配功能。如果相关的字段,构造函数或方法带有 @Autowired 注解且需要 ApplicationContext 类型,ApplicationContext 会自动装入一个字段,构造函数参数或方法参数。有关更多信息,请参阅使用 @Autowired

ApplicationContext 创建实现 org.springframework.beans.factory.BeanNameAware 接口的类时,将为该类提供其关联对象定义中对应名称的引用。以下清单显示了 BeanNameAware 接口的定义:

  1. public interface BeanNameAware {
  2. void setBeanName(String name) throws BeansException;
  3. }

回调在普通 bean 属性设置之后,但在一个初始化回调之前,例如 InitializingBeanafterPropertiesSet 或自定义的初始化方法。

1.6.3 其他 Aware 接口

除了 ApplicationContextAwareBeanNameAware (在之前讨论过),Spring 提供了广泛的 Aware 回调让 bean 向容器指出他们需要一定的基础设依赖。作为一般规则,名称表示依赖关系类型。下表总结了最重要的 Aware 接口:

名称 注入依赖 描述
ApplicationContextAware 声明 ApplicationContext ApplicationContextAwareBeanNameAware
ApplicationEventPublisherAware 包含 ApplicationContext 的时间发布者 ApplicationContext 的其他功能
BeanClassLoaderAware 用于加载bean类的类加载器 实例化 Bean
BeanFactoryAware 声明 BeanFactory ApplicationContextAwareBeanNameAware
BeanNameAware 声明 bean 的名称 ApplicationContextAwareBeanNameAware
BootstrapContextAware 容器运行的资源适配器 BootstrapContext。通常仅在支持 JCA 的 ApplicationContext 实例中可用 JCA CCI
LoadTimeWeaverAware 定义在加载时用于处理类定义的 weaver 在 Spring 框架中使用 AspectJ 进行加载时织入
MessageSourceAware 用于解析消息的已配置策略(支持参数化和国际化) ApplicationContext 的其他功能
NotificationPublisherAware Spring JMX 通知发布者 通知
ResourceLoaderAware 用于对低层次资源进行访问的配置加载器 资源
ServletConfigAware 当前容器运行的 ServletConfig。仅在支持 Web 的 ApplicationContext 中有效 Spring MVC
ServletContextAware 当前容器运行的 ServletContext。仅在支持 Web 的 ApplicationContext 中有效 Spring MVC

请再次注意,使用这些接口会使你的代码与 Spring API 耦合,而且不会遵循 IOC 规范。因此,我们建议将它们用于需要以编程方式访问容器的基础架构 bean。

Spring 核心技术(7)的更多相关文章

  1. Spring核心技术(八)——Spring自动装载的注解

    本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class Sim ...

  2. Spring核心技术(六)——Spring中Bean的生命周期

    前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和Dispos ...

  3. Spring核心技术(五)——Spring中Bean的作用域

    前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了该如何创建Bean实例.这些具体创建的过程是很重 ...

  4. Spring核心技术(四)——Spring的依赖及其注入(续二)

    前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也 ...

  5. Spring 核心技术(3)

    接上篇:Spring 核心技术(2) version 5.1.8.RELEASE 1.4 依赖 典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean).即使是最简单的应用程序也是 ...

  6. Spring 核心技术(2)

    接上篇:Spring 核心技术(1) version 5.1.8.RELEASE 1.3 Bean概述 Spring IoC 容器管理一个或多个bean,他们都是根据提供的配置元数据(例如 XML 中 ...

  7. Spring 核心技术(4)

    接上篇:Spring 核心技术(3) version 5.1.8.RELEASE 1.4.2 依赖关系及配置详情 如上一节所述,你可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者 ...

  8. Spring 核心技术(5)

    接上篇:Spring 核心技术(4) version 5.1.8.RELEASE 1.4.5 自动装配协作者 Spring 容器可以自动连接协作 bean 之间的关系.你可以让 Spring 通过检查 ...

  9. Spring 核心技术(6)

    接上篇:Spring 核心技术(5) version 5.1.8.RELEASE 1.5 Bean 作用域 创建 bean 定义时,你创建了一种用于创建 bean 定义中定义的类实例的方法.bean定 ...

随机推荐

  1. 了解下Java中的Serializable

      在项目中也写了不少的JavaBean,也知道大多的JavaBean都实现了Serializable接口,也知道它的作用是序列化,序列化就是保存,反序列化就是读取.主要体现在这两方面: 1.存储.将 ...

  2. web设计_5_自由的框式组件

    1. CSS3 border-radius 圆角矩形框 圆角矩形框组件是页面布局中常常用到的,利用CSS3的border-radius可非常方便的创建. 并且在横向纵向上面都有很好的扩展性和灵活性. ...

  3. Linux学习笔记03

    一.Linux常见命令 file:查看文件类型(windows用扩展名识别文件类型) 语法:file [options] [args] -b:显示结果时,不显示文件名 -c:显示执行file命令的执行 ...

  4. hibernate 命名策略

    对于Java开发人员,Hibernate 3 annotations提供了非常好的方式来展示域分层.你可以很轻松的通过Hibernate自动生成需要的数据库架构,带有完整的SQL脚本.然而回到现实世界 ...

  5. 前端笔记之React(八)上传&图片裁切

    一.上传 formidable天生可以处理上传的文件,非常简单就能持久上传的文件. 今天主要讲解的是,前后端的配合套路. 上传分为同步.异步.同步公司使用非常多,异步我们也会讲解. 1.1 先看一下a ...

  6. 跟着大彬读源码 - Redis 9 - 对象编码之 三种list

    目录 1 ziplist 2 skiplist 3 quicklist 总结 Redis 底层使用了 ziplist.skiplist 和 quicklist 三种 list 结构来实现相关对象.顾名 ...

  7. 让techempower帮你通讯服务框架的性能

    在编写服务应用框架的时候一般都需要进行性能测试,但自己测试毕竟资源受限所以很难做更高性能上的测试.其实GitHub上有一个项目可以让开发人员提交自己的框架服务代码然后进行一个标准测试:现在已经有上百个 ...

  8. 想成为顶尖 Java 程序员?请先过了下面这些技术问题。

    一.数据结构与算法基础 说一下几种常见的排序算法和分别的复杂度. 用Java写一个冒泡排序算法 描述一下链式存储结构. 如何遍历一棵二叉树? 倒排一个LinkedList. 用Java写一个递归遍历目 ...

  9. powerdesign进军(一)--安装破解

    目录 资源下载地址 安装powerdesign 破解powerdesign 汉化 总结 IT行业不管是web开发还是客户端开发都需要数据库,因为现在是数据时代能够拥有强大的数据就是行业的王者.目前一些 ...

  10. Flink 源码解析 —— JobManager 处理 SubmitJob 的过程

    JobManager 处理 SubmitJob https://t.zsxq.com/3JQJMzZ 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1 ...