定制Bean的特性

58 阅读10分钟

​核心概念:​
Spring Framework 提供了一系列接口,允许您定制 Bean 的特性。这些接口主要分为以下几类:生命周期回调、Aware 接口 和 Bean 作用域(本页主要涵盖前两者)。

​一、生命周期回调 (Lifecycle Callbacks)​

Spring 容器管理 Bean 的生命周期,允许您通过回调方法在 Bean 初始化和销毁时执行特定操作。

  1. ​实现方式:​

    • ​Spring 专用接口:​​ InitializingBean 和 DisposableBean

      • InitializingBean 要求实现 void afterPropertiesSet() throws Exception; 方法。容器在设置完 Bean 的所有必要属性后调用此方法。
      • DisposableBean 要求实现 void destroy() throws Exception; 方法。当包含该 Bean 的容器销毁时调用此方法。
      • ​注意:​​ Spring ​​不推荐​​使用这两个接口,因为它们会将代码与 Spring API 耦合。
    • ​JSR-250 标准注解 (推荐):​

      • @PostConstruct: 标注在方法上,效果等同于 afterPropertiesSet(),用于初始化后操作。
      • @PreDestroy: 标注在方法上,效果等同于 destroy(),用于销毁前操作。
      • ​优点:​​ 使用标准注解解耦了代码与 Spring 特定接口,是现代 Spring 应用的​​最佳实践​​。
    • ​Bean 定义元数据:​​ init-method 和 destroy-method 属性。

      • ​XML 配置:​​ 在 <bean> 元素上指定 init-method="方法名" 和/或 destroy-method="方法名"。方法需是 public void 方法名() 形式(无参数)。

        • <bean id="exampleBean" class="com.example.Bean" init-method="myInit" destroy-method="myCleanup"/>
      • ​Java 配置 (@Bean):​​ 使用 @Bean(initMethod = "myInit", destroyMethod = "myCleanup")

      • ​优点:​​ 解耦,不依赖 Spring 特定接口或注解。

    • ​底层机制:​​ Spring 内部使用 BeanPostProcessor 实现来处理找到的所有回调接口并调用相应方法。如需自定义生命周期行为,可自行实现 BeanPostProcessor

  2. ​初始化回调详解 (@PostConstruct / init-method / afterPropertiesSet):​

    • 调用时机:在 Bean 的所有依赖项被注入(属性设置完成)​​之后​​。

    • @PostConstruct 和常规初始化方法在容器的​​单例创建锁​​内执行。Bean 实例只有在 @PostConstruct 方法返回后才被视为​​完全初始化​​并可发布给其他对象使用。

    • ​重要限制:​​ 这些初始化方法​​仅​​应用于验证配置状态或基于给定配置准备数据结构。​​禁止​​在其中进行访问外部 Bean 的复杂活动,否则可能导致​​初始化死锁​​。

    • ​昂贵初始化操作:​​ 如果需要在初始化后执行耗时操作(如异步数据库准备),应选择:

      • 实现 SmartInitializingSingleton.afterSingletonsInstantiated() 方法。
      • 监听上下文刷新事件:实现 ApplicationListener<ContextRefreshedEvent> 或使用 @EventListener(ContextRefreshedEvent.class)
      • 这些方式在所有常规单例初始化​​完成后​​执行,​​不受​​单例创建锁限制。
      • 替代方案:实现 (Smart)Lifecycle 接口以集成到容器的整体生命周期管理中(见下文)。
  3. ​销毁回调详解 (@PreDestroy / destroy-method / destroy):​

    • 调用时机:当包含该 Bean 的容器被销毁时。

    • Spring 支持推断销毁方法:自动检测 Bean 类上的 public void close() 或 public void shutdown() 方法。

      • Java 配置 (@Bean) 默认支持此推断,且会自动匹配 java.lang.AutoCloseable 或 java.io.Closeable 实现。
      • XML 配置:将 <bean> 的 destroy-method 属性设置为特殊值 (inferred) 以启用自动检测。
      • XML 默认配置:在顶级 <beans> 元素上设置 default-destroy-method="(inferred)" 可将此行为应用于该文件中的所有 Bean。
    • ​扩展的关闭阶段:​​ 实现 Lifecycle 接口可以在任何单例 Bean 的销毁方法被调用之前接收一个“停止”信号。实现 SmartLifecycle 可以进行有时间限制的停止步骤(容器会等待该步骤完成再执行销毁方法)。

  4. ​默认初始化和销毁方法:​

    • 可以在顶级 <beans> 元素上设置 default-init-method="init" 和 default-destroy-method="destroy" 属性。
    • 作用:容器会检查该文件中定义的​​每个​​ Bean 类,如果存在指定名称的方法(如 init() 或 destroy()),就会在相应生命周期阶段调用它。
    • ​优点:​​ 促进项目内命名一致性,减少重复配置。
    • ​覆盖默认:​​ 如果某个 Bean 的回调方法命名与默认不同,可以在其 <bean> 定义中显式使用 init-method 或 destroy-method 属性指定方法名。
  5. ​回调执行顺序 (组合机制):​

    • 如果一个 Bean 配置了多种生命周期机制且方法名不同,则按以下​​固定顺序​​执行:

      • ​初始化:​

        1. 用 @PostConstruct 注解的方法
        2. InitializingBean.afterPropertiesSet()
        3. 自定义配置的初始化方法 (如 init-method 指定)
      • ​销毁:​

        1. 用 @PreDestroy 注解的方法
        2. DisposableBean.destroy()
        3. 自定义配置的销毁方法 (如 destroy-method 指定)
    • 如果为同一个生命周期事件(如初始化)配置了多个机制但​​方法名相同​​(如都叫 init()),该方法​​只会被执行一次​​。

  6. ​启动和停止回调 (Lifecycle 接口):​

    • Lifecycle 接口 (void start(); void stop(); boolean isRunning();) 适用于拥有自身生命周期需求的对象(如启停后台进程)。

    • ​传播机制:​​ 当 ApplicationContext 本身收到启动 (start()) 或停止 (stop()) 信号(例如运行时停止/重启场景)时,它会将这些调用​​级联​​传播给其上下文内定义的所有 Lifecycle 实现。这是通过委托给 LifecycleProcessor 实现的。

    • LifecycleProcessor 接口扩展了 Lifecycle,增加了 void onRefresh(); void onClose(); 方法,用于响应上下文的刷新和关闭事件。

    • ​重要:​​ 基本的 Lifecycle 接口只提供显式的启动/停止通知,​​不​​意味着在上下文刷新时自动启动。

    • ​精细控制 (SmartLifecycle 接口):​​ 对于对自动启动和优雅停止(包括启动和停止阶段)的精细控制,应实现 SmartLifecycle 接口。它扩展了 Lifecycle 和 Phased 接口。

      • Phased 接口 (int getPhase();):定义阶段 (Phase) 值。

      • SmartLifecycle 增加方法:boolean isAutoStartup(); void stop(Runnable callback);

      • ​执行顺序控制 (Phase):​

        • ​启动 (start()):​​ phase 值​​最低​​的 Bean ​​最先​​启动。
        • ​停止 (stop()):​​ phase 值​​最高​​的 Bean ​​最先​​停止(与启动顺序​​相反​​)。
        • ​默认值:​​ 未实现 SmartLifecycle 的普通 Lifecycle Bean 的 phase 默认为 0
        • ​负值:​​ 启动更早 (<0),停止更晚 (<0 意味着在顺序中更靠后停止)。
        • ​正值:​​ 启动更晚 (>0),停止更早 (>0 意味着在顺序中更靠前停止)。
      • ​优雅停止 (stop(Runnable callback)):​​ 实现者必须在自己的停止过程​​完成后​​调用 callback.run()。这支持必要的异步关闭。默认的 DefaultLifecycleProcessor 会等待每个阶段内的所有对象调用此回调,默认等待​​每个阶段 30 秒​​。

        • ​配置超时:​​ 可以通过在上下文中定义一个名为 lifecycleProcessor 的 Bean 来覆盖默认处理器或修改超时。

          • <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <property name="timeoutPerShutdownPhase" value="10000"/> <!-- 10秒超时 --> </bean>
      • ​自动启动 (isAutoStartup()):​​ 在上下文刷新完成后(所有 Bean 实例化并初始化后),如果 SmartLifecycle Bean 的 isAutoStartup() 返回 true,则该 Bean 会在此时​​自动启动​​,而无需显式调用上下文或其自身的 start() 方法。启动顺序同样由 phase 值和依赖关系决定。

  7. ​在非 Web 应用中优雅关闭 Spring IoC 容器:​

    • ​仅适用于非 Web 应用。​​ (Spring Web 应用的 ApplicationContext 已有内置关闭逻辑)

    • 在非 Web 环境(如桌面应用)中使用 Spring IoC 容器时,需要向 JVM ​​注册一个关闭钩子 (Shutdown Hook)​​ 来确保容器优雅关闭,并调用单例 Bean 的销毁方法释放资源。

    • ​注册方法:​​ 调用 ConfigurableApplicationContext 的 registerShutdownHook() 方法。

      • Java 示例:

        public final class Boot {
            public static void main(final String[] args) throws Exception {
                ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
                ctx.registerShutdownHook(); // 注册关闭钩子
                // ... 应用运行逻辑 ...
                // main 方法退出时,关闭钩子会被调用,触发容器优雅关闭
            }
        }
        
      • Kotlin 示例:

        fun main() {
            val ctx = ClassPathXmlApplicationContext("beans.xml")
            ctx.registerShutdownHook() // 注册关闭钩子
            // ... 应用运行逻辑 ...
            // main 方法退出时,关闭钩子会被调用,触发容器优雅关闭
        }
        
  8. ​线程安全性与可见性 (Thread Safety and Visibility):​

    • Spring 核心容器以​​线程安全的方式发布创建的单例实例​​,通过单例锁保护访问并保证对其他线程的可见性。
    • ​初始化状态可见性:​​ 应用提供的 Bean 类无需关心其初始化状态的可见性问题。只要配置字段仅在初始化阶段被修改,它们​​无需​​标记为 volatile。Spring 提供的可见性保证类似于 final 字段,即使对于该阶段可变的基于 setter 的配置状态也是如此。
    • ​初始化后修改:​​ 如果在 Bean 创建阶段及其后续的初始发布​​之后​​修改字段,则访问这些字段时需要将其声明为 volatile 或使用公共锁进行保护。
    • ​单例访问:​​ 在单例 Bean 实例(如控制器、仓库)中对这种配置状态进行并发访问,在容器完成安全初始发布后是​​完全线程安全​​的。这包括在常规单例锁内处理的 FactoryBean 实例。
    • ​销毁与运行时状态:​​ 销毁回调期间的配置状态仍然是线程安全的。但在初始化和销毁之间累积的任何​​运行时状态​​,应按照常规 Java 准则保存在线程安全的结构中(或对于简单情况使用 volatile 字段)。更深入的生命周期集成(如 SmartLifecycle 中的 Runnable 字段)需要声明为 volatile
    • ​生命周期顺序与状态:​​ 虽然常规回调遵循顺序(如 start() 保证在初始化完成后调用,stop() 在 start() 之后),但在“在销毁前停止”的常规安排中存在特殊情况:建议任何此类 Bean 的内部状态也应允许在​​没有前置 stop() 的情况下立即响应 destroy() 回调​​,因为这在引导取消后的异常关闭或由另一个 Bean 导致的停止超时情况下可能发生。

