PDF大白话说Java面试题 — 06_Spring篇第14题Spring 支持的 Bean 作用域回答核心考点 Spring Bean 作用域是 Spring IoC 容器的核心设计之一大厂面试不会只问有哪几种而是深入考察各作用域的底层实现机制DefaultListableBeanFactory如何管理不同作用域的 Bean、作用域代理ScopedProxyMode的工作原理CGLIB/JDK 代理在跨作用域注入时的角色、Web 作用域与RequestContextListener/ServletRequestListener的生命周期绑定、以及prototype作用域 Bean 的销毁机制与内存泄漏风险。面试官真正想判断的是你是否能从源码层面理解作用域的设计意图以及能否在 Web 应用、微服务、分布式会话等生产场景中正确选型。1. Spring 支持的六种 Bean 作用域Spring Framework 定义了 6 种标准作用域其中 2 种适用于所有应用4 种仅适用于 Web 环境作用域常量说明生命周期线程安全适用场景singletonConfigurableBeanFactory.SCOPE_SINGLETON每个 Spring 容器只有一个实例容器启动时创建容器关闭时销毁无状态安全有状态不安全Service、DAO、配置类prototypeConfigurableBeanFactory.SCOPE_PROTOTYPE每次获取都创建新实例获取时创建Spring 不管理销毁安全实例隔离有状态对象、多例策略requestWebApplicationContext.SCOPE_REQUEST每个 HTTP 请求一个实例请求开始时创建请求结束时销毁安全请求隔离请求级上下文、TraceIdsessionWebApplicationContext.SCOPE_SESSION每个 HTTP Session 一个实例Session 创建时创建Session 失效时销毁安全会话隔离用户购物车、登录状态applicationWebApplicationContext.SCOPE_APPLICATION每个 ServletContext 一个实例应用启动时创建应用关闭时销毁无状态安全有状态不安全全局配置、应用级缓存websocketWebApplicationContext.SCOPE_WEBSOCKET每个 WebSocket 连接一个实例连接建立时创建连接关闭时销毁安全连接隔离WebSocket 会话状态注意global-session全局会话在 Spring 5 中已随 Portlet 支持一起移除不再推荐使用。2. singleton 作用域——默认且最常用2.1 定义与实现singleton是 Spring 的默认作用域每个 Spring 容器只创建一个 Bean 实例存储在DefaultSingletonBeanRegistry.singletonObjects一级缓存中。// 默认就是 singleton可省略 ScopeComponentpublicclassUserService{}// 显式声明Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)ComponentpublicclassUserService{}2.2 单例 Bean 的创建时机配置创建时机说明默认非懒加载容器启动时ApplicationContext.refresh()阶段Lazy首次获取时getBean()或依赖注入时lazy-inittrueXML首次获取时同LazyLazy// 延迟初始化ComponentpublicclassHeavyService{}2.3 单例 Bean 的线程安全单例 Bean 被多线程共享必须设计为无状态Service// singleton无状态线程安全publicclassUserService{AutowiredprivateUserDaouserDao;// 依赖注入本身无状态publicUsergetUser(Longid){returnuserDao.findById(id);// 纯查询不修改实例变量}}3. prototype 作用域——每次获取新实例3.1 定义与实现每次调用getBean()或注入依赖时Spring 都会创建一个新的 Bean 实例。Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)ComponentpublicclassPrototypeBean{privateintcount0;publicvoidincrement(){count;}publicintgetCount(){returncount;}}AutowiredprivatePrototypeBeanbean1;AutowiredprivatePrototypeBeanbean2;// bean1 ! bean2是两个不同的实例3.2 prototype 的销毁机制——重大陷阱Spring 不管理 prototype Bean 的完整生命周期。虽然会调用初始化回调PostConstruct、InitializingBean但不会调用销毁回调PreDestroy、DisposableBean。Scope(prototype)ComponentpublicclassPrototypeResourceimplementsDisposableBean{privateConnectionconnection;PostConstructpublicvoidinit(){connectiondataSource.getConnection();// 获取资源}Overridepublicvoiddestroy(){connection.close();// ❌ Spring 不会调用内存泄漏}}解决方案方案实现方式说明自定义销毁逻辑客户端代码手动调用销毁侵入性强不推荐Bean 后处理器实现DestructionAwareBeanPostProcessor在 Bean 销毁前执行清理使用 ObjectFactory延迟获取客户端管理生命周期推荐// 推荐使用 ObjectFactory客户端控制生命周期ServicepublicclassClientService{AutowiredprivateObjectFactoryPrototypeResourceresourceFactory;publicvoiddoWork(){PrototypeResourceresourceresourceFactory.getObject();try{// 使用资源...}finally{resource.close();// 客户端负责清理}}}3.3 prototype 的性能考量频繁创建 prototype Bean 可能带来性能问题场景影响优化方案每次请求创建对象创建开销大使用对象池Apache Commons Pool依赖注入复杂依赖树递归创建使用ObjectFactory延迟创建内存占用高大量实例未回收确保客户端及时释放4. Web 作用域——request、session、application、websocketWeb 作用域仅在 Web 应用上下文中有效WebApplicationContext需要配置RequestContextListener或DispatcherServlet。4.1 request 作用域每个 HTTP 请求创建一个实例请求结束后销毁。Scope(valueWebApplicationContext.SCOPE_REQUEST,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassRequestContext{privateStringtraceId;privateLonguserId;// ... 请求级状态}底层绑定机制Spring 通过RequestContextListener监听请求生命周期在requestInitialized()时创建 Bean在requestDestroyed()时销毁。4.2 session 作用域每个 HTTP Session 创建一个实例Session 失效时销毁。Scope(valueWebApplicationContext.SCOPE_SESSION,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassShoppingCart{privateListItemitemsnewArrayList();// ... 购物车状态}分布式 Session 问题在微服务/集群环境下Session 默认不共享。解决方案Spring Session Redis将 Session 存储到 Redis实现分布式共享JWT Token无状态认证不依赖 SessionSticky Session负载均衡器将同一用户固定到同一节点不推荐。4.3 application 作用域每个 ServletContext 创建一个实例等同于singleton但生命周期绑定到 Web 应用。Scope(valueWebApplicationContext.SCOPE_APPLICATION,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassAppConfig{privateMapString,ObjectglobalCachenewConcurrentHashMap();}4.4 websocket 作用域每个 WebSocket 连接创建一个实例连接关闭时销毁。Scope(scopeNamewebsocket,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassWebSocketSession{privateStringsessionId;privateListMessagemessagesnewArrayList();}5. 作用域代理 ScopedProxyMode——跨作用域注入的核心5.1 为什么需要作用域代理当singletonBean 注入request/session/prototype作用域 Bean 时由于singletonBean 只创建一次注入的依赖在首次注入后固定不变导致后续请求获取的是旧实例。Service// singletonpublicclassUserService{AutowiredprivateRequestContextrequestContext;// request 作用域publicvoidprocess(){// 问题requestContext 是第一次注入时的实例不是当前请求的StringtraceIdrequestContext.getTraceId();}}5.2 ScopedProxyMode 的工作原理ScopedProxyMode为作用域 Bean 创建代理对象singletonBean 注入的是代理而非真实实例。每次调用代理方法时代理从当前作用域获取真实实例。代理模式实现方式适用条件说明NO不创建代理同作用域注入默认无代理开销INTERFACESJDK 动态代理目标类实现接口要求目标类有接口TARGET_CLASSCGLIB 代理任意类最常用无需接口Scope(valueWebApplicationContext.SCOPE_REQUEST,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassRequestContext{}代理执行流程UserServicesingleton调用 requestContext.getTraceId() ↓ 调用 CGLIB 代理对象的 getTraceId() ↓ 代理从 RequestAttributesThreadLocal获取当前 Request ↓ 从 Request 作用域缓存中获取真实的 RequestContext 实例 ↓ 调用真实实例的 getTraceId()5.3 prototype 作用域的代理问题即使配置了proxyMode TARGET_CLASSsingletonBean 注入prototypeBean 时由于代理对象本身也是单例的每次调用代理方法时虽然会创建新的目标实例但如果代理方法内部缓存了引用仍然可能共享状态。Scope(valueConfigurableBeanFactory.SCOPE_PROTOTYPE,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassPrototypeBean{}ServicepublicclassUserService{AutowiredprivatePrototypeBeanprototypeBean;publicvoidprocess(){// 每次调用都会创建新的 PrototypeBean 实例prototypeBean.doSomething();}}更推荐的方式使用ObjectFactory或Lookup方法ServicepublicclassUserService{AutowiredprivateObjectFactoryPrototypeBeanprototypeBeanFactory;publicvoidprocess(){PrototypeBeanbeanprototypeBeanFactory.getObject();// 每次获取新实例bean.doSomething();}}// 或使用 LookupServicepublicabstractclassUserService{LookupprotectedabstractPrototypeBeangetPrototypeBean();publicvoidprocess(){PrototypeBeanbeangetPrototypeBean();// 每次获取新实例bean.doSomething();}}6. 自定义作用域Spring 允许自定义作用域实现Scope接口// 自定义线程作用域publicclassThreadScopeimplementsScope{privatefinalThreadLocalMapString,ObjectthreadScopeThreadLocal.withInitial(HashMap::new);OverridepublicObjectget(Stringname,ObjectFactory?objectFactory){MapString,ObjectscopethreadScope.get();Objectbeanscope.get(name);if(beannull){beanobjectFactory.getObject();scope.put(name,bean);}returnbean;}OverridepublicObjectremove(Stringname){returnthreadScope.get().remove(name);}// ... 其他方法}// 注册自定义作用域BeanpublicCustomScopeConfigurercustomScopeConfigurer(){CustomScopeConfigurerconfigurernewCustomScopeConfigurer();configurer.addScope(thread,newThreadScope());returnconfigurer;}// 使用Scope(thread)ComponentpublicclassThreadScopedBean{}7. 生产环境避坑指南7.1 prototype Bean 的内存泄漏Spring 不管理 prototype Bean 的销毁如果 Bean 持有资源数据库连接、线程池必须客户端手动释放。7.2 session 作用域在分布式环境失效集群环境下 Session 默认不共享使用 Spring Session Redis 或 JWT 替代。7.3 忘记配置 proxyMode 导致数据串乱Scope(request)// ❌ 忘记 proxyModeComponentpublicclassRequestContext{}// singleton Bean 注入后所有请求共享同一个 RequestContext 实例7.4 Web 作用域在非 Web 环境启动失败request/session等作用域需要WebApplicationContext在纯 Java 应用中使用会抛出IllegalStateException。7.5 Async 与作用域 BeanAsync使用线程池执行异步任务脱离了原请求/会话的上下文作用域 Bean 可能获取不到正确的实例。8. 面试官追问与高分回答模板追问 1“Spring 支持哪些 Bean 作用域”低分回答“有 singleton、prototype、request、session、global-session 五种。”过时缺少 application 和 websocket高分回答Spring 支持 6 种标准作用域作用域适用范围说明singleton所有应用每个容器一个实例默认作用域prototype所有应用每次获取创建新实例requestWeb 应用每个 HTTP 请求一个实例sessionWeb 应用每个 HTTP Session 一个实例applicationWeb 应用每个 ServletContext 一个实例websocketWeb 应用每个 WebSocket 连接一个实例注意global-session在 Spring 5 中已随 Portlet 支持移除。其中singleton和prototype适用于所有应用类型其余 4 种需要 Web 环境。追问 2“singleton 和 prototype 有什么区别prototype 有什么陷阱”高分回答| 维度 | singleton | prototype ||------|-----------|-----------||实例数量| 每个容器一个 | 每次获取创建新实例 ||创建时机| 容器启动默认或首次获取Lazy | 每次 getBean() 或注入时 ||销毁管理| Spring 管理完整生命周期 |Spring 不调用销毁回调||线程安全| 需设计为无状态 | 天然安全实例隔离 ||性能| 创建一次复用 | 频繁创建开销大 |prototype 的最大陷阱是销毁机制Spring 会调用PostConstruct/InitializingBean初始化但不会调用PreDestroy/DisposableBean销毁。如果 prototype Bean 持有数据库连接、线程池等资源会导致内存泄漏。解决方案使用ObjectFactory延迟获取由客户端管理生命周期或实现自定义的DestructionAwareBeanPostProcessor。追问 3“request 作用域 Bean 怎么在 singleton Bean 中使用”高分回答直接在 singleton Bean 中注入 request 作用域 Bean 会有问题singleton 只创建一次注入的 request Bean 在首次注入后固定不变后续请求获取的是旧实例。解决方案是配置proxyMode ScopedProxyMode.TARGET_CLASSScope(valueWebApplicationContext.SCOPE_REQUEST,proxyModeScopedProxyMode.TARGET_CLASS)ComponentpublicclassRequestContext{}原理Spring 为 request Bean 创建 CGLIB 代理对象singleton Bean 注入的是代理。每次调用代理方法时代理从当前 Request 作用域通过RequestContextHolder底层 ThreadLocal获取真实的 Bean 实例确保每次请求获取的都是当前请求的实例。类似地session、application、websocket 作用域跨域注入时也需要配置 proxyMode。追问 4“Spring 的 session 作用域在分布式环境下有什么问题”高分回答Spring 的session作用域基于 Servlet 容器的 HttpSession在分布式/集群环境下存在 Session 不共享的问题Session 粘滞Sticky Session负载均衡器将同一用户固定到同一节点但节点故障时会话丢失Session 复制Tomcat 等容器支持 Session 复制但性能开销大不适合大规模集群Session 共享使用 Redis/Memcached 等外部存储共享 Session。推荐方案Spring Session Redis将 Session 存储到 Redis实现分布式共享同时支持session作用域 Bean 的跨节点一致性JWT Token无状态认证不依赖服务器 Session天然支持分布式OAuth2/OIDC使用令牌机制服务端无会话状态。现代微服务架构中推荐使用 JWT 等无状态方案彻底避免 Session 共享问题。追问 5“prototype 作用域 Bean 的销毁机制是怎样的”高分回答Spring不管理 prototype Bean 的销毁。具体表现初始化回调会执行PostConstruct、InitializingBean.afterPropertiesSet()、自定义 init-method 都会正常调用销毁回调不会执行PreDestroy、DisposableBean.destroy()、自定义 destroy-method不会被 Spring 调用。原因Spring 的DefaultSingletonBeanRegistry只管理 singleton Bean 的销毁prototype Bean 创建后直接返回给客户端Spring 不持有引用因此无法在容器关闭时遍历销毁。解决方案使用 ObjectFactory客户端通过ObjectFactory.getObject()获取实例使用完毕后手动调用清理方法自定义 Scope 实现在自定义 Scope 中管理 Bean 的生命周期DestructionAwareBeanPostProcessor实现该接口在postProcessBeforeDestruction()中处理 prototype Bean 的清理。最佳实践避免在 prototype Bean 中持有需要释放的资源或确保客户端代码负责资源清理。追问 6“Spring 允许自定义作用域吗怎么实现”高分回答Spring 允许自定义作用域需要实现org.springframework.beans.factory.config.Scope接口publicclassThreadScopeimplementsScope{privatefinalThreadLocalMapString,ObjectthreadScopeThreadLocal.withInitial(HashMap::new);OverridepublicObjectget(Stringname,ObjectFactory?objectFactory){MapString,ObjectscopethreadScope.get();returnscope.computeIfAbsent(name,k-objectFactory.getObject());}OverridepublicObjectremove(Stringname){returnthreadScope.get().remove(name);}// ... 实现 registerDestructionCallback、resolveContextualObject、getConversationId}然后通过CustomScopeConfigurer注册BeanpublicCustomScopeConfigurercustomScopeConfigurer(){CustomScopeConfigurerconfigurernewCustomScopeConfigurer();configurer.addScope(thread,newThreadScope());returnconfigurer;}典型应用场景实现线程级作用域每个线程一个实例用于线程池环境下的状态隔离。9. 方案选型速查表业务场景推荐作用域代理模式核心理由Service/DAO 层singletonNO无状态设计性能最优有状态策略对象prototypeNO/ObjectFactory每次获取新实例客户端管理生命周期请求级上下文TraceIdrequestTARGET_CLASS请求隔离跨 singleton 注入需代理用户购物车sessionTARGET_CLASS会话隔离注意分布式 Session全局配置/缓存applicationTARGET_CLASS应用级共享WebSocket 会话websocketTARGET_CLASS连接隔离线程级状态隔离自定义threadNO线程池环境下隔离状态延迟初始化singletonLazyNO优化启动速度面试官想要的满分总结Spring 的 6 种 Bean 作用域是 IoC 容器管理对象生命周期和可见范围的核心机制。理解作用域必须抓住三个关键点singleton 是默认且最常用必须设计为无状态利用容器启动时创建、全局复用的特性提升性能。Lazy可优化启动速度。prototype 的销毁陷阱Spring 不管理 prototype Bean 的销毁如果持有资源连接、线程池会导致内存泄漏。推荐使用ObjectFactory由客户端管理生命周期。Web 作用域必须配代理request/session等作用域 Bean 被 singleton Bean 注入时必须配置proxyMode TARGET_CLASS通过 CGLIB 代理确保每次调用获取当前作用域的真实实例。工程实践中99% 的 Bean 使用 singleton 无状态设计。Web 作用域request/session适用于请求级/会话级上下文但需注意分布式环境下的 Session 共享问题。自定义作用域如线程级作用域在特定场景下线程池状态隔离有独特价值。理解ScopedProxyMode的代理机制——从 ThreadLocal 获取当前作用域实例——是掌握 Web 作用域的核心。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~