Bean 的作用域不同,管理方式不同 Bean 的作用域不同管理方式不同Spring 对不同作用域的 Bean管理方式有本质区别。核心差异体现在三个维度何时创建、是否缓存、谁来销毁。一、singleton全程托管singleton 是默认作用域Spring 对它管理得最彻底。创建时机容器启动时preInstantiateSingletons()遍历所有非懒加载的单例 BeanDefinition调用getBean()触发创建。懒加载的单例则在第一次getBean()时创建。缓存机制创建完成后放入singletonObjects一级缓存。后续所有getBean()调用先查这个缓存命中则直接返回不会重复创建。销毁管理创建完成后registerDisposableBeanIfNecessary()会把 Bean 包装成DisposableBeanAdapter注册到disposableBeans集合。容器关闭时destroySingletons()遍历这个集合依次调用PreDestroy、DisposableBean.destroy()、destroy-method。循环依赖通过三级缓存解决。实例化后提前把ObjectFactory放入singletonFactories依赖方可以从三级缓存拿到早期引用等属性填充完成后再升级到一级缓存。一句话singleton 从创建到销毁Spring 全程负责。二、prototype只管生不管死prototype 作用域的 BeanSpring 只负责创建不负责缓存和销毁。创建时机每次调用getBean()都创建新实例。doGetBean()中判断mbd.isPrototype()为 true 时直接调用createBean()不走getSingleton()缓存逻辑。缓存机制不缓存。每次getBean()都执行完整的创建流程实例化 → 属性填充 → 初始化。所以 prototype Bean 的创建开销比 singleton 大。销毁管理registerDisposableBeanIfNecessary()中有一个判断if(!mbd.isPrototype()requiresDestruction(bean,mbd)){// 注册销毁回调}prototype 被明确排除在外。原因很简单Spring 创建完 prototype Bean 后不持有它的引用无法在容器关闭时找到这些实例。销毁工作只能由调用方自己负责。循环依赖不支持。prototype 每次创建新实例无法提前暴露一个稳定的早期引用所以循环依赖会直接抛出BeanCurrentlyInCreationException。一句话prototype 由 Spring 创建但生命周期由调用方管理。三、Web 作用域由 Scope 接口管理request、session、application、websocket 这些 Web 作用域既不像 singleton 那样全程托管也不像 prototype 那样完全不管。它们通过Scope接口实现自定义管理。创建时机由对应的Scope实现类决定。RequestScope在每次 HTTP 请求开始时创建 BeanSessionScope在会话建立时创建。缓存机制缓存在Scope内部。RequestScope把 Bean 存在RequestAttributes中SessionScope存在Session中。同一个请求或会话内多次getBean()返回同一个实例。销毁管理Scope实现类通过registerDestructionCallback()注册销毁回调。请求结束或会话失效时Scope触发回调执行销毁逻辑。注入到 singleton 的问题Web 作用域的 Bean 注入到 singleton Bean 时必须配置proxyMode。因为 singleton 在容器启动时创建此时没有 HTTP 请求上下文。代理对象在方法调用时才从当前请求的Scope中获取真实 Bean。ComponentScope(valuerequest,proxyModeScopedProxyMode.TARGET_CLASS)publicclassRequestContext{// 注入到 singleton 时实际注入的是代理}一句话Web 作用域由对应的 Scope 实现类管理生命周期与请求/会话绑定。四、源码层面的差异doGetBean()中根据作用域走不同分支if(mbd.isSingleton()){sharedInstancegetSingleton(beanName,()-createBean(beanName,mbd,args));beangetObjectForBeanInstance(sharedInstance,name,beanName,mbd);}elseif(mbd.isPrototype()){ObjectprototypeInstancecreateBean(beanName,mbd,args);beangetObjectForBeanInstance(prototypeInstance,name,beanName,mbd);}else{StringscopeNamembd.getScope();Scopescopethis.scopes.get(scopeName);ObjectscopedInstancescope.get(beanName,()-createBean(beanName,mbd,args));beangetObjectForBeanInstance(scopedInstance,name,beanName,mbd);}singleton 走getSingleton()有缓存逻辑prototype 直接createBean()无缓存其他作用域委托给Scope.get()由Scope实现类管理缓存销毁注册也有明确判断protectedvoidregisterDisposableBeanIfNecessary(StringbeanName,Objectbean,RootBeanDefinitionmbd){if(!mbd.isPrototype()requiresDestruction(bean,mbd)){// singleton 和其他作用域注册销毁回调}}只有 prototype 被排除。五、对比总结维度singletonprototypeWeb 作用域创建时机容器启动时或首次获取每次 getBean()请求/会话开始时是否缓存缓存到 singletonObjects不缓存缓存在 Scope 内部销毁管理Spring 负责调用方负责Scope 负责循环依赖三级缓存解决不支持不适用创建开销一次每次每个请求/会话一次线程安全需注意共享状态天然隔离请求/会话内隔离理解这些差异能帮你在设计时做出正确选择无状态的服务用 singleton有状态的对象用 prototype用户级别的数据用 session请求级别的数据用 request。选错作用域要么浪费内存要么引发线程安全问题。