在上一篇文章中,我们了解了Spring IoC容器如何接管对象的创建和依赖注入,实现了松耦合。容器创建并管理的对象,我们称之为Bean。
但是,容器仅仅是创建Bean就够了吗?显然不是。我们还需要关心:
-
这个Bean在容器中应该存在多少个实例? (是全局唯一,还是每次请求都创建一个新的?)
-
这个Bean从创建到销毁会经历哪些阶段? (我们能否在特定阶段执行一些自定义逻辑?)
这就是我们今天要探讨的核心内容:Bean的作用域(Scope) 和 Bean的生命周期(Lifecycle)。
一、Bean的作用域 (Scope):定义实例的存在范围
Bean的作用域定义了Spring容器根据Bean定义创建的实例数量以及这些实例的共享范围。简单来说,它决定了当你向容器请求一个Bean时,是返回一个已存在的共享实例,还是创建一个全新的实例。
Spring框架定义了多种作用域,最核心和常用的有以下几种:
-
Singleton (单例作用域 - 默认)
-
定义:在一个Spring IoC容器中,无论你请求多少次该Bean(通过getBean()或依赖注入),只会存在一个共享的Bean实例。
-
特点:容器启动时(非懒加载情况下)就会创建这个单例Bean,之后所有对该Bean的请求都会返回这同一个实例。它是Spring的默认作用域。
-
适用场景:无状态的Bean,如Service层对象、Repository层对象、工具类、配置类等。这些Bean通常不持有与特定请求相关的状态,可以被多线程安全地共享。
-
代码示例:
import org.springframework.stereotype.Service; // 默认就是singleton,可以省略 @Scope("singleton") // import org.springframework.context.annotation.Scope; // @Scope("singleton") @Service public class UserService { // ... 无状态的方法 ... public User findUser(long id) { System.out.println("Fetching user " + id + " using instance: " + this.hashCode()); // ... 实际逻辑 ... return new User(id, "SingletonUser"); } }
-
注意:由于是共享实例,如果单例Bean持有可变状态(成员变量),必须特别注意线程安全问题。通常应设计为无状态或使用线程安全的方式管理状态(如ThreadLocal,或委托给其他有状态的prototype bean)。
-
-
Prototype (原型作用域)
-
定义:每次向Spring容器请求该Bean时,容器都会创建一个全新的Bean实例并返回。
-
特点:容器负责创建和注入依赖,但一旦将实例交给请求方,容器就不再跟踪和管理该实例的完整生命周期(特别是销毁阶段)。你需要自己负责后续的资源释放(如果需要)。
-
适用场景:有状态的Bean,即那些需要为每个请求或会话维护独立状态的对象。例如,一个代表用户购物车或者某个具体操作上下文的对象。
-
代码示例:
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.beans.factory.config.ConfigurableBeanFactory; // 推荐使用常量 @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或者 @Scope("prototype") public class UserActionContext { private long userId; private String action; public UserActionContext() { System.out.println("Creating new UserActionContext instance: " + this.hashCode()); } // Getters and Setters for state... public void setUserId(long userId) { this.userId = userId; } public void setAction(String action) { this.action = action; } // ... }
-
注意:频繁创建和销毁prototype Bean可能会带来性能开销。同时,Spring容器不会自动调用prototype Bean的销毁回调方法(如@PreDestroy或DisposableBean.destroy),需要使用者自行管理。
-
-
Web应用专属作用域 (仅在Web环境有效)
-
Request: 每个HTTP请求都会创建一个新的Bean实例。该实例仅在当前HTTP请求内有效。
-
Session: 每个HTTP Session会创建一个新的Bean实例。该实例在当前HTTP Session内共享。
-
Application: 在整个Web应用(ServletContext)生命周期内,只创建一个Bean实例。类似于singleton,但作用范围是ServletContext。
-
WebSocket: 在WebSocket生命周期内,只创建一个Bean实例。
这些作用域在构建Web应用程序时非常有用,用于管理与请求、会话等相关的状态。我们将在后续的Web开发专题中更详细地探讨它们。使用它们需要你的应用是一个Web应用,并且进行了相应的配置(例如,在Spring MVC或Spring WebFlux环境)。
如何指定作用域?
-
注解方式: 使用@Scope注解,如 @Scope("prototype") 或 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)。
-
XML方式: 在<bean>标签中使用scope属性,如 <bean id="myBean" class="..." scope="prototype"/>。
-
二、Bean的生命周期 (Lifecycle):从诞生到消亡
Spring容器不仅创建Bean,还管理它们的整个生命周期,从实例化到最终销毁。理解这个过程,以及如何在关键节点介入,对于进行资源初始化、资源释放、逻辑验证等操作至关重要。
一个典型的(主要针对Singleton Bean)生命周期包含以下关键阶段:
-
实例化 (Instantiation): Spring容器根据Bean定义(XML、注解等)找到对应的类,通过Java反射机制调用构造函数创建Bean的实例。
-
填充属性 (Populate Properties): 容器分析Bean的依赖关系(DI),通过Setter方法或直接字段注入(或者构造器注入在实例化阶段完成)将依赖的Bean或其他配置值(如@Value注解的值)设置到Bean实例的属性中。
-
初始化 (Initialization): 这是Bean生命周期中一个非常重要的阶段,允许开发者执行自定义的初始化逻辑。Spring提供了多种方式介入:
-
执行Aware接口方法: 如果Bean实现了特定的Aware接口(如BeanNameAware, BeanFactoryAware, ApplicationContextAware),Spring会调用相应的方法,将容器自身的一些资源注入给Bean。例如,setBeanName()会在setBeanFactory()之前调用。
-
执行BeanPostProcessor前置处理: BeanPostProcessor接口的postProcessBeforeInitialization方法会被调用。这是一个全局性的扩展点,可以对容器中所有(或筛选后的)Bean进行处理。
-
执行初始化回调:
-
如果Bean实现了InitializingBean接口,其afterPropertiesSet()方法会被调用。
-
如果Bean定义中通过@Bean(initMethod="...")或XML的init-method属性指定了自定义初始化方法,该方法会被调用。
-
如果Bean的方法使用了@PostConstruct注解(JSR-250标准),该方法会被调用。这是目前推荐的方式,因为它不依赖Spring特定接口,更加标准。
(执行顺序: @PostConstruct -> InitializingBean.afterPropertiesSet -> init-method)
-
-
执行BeanPostProcessor后置处理: BeanPostProcessor接口的postProcessAfterInitialization方法会被调用。常用于对Bean进行代理包装(如AOP实现)。
-
-
Bean可用 (Bean is Ready): 经过以上步骤,Bean实例已经完全创建并初始化好,可以被应用程序使用了。对于Singleton Bean,它会驻留在容器的单例缓存中。
-
销毁 (Destruction): 当Spring容器关闭时(或者对于非Singleton作用域,在特定条件下),容器会管理Bean的销毁过程。同样提供了多种回调机制:
-
执行销毁回调:
-
如果Bean实现了DisposableBean接口,其destroy()方法会被调用。
-
如果Bean定义中通过@Bean(destroyMethod="...")或XML的destroy-method属性指定了自定义销毁方法,该方法会被调用。
-
如果Bean的方法使用了@PreDestroy注解(JSR-250标准),该方法会被调用。这也是目前推荐的方式。
(执行顺序: @PreDestroy -> DisposableBean.destroy -> destroy-method)
-
-
生命周期回调示例 (@PostConstruct 和 @PreDestroy)
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component // 默认是 singleton
public class ResourceHandler {
public ResourceHandler() {
System.out.println("1. Constructor: ResourceHandler instance created.");
}
@PostConstruct // 初始化回调
public void init() {
System.out.println("2. @PostConstruct: Initializing resources...");
// 例如:建立数据库连接、加载配置文件、启动后台线程等
}
public void useResource() {
System.out.println("3. Using ResourceHandler...");
}
@PreDestroy // 销毁回调
public void cleanup() {
System.out.println("4. @PreDestroy: Cleaning up resources...");
// 例如:关闭数据库连接、释放文件句柄、停止后台线程等
}
}
// --- 在Spring应用中 ---
// ApplicationContext context = ... ;
// ResourceHandler handler = context.getBean(ResourceHandler.class);
// handler.useResource();
// ((ConfigurableApplicationContext) context).close(); // 关闭容器时会触发 @PreDestroy
输出大致顺序:
-
Constructor: ResourceHandler instance created.
-
@PostConstruct: Initializing resources...
-
Using ResourceHandler...
-
(当容器关闭时) @PreDestroy: Cleaning up resources...
注意:
-
prototype作用域的Bean,Spring容器在创建并交给调用者后,不会负责其后续的销毁回调(@PreDestroy, DisposableBean, destroy-method)。如果prototype Bean需要释放资源,需要调用者手动处理,或者使用BeanPostProcessor等高级技巧来管理。
三、作用域与生命周期的交互
-
Singleton Bean: 经历完整的生命周期,由Spring容器严格管理其创建、初始化和销毁。
-
Prototype Bean: 每次请求都创建新实例,执行实例化、属性填充、初始化回调(如@PostConstruct),然后交给请求方。销毁阶段不由容器管理。
-
Web作用域 Bean: 生命周期与对应的Web范围(Request、Session等)绑定。当范围结束时,容器负责销毁这些Bean并执行销毁回调。
四、为什么理解作用域和生命周期很重要?
-
资源管理: 通过生命周期回调(特别是@PostConstruct和@PreDestroy),可以在Bean创建后初始化资源(如连接池、文件句柄),在Bean销毁前释放资源,避免内存泄漏或资源枯竭。
-
状态管理: 正确选择作用域(singleton vs prototype等)是管理Bean状态的关键。误用singleton处理每个请求都不同的状态会导致数据错乱和线程安全问题。
-
性能考量: singleton性能较好(实例复用),而prototype涉及实例创建销毁开销。需要根据场景权衡。
-
调试与问题排查: 了解Bean的创建和销毁过程有助于定位配置错误、循环依赖、初始化失败等问题。
-
框架扩展: BeanPostProcessor等生命周期接口是Spring框架实现AOP、事务管理等功能的基础,理解生命周期有助于理解这些高级特性。
五、总结
Spring Bean的作用域(Scope)定义了Bean实例的共享策略和存在范围(如singleton, prototype),而生命周期(Lifecycle)则描述了Bean从创建到销毁所经历的各个阶段以及开发者可以介入的时机(通过@PostConstruct, @PreDestroy等回调)。
熟练掌握这两个概念,能够让你更精确地控制Bean的行为,更有效地管理资源,编写出更健壮、高效的Spring应用程序。它们是深入理解Spring内部机制和进行高级开发的必备知识。