​二、ApplicationContextAware 和 BeanNameAware​

  1. ​ApplicationContextAware:​

    • 如果一个对象实现了 org.springframework.context.ApplicationContextAware 接口,当 ApplicationContext 创建它时,会通过 setApplicationContext(ApplicationContext applicationContext) 方法将自身引用注入给该对象。
    • 用途:允许 Bean 以编程方式操作创建它的 ApplicationContext(例如获取其他 Bean、访问资源、发布事件等)。
    • ​注意:​​ 这种方式将代码与 Spring API 耦合,违反了控制反转(IoC)原则(依赖应作为属性注入)。​​通常建议避免使用​​,除非是基础架构 Bean 确实需要编程式容器访问。
    • ​替代方案:​​ 使用​​自动装配 (Autowiring)​​。可以通过构造器、setter 方法、字段或方法参数自动装配 ApplicationContext 类型的依赖(配合 @Autowired 注解是更灵活的方式)。
  2. ​BeanNameAware:​

    • 如果一个对象实现了 org.springframework.beans.factory.BeanNameAware 接口,当 ApplicationContext 创建它时,会通过 setBeanName(String name) 方法将其在 Bean 定义中配置的名称注入给该对象。
    • 调用时机:在 Bean 的普通属性被设置之后,但在任何初始化回调(如 InitializingBean.afterPropertiesSet() 或自定义 init-method)​​之前​​。

