导航
📖 本讲目标
- 理解 Bean 作用域的概念
- 实现 Singleton(单例)作用域
- 实现 Prototype(原型)作用域
- 实现 @Scope 注解
- 理解不同作用域的使用场景
🎯 Bean 作用域概述
什么是 Bean 作用域?
Bean 作用域(Scope) 定义了 Bean 实例在容器中的生命周期和可见性。不同的作用域决定了:
- Bean 实例的数量:容器中创建多少个实例
- Bean 的生命周期:实例何时创建、何时销毁
- Bean 的共享范围:哪些地方可以访问这个实例
Spring 框架的作用域
Spring 框架支持以下作用域:
- singleton:单例模式(默认)
- prototype:原型模式
- request:每个 HTTP 请求一个实例(Web 环境)
- session:每个 HTTP 会话一个实例(Web 环境)
- application:每个 ServletContext 一个实例(Web 环境)
本教程只实现 singleton 和 prototype 两种作用域。
🏷️ @Scope 注解实现
注解定义
创建 src/main/java/com/minispring/context/annotation/Scope.java:
package com.minispring.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Scope 注解用于指定 Bean 的作用域
* 支持 singleton(单例)和 prototype(原型)两种作用域
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
/**
* 作用域类型,支持以下值:
* - "singleton":单例模式(默认),容器只创建一个实例
* - "prototype":原型模式,每次获取 Bean 都创建新实例
*/
String value() default "singleton";
/**
* 作用域常量定义
*/
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
}
使用示例
@Component
@Scope("singleton") // 单例模式(默认)
public class UserService {
// ...
}
@Component
@Scope("prototype") // 原型模式
public class OrderService {
// ...
}
🔄 Singleton 单例模式
单例模式特点
- 唯一实例:容器中只创建一个实例
- 共享使用:所有地方获取的都是同一个实例
- 生命周期:与容器生命周期相同
- 线程安全:需要自己保证线程安全
实现机制
单例 Bean 使用缓存机制实现:
/**
* 单例 Bean 缓存池(一级缓存)
* Key: beanName, Value: Bean 实例
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
/**
* 获取单例 Bean(从缓存或创建)
*/
private Object getSingleton(String beanName, BeanDefinition beanDefinition) throws Exception {
synchronized (singletonObjects) {
// 1. 先从缓存中获取
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 2. 创建 Bean 实例
singletonObject = createBean(beanName, beanDefinition);
// 3. 放入缓存
singletonObjects.put(beanName, singletonObject);
System.out.println("单例 Bean 已缓存: " + beanName);
return singletonObject;
}
}
缓存策略
- 一级缓存(singletonObjects):存储完整的单例 Bean
- 线程安全:使用
synchronized保证并发安全 - 延迟创建:首次获取时才创建,后续从缓存获取
🔄 Prototype 原型模式
原型模式特点
- 每次新建:每次获取 Bean 都创建新实例
- 独立实例:每个实例都是独立的
- 生命周期:由调用者管理,容器不负责销毁
- 无缓存:不进行缓存,每次都创建新实例
实现机制
原型 Bean 不缓存,每次都创建新实例:
@Override
public Object getBean(String beanName) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new NoSuchBeanDefinitionException(beanName);
}
// 根据作用域决定返回策略
if (beanDefinition.isSingleton()) {
// 单例模式:从缓存获取或创建
return getSingleton(beanName, beanDefinition);
} else {
// 原型模式:每次都创建新实例
return createBean(beanName, beanDefinition);
}
}
🏷️ @Lazy 懒加载注解
懒加载概念
懒加载(Lazy Loading) 是指 Bean 不在容器启动时创建,而是在第一次使用时才创建。
注解定义
创建 src/main/java/com/minispring/context/annotation/Lazy.java:
package com.minispring.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Lazy 注解用于标记 Bean 为懒加载
* 容器启动时不会立即创建该 Bean,而是在第一次使用时才创建
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
/**
* 是否启用懒加载,默认为 true
*/
boolean value() default true;
}
实现懒加载
在 ApplicationContext 中添加预实例化方法:
/**
* 预实例化所有非懒加载的单例 Bean
*/
private void preInstantiateSingletons() {
System.out.println("n开始预实例化单例 Bean...");
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
// 只实例化单例且非懒加载的 Bean
if (beanDefinition.isSingleton() && !beanDefinition.isLazy()) {
try {
getBean(beanName);
} catch (Exception e) {
throw new RuntimeException("预实例化 Bean 失败: " + beanName, e);
}
}
}
System.out.println("单例 Bean 预实例化完成n");
}
📊 作用域对比
对比表格
| 特性 | Singleton | Prototype |
|---|---|---|
| 实例数量 | 1 个 | 多个 |
| 创建时机 | 容器启动时(非懒加载) | 每次获取时 |
| 缓存机制 | ✅ 缓存 | ❌ 不缓存 |
| 生命周期 | 与容器相同 | 由调用者管理 |
| 线程安全 | 需要保证 | 每个实例独立 |
| 内存占用 | 低 | 高 |
| 适用场景 | 无状态服务 | 有状态对象 |
使用场景
Singleton 适用场景:
- 无状态的服务类(Service、Repository)
- 工具类
- 配置类
Prototype 适用场景:
- 有状态的对象
- 需要每次创建新实例的场景
- 线程不安全的对象
🧪 测试示例
测试类
package com.minispring.demo;
import com.minispring.context.annotation.Scope;
import com.minispring.stereotype.Component;
import com.minispring.beans.factory.config.BeanDefinition;
import com.minispring.context.ApplicationContext;
@Component
@Scope("singleton")
class SingletonService {
private final String id = "Singleton-" + System.currentTimeMillis();
public String getId() {
return id;
}
}
@Component
@Scope("prototype")
class PrototypeService {
private final String id = "Prototype-" + System.currentTimeMillis();
public String getId() {
return id;
}
}
public class ScopeTest {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ApplicationContext();
// 注册 Bean
BeanDefinition singletonBd = new BeanDefinition(SingletonService.class);
singletonBd.setScope("singleton");
context.registerBeanDefinition("singletonService", singletonBd);
BeanDefinition prototypeBd = new BeanDefinition(PrototypeService.class);
prototypeBd.setScope("prototype");
context.registerBeanDefinition("prototypeService", prototypeBd);
// 测试单例模式
SingletonService s1 = context.getBean("singletonService", SingletonService.class);
SingletonService s2 = context.getBean("singletonService", SingletonService.class);
System.out.println("单例模式: s1 == s2 = " + (s1 == s2));
System.out.println("s1 ID: " + s1.getId());
System.out.println("s2 ID: " + s2.getId());
// 测试原型模式
PrototypeService p1 = context.getBean("prototypeService", PrototypeService.class);
PrototypeService p2 = context.getBean("prototypeService", PrototypeService.class);
System.out.println("原型模式: p1 == p2 = " + (p1 == p2));
System.out.println("p1 ID: " + p1.getId());
System.out.println("p2 ID: " + p2.getId());
}
}
📝 本讲总结
本讲我们完成了:
- ✅ 理解了 Bean 作用域的概念
- ✅ 实现了 @Scope 注解
- ✅ 实现了 Singleton 单例模式(使用缓存)
- ✅ 实现了 Prototype 原型模式(每次新建)
- ✅ 实现了 @Lazy 懒加载注解
- ✅ 理解了不同作用域的使用场景
🎯 关键点回顾
- Singleton:使用缓存机制,容器中只有一个实例
- Prototype:不缓存,每次获取都创建新实例
- @Lazy:延迟创建,首次使用时才创建
- 线程安全:Singleton 需要保证线程安全
⚠️ 注意事项
- Singleton 线程安全:多线程环境下需要保证线程安全
- Prototype 内存:频繁创建可能占用较多内存
- 懒加载时机:懒加载的 Bean 在首次使用时创建
🚀 下一讲预告
在下一讲中,我们将实现 Bean 生命周期管理,包括:
- Bean 创建流程
- @PostConstruct 初始化回调
- @PreDestroy 销毁回调
- 初始化方法调用
准备好了吗?让我们继续 第 07 讲:Bean 生命周期管理!
思考题:
- 为什么 Singleton 模式需要缓存?
- Prototype 模式适合什么场景?
- 懒加载有什么优缺点?
