我们来看一段代码:
public class FunRestController {
private Coach coach;
private Coach anotherCoach;
@Autowired
public FunRestController(@Qualifier("cricketCoach") Coach coach,
@Qualifier("cricketCoach") Coach anotherCoach) {
this.coach = coach;
this.anotherCoach = anotherCoach;
System.out.println("Comparing beans: coach == anotherCoach: " + (coach == anotherCoach));
}
}
你觉得控制台输出的会是 true 还是 false?答案可能出乎意料:不一定。如果你对这个结果感到疑惑,那么说明对于 Spring 框架中 Bean 的作用域(Scope)概念,还需要进一步的理解。这正是理解 Spring 依赖注入与 IOC 容器 核心机制的关键一环。
1. 默认的单例(Singleton)作用域
在 Spring 容器中,一个 Bean 定义通常只对应一个实例,这就是默认的单例作用域。容器在初始化时会创建这个唯一的 Bean 实例,并将其放入缓存中。后续所有对该 Bean 的依赖注入请求,都将返回这同一个缓存实例。
因此,在上述代码的默认情况下,coach 和 anotherCoach 这两个引用实际上指向的是容器中的同一个 CricketCoach 实例,比较结果为 true。
我们可以使用 @Scope 注解来显式地声明 Bean 的作用域。在 Bean 类上添加如下注解,其效果与默认行为一致:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class CricketCoach implements Coach {
// ... 类实现
}
2. Spring Bean 的作用域类型
Spring 框架为 Bean 提供了多种作用域,以适应不同的应用场景:
| Scope |
描述 |
| singleton |
(默认)为每个 Spring IoC 容器创建一个单一的 Bean 实例。 |
| prototype |
每次请求(注入或 getBean())时都会创建一个新的 Bean 实例。 |
| request |
为每一个 HTTP 请求创建一个 Bean 实例,仅适用于 Web 应用环境。 |
| session |
为每一个 HTTP Session 创建一个 Bean 实例,仅适用于 Web 应用环境。 |
| application |
为整个 ServletContext 生命周期创建一个 Bean 实例,适用于 Web 应用。 |
| websocket |
为每一个 WebSocket 会话创建一个 Bean 实例,适用于 WebSocket 应用。 |
3. 原型(Prototype)作用域实践
现在,我们将 CricketCoach 的作用域修改为 prototype:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CricketCoach implements Coach {
public CricketCoach() {
System.out.println("In constructor: " + getClass().getSimpleName());
}
}
此时,每次进行依赖注入时,Spring 容器都会创建一个全新的 CricketCoach 实例。因此,回到开头的示例,FunRestController 构造函数中通过 @Autowired 注入的两个 Coach 参数,会是两个不同的对象,coach == anotherCoach 的比较结果将变为 false。
如果我们将作用域改回 singleton,结果则会变回 true。这种差异清晰地展示了作用域配置如何直接影响 SpringBoot 应用中对象的生命周期与行为,尤其是在构建需要区分状态的服务或控制器时尤为重要。理解并正确配置 Bean 的作用域,是进行高效、可靠的 Java 企业级开发,特别是 Web 应用开发的基础。
|