​三、其他 Aware 接口 (Other Aware Interfaces)​

除了 ApplicationContextAware 和 BeanNameAware,Spring 还提供了广泛的 Aware 回调接口,让 Bean 向容器表明它们需要特定的基础设施依赖。接口名称通常表明了依赖类型。

接口名称 (Name)注入的依赖 (Injected Dependency)说明 (Explained in...)
​ApplicationContextAware​声明该 Bean 的 ApplicationContext本页
​ApplicationEventPublisherAware​封闭 ApplicationContext 的事件发布器ApplicationContext 的附加功能
​BeanClassLoaderAware​用于加载 Bean 类的类加载器实例化 Beans
​BeanFactoryAware​声明该 Bean 的 BeanFactoryBeanFactory API
​BeanNameAware​声明该 Bean 的名称本页
​LoadTimeWeaverAware​用于在加载时处理类定义的定义织入器 (weaver)Spring Framework 中使用 AspectJ 进行加载时织入
​MessageSourceAware​配置的用于解析消息的策略(支持参数化和国际化)ApplicationContext 的附加功能
​NotificationPublisherAware​Spring JMX 通知发布器通知 (Notifications)
​ResourceLoaderAware​配置的用于低级访问资源的加载器资源 (Resources)
​ServletConfigAware​容器运行所在的当前 ServletConfig ​​(仅限 Web 环境)​Spring MVC
​ServletContextAware​容器运行所在的当前 ServletContext ​​(仅限 Web 环境)​Spring MVC
  • ​再次注意:​​ 使用这些 Aware 接口会将代码与 Spring API 绑定,不符合控制反转原则。​​建议仅将其用于需要编程式访问容器的基础设施 Bean。​