教程:Bean作用域作者:太虚野老
目录
说明: 3
Bean作用域 4
singleton作用域 4
prototype作用域 5
具有原型Bean依赖的单例Bean 7
Request, Session, Application, 和 WebSocket 作用域 7
初始网络配置 7
request作用域 8
session作用域 8
application作用域 9
websocket作用域 9
作用域 Bean 作为依赖项 9
选择要创建的代理类型 11
直接注入Request/Session引用 11
自定义作用域 11
创建自定义作用域 11
使用自定义作用域 12
附录一:原型作用域(Prototype)测试代码 13
附录二:具有原型Bean依赖的单例Bean测试代码 16
附录三:四个web作用域测试代码 19
1、@RequestScope 19
2、@SessionScope 21
3、@ApplicationScope 25
4、@WebSocketScope 28
附录四:作用域 Bean 作为依赖项测试代码 40
附录五:演示ObjectFactory的作用 43
附录六:演示ObjectProvider的作用 48
附录七:火狐浏览器清除任意网站的 Cookie 55
说明:
1.Spring Framework版本:7.0.6
2.开发框架:Spring boot(版本4.0.5)
3.开发工具:eclipse 2025-09 (4.37.0)
4.jdk版本:25
5.操作系统:debian12
Bean作用域
当你创建一个bean定义时,你实际上是在为创建由该bean定义所定义的类的实际实例制定一个配方。bean定义是配方的概念很重要,因为这意味着,就像使用一个类一样,你可以根据一个配方创建出许多对象实例。
您不仅可以控制从特定bean定义创建的对象中要插入的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的作用域。这种方法功能强大且灵活,因为您可以通过配置来选择所创建对象的范围,而不必在Java类级别上硬性规定对象的范围。Bean 可以被定义部署在多个作用域中的任意一个。Spring Framework 支持六个作用域,其中四个仅在使用支持 Web 的 ApplicationContext 时可用。您还可以创建自定义作用域。
下表描述了支持的作用域:
Table 1. Bean scopes | |
作用域 | 描述 |
singleton | (默认)将单个Bean定义的作用域限定为每个Spring IoC容器中的一个对象实例。 |
prototype | 将单个Bean定义作用域扩展到任意数量的对象实例。 |
request | 将单个Bean定义的作用域限定在单个HTTP请求的生命周期内。也就是说,每个HTTP请求都会根据单个Bean定义创建自己的Bean实例。仅在支持Web的Spring ApplicationContext上下文中有效。 |
session | 将单个Bean定义的作用域限定在HTTP Session的生命周期内。仅在支持Web的Spring ApplicationContext上下文中有效。 |
application | 将单个Bean定义的作用域限定在ServletContext的生命周期内。仅在支持Web的SpringApplicationContext上下文中有效。 |
websocket | 将单个Bean定义的作用域限定在WebSocket的生命周期内。仅在支持Web的Spring ApplicationContext上下文中有效。 |
注意:
线程作用域可用,但默认情况下未注册。有关更多信息,请参阅SimpleThreadScope的文档。有关如何注册此作用域或任何其他自定义作用域的说明,请参阅使用自定义作用域。
singleton作用域
只有一个单例Bean的共享实例受到管理,对于具有与该Bean定义匹配的一个或多个ID的Bean的所有请求,都会导致Spring容器返回该特定的Bean实例。
换言之,当你定义一个 bean 定义并且其作用域为单例时,Spring IoC 容器会为该 bean 定义所定义的对象创建一个实例。这个单一实例存储在这样一个单例Bean的缓存中,所有后续对该命名Bean的请求和引用都会返回缓存的对象。下图展示了单例作用域的工作原理:
Spring中的单例Bean概念与《四人组(GoF)模式》一书中所定义的单例模式有所不同。GoF 单例模式对对象的范围进行了硬编码,使得每个ClassLoader(类加载器)只能创建一个且只能创建一个特定类的实例。Spring 单例的作用域最好描述为容器内和 bean 内。这意味着,如果你在单个Spring容器中为某个特定类定义了一个bean,那么Spring容器将创建该bean定义所定义的类的一个且仅一个实例。在Spring中,单例作用域是默认的作用域。要在XML中将一个bean定义为单例,可以按照以下示例定义一个bean:
<bean/> <!-- 以下内容是等价的,但有些冗余(默认作用域为单例) --> <bean scope="singleton"/>prototype作用域
在 bean 部署中,非单例原型作用域会导致每次请求特定 bean 时都会创建一个新的 bean 实例。也就是说,当将该 bean 注入到另一个 bean 中,或者通过在容器上调用getBean()方法请求该 bean 时,都会如此。通常,您应该将所有有状态的 bean 使用原型作用域,将无状态的 bean 使用单例作用域。
下图展示了Spring原型作用域:
数据访问对象(DAO)通常不配置为原型,因为典型的DAO不包含任何会话状态。对我们来说,重用单例图的核心部分更容易。)
以下示例在XML中将一个bean定义为原型:
<bean scope="prototype"/>与其他作用域相比,Spring 并不管理原型 bean 的完整生命周期。容器会实例化、配置并以其他方式组装原型对象,然后将其交给客户端,而不再保留该原型实例的任何记录。因此,尽管初始化生命周期回调方法会在所有对象上调用,而不考虑其作用域,但在原型的情况下,配置的销毁生命周期回调方法则不会被调用。客户端代码必须清理原型作用域内的对象,并释放原型Bean占用的昂贵资源。要让Spring容器释放原型作用域Bean占用的资源,可以尝试使用一个自定义Bean后处理器,该处理器持有需要清理的Bean的引用。
在某些方面,Spring容器在原型作用域Bean中的作用相当于Java new操作符的替代品。此后所有的生命周期管理都必须由客户端处理。(有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)
注:
每一次
@Autowired注入都会生成一个全新不同的实例,单例作用域除外。每次依赖注入 / 每次 getBean () 都新建对象
永不自动销毁
Spring容器本身不会保存任何原型 Bean 实例引用!
触发销毁方法的两种方法:
使用
DisposableBean接口(原生销毁接口)。不用注解,实现接口就行调用ConfigurableBeanFactory.
destroyBean(beanName,beanInstance)。@PreDestroy依然生效,必须保留触发销毁方法的两种方法测试代码详见附录一。
想要每次调用都拿到全新原型,不能直接字段注入,要用:
ObjectProvider<Bean>或者ApplicationContext.getBean(),或者ConfigurableBeanFactory.getBean()也可以。
@Autowired private ObjectProvider<ProtoBean> provider; public void getNew(){ ProtoBean b1 = provider.getObject(); ProtoBean b2 = provider.getObject(); // b1 != b2 }具有原型Bean依赖的单例Bean
当您使用具有原型Bean依赖的单例作用域Bean时,请注意依赖是在实例化时解析的。因此,如果你将一个原型作用域的Bean依赖注入到一个单例作用域的Bean中,一个新的原型Bean将被实例化,然后被依赖注入到单例Bean中。这个原型实例是唯一提供给单例作用域Bean的实例。
然而,假设您希望单例作用域的Bean在运行时反复获取原型作用域Bean的新实例。你不能将原型作用域的Bean依赖注入到单例Bean中,因为这种注入只在Spring容器实例化单例Bean并解析和注入其依赖项时发生一次。如果你在运行时需要多次获取原型Bean的新实例,请参阅方法注入。
测试代码详见附录二。
Request, Session, Application, 和 WebSocket 作用域
只有在使用支持 Web 的 Spring ApplicationContext 实现(如 XmlWebApplicationContext)时,request,session,application和websocket作用域才可用。如果你将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会抛出一个IllegalStateException,指出存在未知的Bean作用域。
四个 Web作用域的测试代码详见附录三。
初始网络配置
为了支持在request,session,application和websocket(Web范围Bean)对Bean的范围界定,在定义Bean之前需要进行一些初步的简单配置。(对于标准范围:单例和原型,则不需要进行此初始设置。)
如何完成这一初始设置取决于你特定的Servlet环境。
如果你在Spring Web MVC中访问作用域Bean,实际上是在Spring DispatcherServlet处理的请求中,无需进行特殊设置。DispatcherServlet已经公开了所有相关状态。
如果你使用一个Servlet Web容器,且请求是在Spring的DispatcherServlet之外处理的(例如,在使用JSF时),那么你需要注册org.springframework.web.context.request.RequestContextListener这个ServletRequestListener。这可以通过使用WebApplicationInitializer接口以编程方式实现。或者,将以下声明添加到您的Web应用程序的web.xml文件中:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>或者,如果您的监听器设置存在问题,可以考虑使用Spring的RequestContextFilter。该过滤器的映射依赖于周围的Web应用程序配置,因此您必须根据实际情况进行更改。以下清单显示了Web应用程序的过滤器部分:
<web-app> ... <filter> <filter-name>requestContextFilter</filter-name> <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>requestContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... </web-app>DispatcherServlet、RequestContextListener和RequestContextFilter都做着完全相同的事情,即将HTTP请求对象绑定到处理该请求的线程上。这使得请求作用域和会话作用域的Bean在调用链的下游也可用。
request作用域
考虑以下用于 bean 定义的 XML 配置:
<bean scope="request"/>Spring容器会为每个HTTP请求使用loginAction bean的定义来创建一个新的LoginAction bean实例。也就是说,loginAction bean的作用域是在HTTP请求级别。你可以随意更改所创建实例的内部状态,因为从同一loginAction bean定义创建的其他实例看不到这些状态变化。它们特定于单个请求。当请求处理完毕后,作用域仅限于该请求的Bean将被丢弃。
在使用注解驱动组件或Java配置时,可以使用@RequestScope注解将组件分配给请求作用域。以下示例展示了如何执行此操作:
@RequestScope @Component public class LoginAction { // ... }Request作用域 Bean销毁时机:在一次 HTTP 请求处理完全结束、响应返回客户端 之后,立刻销毁。
session作用域
考虑以下用于 bean 定义的 XML 配置:
<bean scope="session"/>Spring容器在单个HTTP会话的生命周期内,通过使用userPreferences bean的定义来创建UserPreferences bean的新实例。换言之,userPreferences bean的作用域实际上是在HTTPSession级别。与请求作用域的Bean一样,您可以随意更改所创建实例的内部状态,因为其他同样使用从同一userPreferences Bean定义创建的实例的HTTP Session实例看不到这些状态更改,因为这些更改特定于单个HTTP Session。当HTTP Session最终被丢弃时,作用域限定在该特定HTTP Session内的Bean也会被丢弃。
在使用注解驱动组件或Java配置时,您可以使用@SessionScope注解将组件分配到会话作用域。
@SessionScope @Component public class UserPreferences { // ... }session作用域bean在会话结束后不会立即销毁,要等到时间到期后销毁。除非立即调用HttpSession的invalidate()方法。
application作用域
考虑以下用于 bean 定义的 XML 配置:
<bean scope="application"/>Spring 容器会根据 appPreferences 的 bean 定义在整个 Web 应用程序中仅创建一次 AppPreferences 类的新实例。也就是说,appPreferences bean的作用域是ServletContext级别,并作为常规的ServletContext属性进行存储。这在一定程度上类似于Spring的单例bean,但在两个重要方面有所不同: 它是一个针对每个ServletContext的单一实例,而不是针对每个Spring ApplicationContext(在任何给定的Web应用程序中可能存在多个)的单一实例,并且它实际上作为ServletContext的属性被公开,因此是可见的。
在使用注解驱动组件或Java配置时,您可以使用@ApplicationScope注解将组件分配到application作用域。以下示例展示了如何操作:
@ApplicationScope @Component public class AppPreferences { // ... }websocket作用域
WebSocket作用域与WebSocket会话的生命周期相关联,并适用于基于WebSocket的STOMP应用,更多详情请参阅WebSocket作用域。
作用域 Bean 作为依赖项
Spring 的 IoC 容器不仅管理您的对象(即 bean)的实例化,还负责将协作对象(或依赖项)连接起来。如果您想将(例如)具有 HTTP 请求作用域的 bean 注入到作用域更长的另一个 bean 中,您可以选择注入一个 AOP 代理来替代该作用域的 bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但还能从相关作用域(例如 HTTP 请求)中检索出真正的目标对象,并将方法调用委托给该真实对象。
注意:
您还可以在作用域为单例的Bean之间使用<aop:scoped-proxy/>,这样引用就会通过一个可序列化的中间代理,从而能够在反序列化时重新获取目标单例Bean。
当对原型作用域的Bean声明<aop:scoped-proxy/>时,对共享代理的每次方法调用都会创建一个新的目标实例,然后将调用转发到该实例。
此外,作用域代理并不是以生命周期安全的方式从较短作用域访问Bean的唯一方法。您还可以将注入点(即构造函数、setter方法参数或自动注入字段)声明为ObjectFactory<MyTargetBean>
