💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 4.6 自定义 bean 的性质 ### 4.6.1 生命周期回调 为了和容器管理 bean 的生命周期进行交互,你可以实现 Spring 的 InitializingBean 和 DisposableBean 接口。容器为前者调用 afterPropertiesSet()方法,为后者调 用 destroy()方法在 bean 的初始化和销毁阶段允许 bean 执行特定的动作。你也可以和容 器达到相同的整合,而不需要通过使用初始化方法和销毁方法的对象定义元数据来耦合你的类到 Spring 的接口中。 从内部来说,Spring Framework 使用了 BeanPostProcessor 的实现来处理任何回调 接口,它可以发现并调用合适的方法。如果你需要自定义特性或其它生命周期行为,Spring 不会提供开箱,你可以自行实现 BeanPostProcessor 接口。要获取更多信息,可以参考 4.8 节,“容器扩展点”。 除了初始化和销毁回调,Spring 管理的对象也会实现 Lifecycle 接口,所以那些对象 可以参与到启动和关闭过程,来作为容器自己的生命周期。 生命周期回调接口在本节中来说明。 #### 4.6.1.1 初始化回调 org.springframework.beans.factory.InitializingBean 接口允许 bean 在 所有必须的属性被容器设置完成之后来执行初始化工作。InitializingBean 接口中仅仅 指定了一个方法: ``` void afterPropertiesSet() throws Exception; ``` 推荐不要使用 InitializingBean 接口,因为它不必要地将代码耦合到 Spring 中。 另外,可以指定一个 POJO 初始化方法。在基于 XML 配置元数据的情形中,你可以使用 init-method 属性来指定签名为无返回值,无参数的方法的名称。比如,下面的定义: ``` <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> public class ExampleBean { public void init() { // 做一些初始化工作 } } ``` 这和下面的做法是完全相同的: ``` <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { //做一些初始化工作 } } ``` 但是没有耦合任何代码到 Spring 中。 #### 4.6.1.2 销毁回调 实现 org.springframework.beans.factory.DisposableBean 接口,当容器 包含的 bean 被销毁时,允许它获得回调。DisposableBean 接口中仅仅有一个方法: ``` void destroy() throws Exception; ``` 推荐不要使用 DisposableBean 接口,因为它不必要地将代码耦合到 Spring 中。另外, 可以指定一个由 bean 支持的通用的方法。使用基于 XML 配置元数据,你可以使用`<bean/>` 元素中的 destroy-method 属性。比如,下面的定义: ``` <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> public class ExampleBean { public void cleanup() { // 做一些销毁工作 (比如释放连接池的连接) } } ``` 这和下面的做法是完全相同的: ``` <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> public class AnotherExampleBean implements DisposableBean { public void destroy() { // 做一些销毁工作 (比如释放连接池的连接) } } ``` 但是没有耦合代码到 Spring 中。 #### 4.6.1.3 默认的初始化和销毁方法 当使用 Spring 指定的 InitializingBean 和 DisposableBean 回调接口来编写初 始化和销毁方法回调时,典型地做法就是编写名称为 init(),initialize(),dispose() 等方法.理想的情况,这样的生命周期回调方法的名称在一个项目中是标准化的,那么所有 的开发人员可以使用相同的方法名称来保证一致性。 你可以配置 Spring 容器在每个 bean 中来查看命名的初始化和销毁回调方法名称。这就 是说,作为应用程序开发人员,你可以编写应用类并且使用初始化回调方法 init(),而不 需要在每个 bean 的定义中来配置 init-method="init"属性。当 bean 被创建时(并按 照前面描述的标准的生命周期回调合约),Spring 的 IoC 容器调用这个方法。这个特性也对 初始化和销毁方法回调强制执行了一致的命名约定。 假设你的初始化回调方法命名为 init(),销毁回调方法命名为 destroy()。那么你 就可以按照如下示例来装配类: ``` public class DefaultBlogService implements BlogService { private BlogDao blogDao; public void setBlogDao(BlogDao blogDao) { this.blogDao = blogDao; } // 这是(毋庸置疑的)初始化回调方法 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 类有这样一个方法,那么它就会在合适的时间被调用。 相似地(也就是在 XML 中 ), 你 可 以 使 用 顶 层 元 素 `<beans/>` 的 default-destroy-method 属性来配置销毁回调方法。 在已经存在的 bean 类中,有命名差异的回调方法,你可以指定(也就是在 XML 中) `<bean/>`本身的 init-method 和 destroy-method 属性来覆盖默认的方法。 Spring 容器保证了在给 bean 提供所有的依赖后,配置的初始化回调方法会立即被调用。 因此初始化回调也被称为原始的 bean 引用,也就意味着 AOP 的拦截器等尚未应用到 bean 中。目标 bean 首先被完全创建,之后 AOP 代理(比方说)和它的拦截器链就会被应用上。 如果目标 bean 和代理分开来定义了,你的代码仍然可以绕开代理和原始目标 bea n 来交互。 因此,引用拦截器到初始化方法中会是不一致的,因为这么来做了会和它的代理/拦截耦合 目标 bean 的合生命周期,当你的代码直接和原始目标 bean 交互时,会留下奇怪的语义。 #### 4.6.1.4 组合生命周期机制 在 Spring 2.5 中,对控制 bean 的生命周期行为有三种选择:InitializingBean(4.6.1.1节)和 DisposableBean(4.6.1.2 节)回调接口;自定义 init()和 destroy()方法; @PostConstruct 和@PreDestroy 注解(4.9.6 节)。你可以组合这些机制来控制给定的 bean。 > ![](img/note.gif) > 注意 > 如果对一个 bean 配置了多个生命周期机制,并且每种机制都用不同的方法名称来配置, 那么每个配置方法就会以下面列出的顺序来执行。然而,如果配置了相同的方法名称,比如说,init()对于初始化方法,多于一个生命周期机制,那么这个方法就执行一次,这在前 面章节解释过了。 相同的 bean 配置了不同初始化方法的多个生命周期机制,那么就按下面顺序调用: * 使用@PostConstruct 注解的方法 * 由 InitializingBean 回调接口定义的 afterPropertiesSet()方法 自定义配置的 init()方法 销毁方法也是同样顺序调用: * 使用@PreDestroy 注解的方法 * 由 DisposableBean 回调接口定义的 destroy ()方法 * 自定义配置的 destroy ()方法 #### 4.6.1.5 启动和关闭回调 Lifecycle 接口定义了对于任意有它自己生命周期需求(比如,开始和结束一些后台 进程)对象的必要方法: ``` public interface Lifecycle { void start(); void stop(); boolean isRunning(); } ``` 任何 Spring 管理的对象可以实现这个接口。那么,当 ApplicationContext 本身启动和关 闭时,它会级联那些定义在上下文中,对所有生命周期实现的调用。它会委托给 LifecycleProcessor 来做: ``` public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); } ``` 要注意 LifecycleProcessor 本身是扩展了 Lifecycle 接口。而且它添加了两个 方法来对上下文的刷新和关闭做出反应。 启动和关闭调用的顺序是很重要的。如果一个“依赖”关系存在于两个任意对象间,那 么需要依赖的一方会在它的依赖启动之后启动,并且在它的依赖关闭之前停止。然而,有时 直接的依赖关系是未知的。你可能仅仅知道确定类型的对象应该优先于另外一种类型对象的启动。在那些情形下,SmartLifecycle 接口定义了另外一种选择,命名为 getPhase() 方法,在它的父接口 Phased 中来定义。 ``` public interface Phased { int getPhase(); } public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); } ``` 当 启 动 时 , 最 低 层 的 对 象 首 先 启 动 , 当 停 止 时 , 是 相 反 的 。 因 此 , 实 现 了 SmartLifecycle 接口,并且 getPhase()方法返回 Integer.MIN_VALUE 的对象就会在最 先开始和最后停止的中间。在范围的另外一端,Integer.MIN_VALUE 的层次值表示对象 应该最后启动并且最先停止(可能是因为它取决于其它进程的执行)。当考虑层次值时,知道任意“普通”对象并且没有实现 Lifecycle 接口的默认的层次是 0,这也是很重要的。 因此,任意负的层次值就表示对象应该在那些标准组件之前启动(在它们之后停止),对于 任意正的层次值,反之亦然。 正如你看到由 SmartLifecycle 定义的停止方法接收回调。任何实现类必须在实现类关闭进程完成之后,调用回调方法 run()。这使得必要时可以进行异步关闭,因为默认的 LifecycleProcessor 接口的实现,DefaultLifecycleProcessor,,会等待每个阶 段一组对象来调用回调的超时值。每个阶段的默认超时是 30 秒。你可以在上下文中定义名 为“lifecycleProcessor”的 bean 来覆盖默认的生命周期进程实例。如果你仅仅想去修改超时 时间,那么定义下面的 bean 就足够了: ``` <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProc essor"> <!-- 毫秒数的超时时间 --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean> ``` 正如所提到的,LifecycleProcessor 接口定义了刷新和关闭上下文的回调方法。后 者将驱动关闭进程,就好像明确地调用了 stop()方法,但是当上下文关闭时它才会发生。另 一方面,‘刷新’回调使得 SmartLifecycle bean 的另外一个特性可用。当上下文刷新时(在所有对象都被实例化和初始化后),才会调用回调方法,在那时,默认的生命周期进程 会检查每个 SmartLifecycle 对象的 isAutoStartup()方法返回的布尔值。如果是“true”,那时对象将会被启动,而不是等待明确的上下文调用或它自己的 start()方法(不像上下文刷新,对标准的上下文实现来说,上下文启动不会自动发生)。“阶段”值和任意“依 赖”关系会按照上面所描述的相同方式来决定启动的顺序。 #### 4.6.1.6 在非 Web 应用中,优雅地关闭 Spring IoC 容器 > ![](img/note.gif) > 注意 > 本节仅对非 Web 应用来说。在相关 Web 应用程序关闭时,Spring 的基于 Web 的 ApplicationContext 实现已经有代码来优雅地关闭 Spring 的 Ioc 容器了。 如果你在非 Web 应用环境中使用 Spring 的 IoC 容器;比如,在富客户端桌面环境中; 你在 JVM 中注册了一个关闭的钩子。这么来做就保证了优雅地关闭并且在单例 bean 中调用相关销毁方法,那么所有资源都会被释放。当然,你必须正确配置并实现这些销毁回调方法。 要注册 一个 关闭 钩子, 可以 调用 AbstractApplicationContext 类中 声明的registerShutdownHook()方法: ``` import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationConte xt; public final class Boot { public static void main(final String[] args) throws Exception { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(new String []{"beans.xml"}); // 对上面的上下文添加一个关闭的钩子... ctx.registerShutdownHook(); // 这里运行应用程序... // 主方法退出,钩子在应用关闭前优先被调用... } } ``` ### 4.6.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 的控制反转的风格。应用上下文 的其它方法提供了访问文件资源,公共应用事件和访问消息资源的方法。这些补充特性为在 4.14 节,“应用上下文的补充功能”来说明。 在 Spring 2.5 中,自动装配是获取 ApplicationContext 引用的另外一种方式。“传 统的”constructor 和 byType 自动装配模式(在 4.4.5 节,“自动装配协作者”中说明的) 可以分别为构造方法参数或 setter 方法参数提供 ApplicationContext 类型的依赖。为了更大的灵活性,包括自动装配字段和多参数方法的能力,还有使用新的基于注解的自动装 配特性。如果字段,构造方法或者普通方法进行@Autowired 注解,而且你这么做了, ApplicationFactory 就会自动装配到期望 BeanFactory 类型的字段,构造方法参数或方法参数。要获取更多信息,可以参考 4.9.2 节,“@Autowired 和@Inject”。 当 应 用 上 下 文 创 建 实 现 了 org.springframework.beans.factory.BeanNameAware 接口的类时,这个类会被 提供一个在相关对象中定义的命名引用。 ``` public interface BeanNameAware { void setBeanName(string name) throws BeansException; } ``` 回 调 函 数 在 普 通 bean 的 属 性 被 填 充 之 后 并 且 在 如 InitializingBean 的 afterPropertiesSet 或自定义初始化方法的初始化回调之前被调用。 ### 4.6.3 其它 Aware 接口 除了上面讨论的 ApplicationContextAware 和 BeanNameAware,Spring 还提供了很多 Aware 接口,它们允许 bean 来告诉容器它们需要确定的基础的依赖。最重要的 Aware 接口在下面总结出来了-作为一个通用的规则,名称就很好地说明了依赖的类型: 表 4.4 Aware 接口 | 名称 | 注入的依赖 | 何处解释 | | --- | --- | --- | | ApplicationContextA ware | 声明 ApplicationContext | 4.6.2 节,“ ApplicationContextAware 和 BeanNameAware” | | ApplicationEventPub lisherAware | 包含在 ApplicationContext内的事件发布 | 4.14 节,“应用上下文的补充 功能” | | BeanClassLoaderAware | 用于加载 bean 类的类加载器 | 4.3.2 节,“实例化 bean” | | BeanFactoryAware | 声明 BeanFactory | 4.6.2 节,“ ApplicationContextA ware 和 BeanNameAware” | | BeanNameAware | 声明 bean 的名称 | 4.6.2 节,“ ApplicationContextA ware 和 BeanNameAware”| | BootstrapContextAwa re | 容器运行的资源适配器 BootstrapContext。典型地 是 仅 仅 在 JCA 感 知 的 ApplicationContext 中可 用。| 第 24 章,JCA CCI | | LoadTimeWeaverAware | 在加载时为处理类定义的织入 器 | 8.8.4 节,“Spring Framework中使用 Aspect J 进行加载时织 入”| | MessageSourceAware | 解析消息配置的策略(支持参数化和国际化)| 4.14 节,“应用上下文的补充功能”| | NotificationPublisherAware| Spring JMX 通知发布 | 23.7 节,“通知” | | PortletConfigAware | 容 器 运 行 的 当 前 的PortletConfig。仅在感知 Web 的 Spring 的 ApplicationContext 中有 效| 第 19 章,Portlet MVC 框架 | | PortletContextAware | 容 器 运 行 的 当 前 的PortletContext。仅在感知 Web 的 Spring 的 ApplicationContext 中有 效| 第 19 章,Portlet MVC 框架 | | ResourceLoaderAware | 为低级别的资源访问配置的加载器| 第 5 章,资源 | | ServletConfigAware | 容 器 运 行 的 当 前 的ServletConfig。仅在感知 Web 的 Spring 的ApplicationContext 中有效| 第 16 章,Web MVC 框架 | | ServletContextAware | 容 器 运 行 的 当 前 的ServletContext。仅在感知 Web 的 Spring 的 ApplicationContext 中有 效| 第 16 章,Web MVC 框架 | 请再次注意绑定你的代码到 Spring API 的这些接口的使用,并不再遵循控制反转风格。 因此,它们被推荐使用到基础的 bean 中,那些 bean 需要编程式地访问容器。