乐于分享
好东西不私藏

Spring Bean的作用域:从源码到实战,彻底搞懂

Spring Bean的作用域:从源码到实战,彻底搞懂

一、一句话总结

Spring Bean的作用域:决定了Bean的生命周期可见范围——什么时候创建、存在多久、谁可以访问。

二、6种作用域速查表

作用域
说明
创建时机
使用场景
singleton
单例,整个容器只有一个实例
容器启动时(默认)
默认值

,无状态Bean
prototype
原型,每次获取都创建新实例
每次获取时
有状态的Bean
request
Web请求级别,每个请求一个实例
每次HTTP请求
Web应用中的请求级数据
session
Web会话级别,每个会话一个实例
每次HTTP会话
用户级数据(购物车)
application
Web应用级别,整个应用一个实例
应用启动时
应用级全局数据
websocket
WebSocket连接级别
每次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连接创建一个实例
  • 适用于实时通信场景

四、作用域对比表

作用域
创建时机
销毁时机
线程安全
典型场景
singleton
容器启动
容器关闭
需自行保证
Service、Dao
prototype
每次获取
不管理
每次都是新的
有状态的Bean
request
每次请求
请求结束
请求内安全
请求参数包装
session
每次会话
会话过期
会话内安全
购物车、用户信息
application
应用启动
应用关闭
需自行保证
全局配置
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> 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:连接内唯一