Spring Bean的作用域:从源码到实战,彻底搞懂
Spring Bean的作用域:决定了Bean的生命周期和可见范围——什么时候创建、存在多久、谁可以访问。
二、6种作用域速查表
|
|
|
|
|
|---|---|---|---|
| singleton |
|
|
默认值
|
| prototype |
|
|
|
| request |
|
|
|
| session |
|
|
|
| application |
|
|
|
| websocket |
|
|
|
三、详细解析
3.1 singleton(单例)—— 默认作用域
@Component@Scope("singleton")// 或不写,默认就是singletonpublicclassUserService{// 整个容器只有一个实例}// 或者通过XML配置<bean id="userService"class="com.example.UserService" scope="singleton"/>
特点:
-
容器启动时创建(默认) -
所有地方获取的都是同一个对象 -
线程安全需要自行保证
源码验证:
// AbstractBeanFactory.getSingleton()public Object getSingleton(String beanName){// 从一级缓存获取 Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 循环依赖时从二级缓存获取 singletonObject = this.earlySingletonObjects.get(beanName); }return singletonObject;}
内存图示:
Spring容器┌─────────────────────────┐│ singletonObjects Map ││ ┌───────────────────┐ ││ │ "userService" → UserService实例 ││ └───────────────────┘ │└─────────────────────────┘ ↓ ↓ ↓ Controller1 Controller2 Controller3 (同一实例) (同一实例) (同一实例)
3.2 prototype(原型)—— 每次都是新的
@Component@Scope("prototype")publicclassShoppingCart{private List<Item> items = new ArrayList<>();// 每次注入都是新实例}// 或者@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
特点:
-
每次获取都创建新实例 -
Spring不管理prototype Bean的完整生命周期 -
适用于有状态的Bean
注意:prototype Bean的销毁方法不会被调用!
@Component@Scope("prototype")publicclassPrototypeBean{@PreDestroypublicvoiddestroy(){// ❌ 这个方法不会被Spring调用! System.out.println("prototype销毁"); }}
获取方式:
@ServicepublicclassOrderService{@Autowiredprivate ApplicationContext context;publicvoidcreateOrder(){// 方式1:通过ApplicationContext获取 ShoppingCart cart1 = context.getBean(ShoppingCart.class); ShoppingCart cart2 = context.getBean(ShoppingCart.class);// cart1 != cart2 ✅ 不同实例// 方式2:通过@Lookup注解(推荐) ShoppingCart cart3 = getShoppingCart(); }@Lookuppublic ShoppingCart getShoppingCart(){returnnull; // Spring会动态实现 }}
3.3 request(请求级别)
@Component@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)publicclassRequestData{private String requestId;private String userId;// getters/setters...}
特点:
-
每个HTTP请求创建一个实例 -
同一个请求内多次注入得到同一个对象 -
不同请求得到不同对象
使用场景:
@RestControllerpublicclassUserController{@Autowiredprivate RequestData requestData; // 每个请求独立的实例@GetMapping("/user/info")public Result getUserInfo(){// 记录请求ID、用户ID等信息 requestData.setRequestId(UUID.randomUUID().toString());// 其他处理...return Result.success(); }}
实现原理:
HTTP请求1 → RequestData实例AHTTP请求2 → RequestData实例BHTTP请求3 → RequestData实例C
3.4 session(会话级别)
@Component@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)publicclassUserSession{private User currentUser;private List<Order> orders;// getters/setters...}
特点:
-
每个用户会话创建一个实例 -
同一个session内多次注入得到同一个对象 -
不同用户/不同session得到不同对象
典型场景:购物车
@Component@Scope(value = WebApplicationContext.SCOPE_SESSION)publicclassShoppingCart{private List<Item> items = new ArrayList<>();publicvoidaddItem(Item item){ items.add(item); }public BigDecimal getTotal(){return items.stream() .map(Item::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); }}@RestControllerpublicclassCartController{@Autowiredprivate ShoppingCart cart; // 每个用户独立的购物车@PostMapping("/cart/add")public Result addToCart(@RequestBody Item item){ cart.addItem(item); // 添加到当前用户的购物车return Result.success(); }}
会话生命周期:
用户登录 → 创建Session → 创建ShoppingCart实例 ↓ 添加商品 → 同一个实例 ↓ 继续购物 → 同一个实例 ↓ 用户登出 → Session销毁 → ShoppingCart实例销毁
3.5 application(应用级别)
@Component@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)publicclassAppConfig{private String appName;private String version;// 应用级全局配置}
特点:
-
整个应用只有一个实例 -
所有用户、所有请求共享 -
类似于singleton,但在ServletContext中
使用场景:
@Component@Scope("application")publicclassAppStatistics{private AtomicInteger requestCount = new AtomicInteger(0);private AtomicLong totalTime = new AtomicLong(0);publicvoidrecordRequest(long time){ requestCount.incrementAndGet(); totalTime.addAndGet(time); }publicdoublegetAvgTime(){return totalTime.get() / (double) requestCount.get(); }}
3.6 websocket(WebSocket级别)
@Component@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)publicclassWebSocketSession{private String sessionId;private String userId;// WebSocket会话数据}
特点:
-
每个WebSocket连接创建一个实例 -
适用于实时通信场景
四、作用域对比表
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
五、高级技巧
5.1 代理模式解决注入问题
当singleton Bean注入request/session Bean时,需要代理:
@Component@Scope("singleton")publicclassUserService{// ❌ 直接注入request作用域的Bean会有问题// @Autowired// private RequestData requestData; // 启动报错// ✅ 使用代理@Autowiredprivate RequestData requestData; // 注入的是代理对象}@Component@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)publicclassRequestData{// ...}
原理:
UserService (singleton) ↓ 持有RequestData代理对象 ↓ 每次调用时从当前请求获取真实的RequestData
5.2 自定义作用域
// 1. 实现Scope接口publicclassCustomScopeimplementsScope{privatefinal Map<String, Object> cache = new ConcurrentHashMap<>();@Overridepublic Object get(String name, ObjectFactory<?> objectFactory){return cache.computeIfAbsent(name, k -> objectFactory.getObject()); }@Overridepublic Object remove(String name){return cache.remove(name); }// 其他方法实现...}// 2. 注册自定义作用域@ConfigurationpublicclassScopeConfig{@Beanpublic CustomScopeConfigurer customScopeConfigurer(){ CustomScopeConfigurer configurer = new CustomScopeConfigurer(); configurer.addScope("custom", new CustomScope());return configurer; }}// 3. 使用自定义作用域@Component@Scope("custom")publicclassCustomScopedBean{// ...}
5.3 线程级别作用域(ThreadLocal)
// 实现线程级别的作用域publicclassThreadScopeimplementsScope{privatefinal ThreadLocal<Map<String, Object>> threadLocal = ThreadLocal.withInitial(HashMap::new);@Overridepublic Object get(String name, ObjectFactory<?> objectFactory){ Map<String, Object> scope = threadLocal.get();return scope.computeIfAbsent(name, k -> objectFactory.getObject()); }@Overridepublic Object remove(String name){return threadLocal.get().remove(name); }// 其他方法...}
六、源码分析
6.1 作用域的核心接口:Scope
publicinterfaceScope{// 获取Bean,不存在则创建Object get(String name, ObjectFactory<?> objectFactory);// 移除BeanObject remove(String name);// 注册销毁回调voidregisterDestructionCallback(String name, Runnable callback);// 获取会话ID(用于session、request等)Object getConversationId();}
6.2 Bean创建时的作用域判断
// AbstractBeanFactory.doGetBean()protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly){// 获取作用域 String scopeName = mbd.getScope(); Scope scope = this.scopes.get(scopeName);if (scope == null) {thrownew IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); }// 根据作用域获取Bean Object scopedInstance = scope.get(beanName, () -> {// 创建Bean的回调return createBean(beanName, mbd, args); });return (T) scopedInstance;}
6.3 Singleton和Prototype的实现差异
// Singleton:从缓存获取public Object getSingleton(String beanName){return singletonObjects.get(beanName);}// Prototype:每次都创建新的public Object getPrototype(String beanName){return createBean(beanName, mbd, args); // 直接创建}
七、面试话术模板
“Spring提供了6种作用域:
1. singleton(单例):默认作用域,整个容器只有一个实例,适用于无状态的Service、Dao。
2. prototype(原型):每次获取都创建新实例,适用于有状态的Bean。需要注意的是,Spring不管理prototype Bean的完整生命周期,销毁方法不会被调用。
3. request(请求):每个HTTP请求一个实例,适用于请求级别的数据包装。
4. session(会话):每个用户会话一个实例,适用于购物车、用户信息等。
5. application(应用):整个Web应用一个实例,适用于全局配置。
6. websocket:每个WebSocket连接一个实例,适用于实时通信。
使用注意:
-
当singleton Bean注入request或session作用域的Bean时,需要配置代理模式 -
可以通过 @Scope注解指定,也可以通过XML的scope属性 -
自定义作用域需要实现Scope接口并注册
底层原理:Spring通过Scope接口统一管理不同作用域的Bean,在doGetBean方法中根据作用域调用对应的Scope实现获取Bean。”
八、快速记忆
默认单例singleton,每次获取都是它原型prototype是新的,用完Spring不管它请求request一个请求用,会话session用户专属应用application全局用,websocket连接专属它singleton:一个prototype:多个request:请求内唯一session:会话内唯一application:应用内唯一websocket:连接内唯一
夜雨聆风