此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
Bean 作用域
创建 Bean 定义时,将创建用于创建实际实例的配方 由该 Bean 定义定义的类。bean 定义是 recipe 很重要,因为它意味着,与 class 一样,您可以创建多个对象 实例。
您不仅可以控制各种依赖项和配置值,这些依赖项和配置值
插入到从特定 bean 定义创建的对象中,但也插入到控件
从特定 Bean 定义创建的对象的范围。这种方法是
功能强大且灵活,因为您可以选择所创建对象的范围
通过配置,而不必在 Java
类级别。可以将 Bean 定义为部署在多个范围之一中。
Spring 框架支持六个范围,其中四个范围仅在
您使用 Web 感知 .您还可以创建自定义范围。ApplicationContext
下表描述了支持的范围:
范围 | 描述 |
---|---|
(默认)将单个 bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。 |
|
将单个 Bean 定义的范围限定为任意数量的对象实例。 |
|
将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是
每个 HTTP 请求都有自己的 bean 实例,该实例是从单个 bean 的背面创建的
定义。仅在 web-aware Spring 的上下文中有效。 |
|
将单个 bean 定义的范围限定为 HTTP 的生命周期。仅在
Web 感知 Spring 的上下文。 |
|
将单个 Bean 定义的范围限定为 .仅在
Web 感知 Spring 的上下文。 |
|
将单个 Bean 定义的范围限定为 .仅在
Web 感知 Spring 的上下文。 |
线程范围可用,但默认情况下未注册。有关更多信息,
请参阅 SimpleThreadScope 的文档。
有关如何注册此 Scope或任何其他自定义 Scope 的说明,请参阅 Using a Custom Scope。 |
单例范围
仅管理单例 bean 的一个共享实例,以及 bean 的所有请求 替换为与该 Bean 定义的一个或多个 ID 匹配,则生成一个特定的 Bean 实例。
换句话说,当你定义一个 bean 定义并且它被定义为 singleton 时,Spring IoC 容器只创建对象的一个实例 由该 bean 定义定义。这个 single 实例存储在 singleton bean 以及该命名 bean 的所有后续请求和引用 返回缓存的对象。下图显示了单一实例范围的工作原理:
Spring 的单例 bean 概念不同于 Gang of Four (GoF) 模式书。GoF 单例对 Object 中,以便每个 ClassLoader 的Spring 单例的范围最好描述为每个容器 和每 bean 的 bean 中。这意味着,如果您在 单个 Spring 容器,则 Spring 容器将创建一个且仅创建一个实例 由该 Bean 定义定义的类。singleton scope 是默认范围 在Spring。要在 XML 中将 bean 定义为单例,可以定义一个 bean,如 以下示例:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
原型范围
bean 部署的非单例原型范围会导致创建一个新的
bean 实例。即 bean
被注入到另一个 bean 中,或者您通过在
容器。通常,您应该对所有有状态 bean 使用 prototype 范围,并且
singleton 作用域。getBean()
下图说明了 Spring 原型范围:
(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不持有 任何对话状态。我们更容易重用 单例图。
下面的示例将 Bean 定义为 XML 中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他范围相比, Spring 不管理 prototype bean 的 bean 中。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 生命周期回调。客户端代码必须清理 prototype-scoped 对象,并释放原型 bean 所持有的昂贵资源。要获取 Spring 容器来释放原型范围的 bean 持有的资源,请尝试使用 自定义 bean 后处理器,它保存对需要清理的 bean 的引用。
在某些方面, Spring 容器在原型范围的 bean 中的作用是
替换 Java 运算符。超过该点的所有生命周期管理都必须
由客户端处理。(有关 Spring 中 bean 生命周期的详细信息
容器,请参阅 生命周期回调。new
具有原型 bean 依赖项的单例 bean
当您使用依赖于原型 bean 的单例作用域 bean 时,请注意 依赖项在实例化时解析。因此,如果你依赖注入一个 原型范围的 bean 转换为单例范围的 bean,则会实例化一个新的原型 bean 然后 dependency-injection 到单例 bean 中。prototype 实例是唯一的 实例。
但是,假设您希望单例范围的 bean 获取 prototype 范围的 bean。您不能依赖注入 prototype 作用域的 bean 添加到你的单例 bean 中,因为该注入只发生 一次,当 Spring 容器实例化单例 bean 并解析 并注入其依赖项。如果您需要原型 bean 的新实例,网址为 运行时,请参阅 Method Injection。
请求、会话、应用程序和 WebSocket 范围
、 、 和 范围 仅可用
如果使用 Web 感知的 Spring 实现(例如 )。如果你将这些作用域与常规的 Spring IoC 容器一起使用,
比如 , an 抱怨
关于未知的 bean 范围。request
session
application
websocket
ApplicationContext
XmlWebApplicationContext
ClassPathXmlApplicationContext
IllegalStateException
初始 Web 配置
为了支持在 、 和 级别(Web 范围的 bean)界定 bean 的范围,一些次要的初始配置是
在定义 bean 之前是必需的。(此初始设置不是必需的
对于标准范围:和 。request
session
application
websocket
singleton
prototype
如何完成此初始设置取决于特定的 Servlet 环境。
如果您在 Spring Web MVC 中访问范围限定的 bean,则实际上,在
由 Spring 处理,无需特殊设置。 already 公开所有相关状态。DispatcherServlet
DispatcherServlet
如果使用 Servlet Web 容器,并且请求在 Spring 之外处理(例如,使用 JSF 时),则需要注册 .
这可以通过使用接口以编程方式完成。
或者,将以下声明添加到 Web 应用程序的文件中:DispatcherServlet
org.springframework.web.context.request.RequestContextListener
ServletRequestListener
WebApplicationInitializer
web.xml
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果您的侦听器设置存在问题,请考虑使用 Spring 的 .过滤器映射取决于周围的 Web
application configuration 的 app 配置,因此您必须根据需要对其进行更改。以下清单
显示了 Web 应用程序的 Filter 部分:RequestContextFilter
<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
、 和 all 都完全执行
同样的事情,即将 HTTP 请求对象绑定到正在服务的
那个请求。这使得请求和会话范围的 bean 进一步可用
沿着调用链向下。RequestContextListener
RequestContextFilter
Thread
请求范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器通过对每个 HTTP 请求使用 bean 定义来创建 bean 的新实例。也就是说,Bean 的范围限定在 HTTP 请求级别。您可以将内部的
state (状态),因为其他实例
从同一 bean 定义创建的 state 中看不到这些更改。
它们特定于单个请求。当请求完成处理时,
范围限定为请求的 bean 将被丢弃。LoginAction
loginAction
loginAction
loginAction
当使用注解驱动的组件或 Java 配置时,注解
可用于将组件分配给范围。以下示例显示了如何操作
为此,请执行以下操作:@RequestScope
request
-
Java
-
Kotlin
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
会话范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器在单个 HTTP 的生命周期中使用 bean 定义来创建 bean 的新实例。在其他
words,则 bean 的作用域实际上是在 HTTP 级别。如
使用请求范围的 bean,您可以更改实例的内部状态,即
根据需要创建尽可能多的 HTTP 实例,要知道其他 HTTP 实例也是
使用从同一 bean 定义创建的实例不会看到这些
状态更改,因为它们特定于单个 HTTP 。当
HTTP 最终被丢弃,范围限定为该特定 HTTP 的 bean 也被丢弃。UserPreferences
userPreferences
Session
userPreferences
Session
Session
userPreferences
Session
Session
Session
使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。@SessionScope
session
-
Java
-
Kotlin
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
应用范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器通过对整个 Web 应用程序使用一次 Bean 定义来创建 Bean 的新实例。也就是说,Bean 的范围限定在 level 上,并存储为 regular 属性。这有点类似于 Spring 单例 bean,但
在两个重要方面有所不同:它是 per , 而不是 per Spring 的单例(在任何给定的 Web 应用程序中都可能有多个),
它实际上是公开的,因此作为一个属性可见。AppPreferences
appPreferences
appPreferences
ServletContext
ServletContext
ServletContext
ApplicationContext
ServletContext
使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。这
以下示例显示了如何执行此操作:@ApplicationScope
application
-
Java
-
Kotlin
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
WebSocket 范围
WebSocket 范围与 WebSocket 会话的生命周期相关联,并应用于 STOMP over WebSocket 应用程序,有关详细信息,请参阅 WebSocket 范围。
作为依赖项的作用域 Bean
Spring IoC 容器不仅管理对象 (bean) 的实例化, 但也包括合作者(或依赖项)的联系。如果要注入 (对于 example) 将一个 HTTP 请求范围的 bean 转换为另一个寿命较长的 bean 中,您可以 选择注入 AOP 代理来代替作用域 Bean。也就是说,您需要注入 一个代理对象,它公开与 Scoped 对象相同的公共接口,但可以 还可以从相关范围(如 HTTP 请求)中检索真正的目标对象 并将方法调用委托到真实对象上。
您还可以在范围为 、
然后,引用通过可序列化的中间代理
因此能够在反序列化时重新获取目标 singleton bean。 当针对 scope 的 bean 进行声明时,每个方法
调用会导致创建一个新的目标实例,该实例的
然后,呼叫将被转发。 此外,作用域代理并不是从较短的作用域访问 bean 的唯一方法
生命周期安全时尚。您还可以声明注入点(即
constructor 或 setter 参数或 autowired 字段)设置为 ,
允许调用以按需检索当前实例
时间 — 无需保留实例或单独存储实例。 作为扩展变体,您可以声明 which deliver
其他几种访问变体,包括 和 . 此的 JSR-330 变体被调用,并与声明和每次检索尝试的相应调用一起使用。
有关 JSR-330 的更多详细信息,请参阅此处。 |
以下示例中的配置只有一行,但对于 了解其背后的 “为什么” 和 “如何”:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/> (1)
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
1 | 定义代理的行。 |
要创建这样的代理,您需要将子元素插入到
作用域 Bean 定义(请参见选择要创建的代理类型和基于 XML 架构的配置)。<aop:scoped-proxy/>
为什么 bean 的定义范围在 、 和 custom-scope
关卡需要元素吗?
考虑以下单例 bean 定义,并将其与
您需要为上述范围定义什么(请注意,以下 bean 定义并不完整):request
session
<aop:scoped-proxy/>
userPreferences
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例 bean () 注入了一个引用
添加到 HTTP 范围的 Bean () 中。这里的要点是 bean 是一个单例:它每个
container 及其依赖项(在本例中只有一个 bean)是
也只注射了一次。这意味着 Bean 仅在
完全相同的对象(即最初注入的对象)。userManager
Session
userPreferences
userManager
userPreferences
userManager
userPreferences
当将生存期较短的作用域 bean 注入
生存期较长的作用域 bean(例如,注入 HTTP 作用域的协作
bean 作为单例 bean 的依赖项)。相反,您需要一个对象,并且在 HTTP 的生命周期中,您需要一个对象
特定于 HTTP .因此,容器会创建一个对象,该对象
公开与类完全相同的公共接口(理想情况下是
对象,它是一个实例),它可以从范围机制(HTTP 请求、 等
forth)。容器将此代理对象注入到 bean 中,即
不知道此引用是代理。在此示例中,当实例在依赖项注入的对象上调用方法时,它实际上是在代理上调用方法。然后,代理从 HTTP(在本例中)获取真实对象,并将
方法调用到检索到的 Real Object 上。Session
userManager
Session
userPreferences
Session
UserPreferences
UserPreferences
UserPreferences
Session
userManager
UserPreferences
UserManager
UserPreferences
UserPreferences
Session
UserPreferences
因此,在将 bean 注入协作对象时,您需要以下(正确和完整的)配置,如下例所示
显示:request-
session-scoped
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型
默认情况下,当 Spring 容器为标记为
元素,将创建基于 CGLIB 的类代理。<aop:scoped-proxy/>
CGLIB 代理不会拦截私有方法。尝试调用私有方法 在这样的代理上,它不会委托给实际的 Scoped Target 对象。 |
或者,您可以将 Spring 容器配置为创建标准 JDK
基于接口的代理,通过为
元素的属性。使用 JDK
基于接口的代理意味着您的
application classpath 来影响此类代理。但是,这也意味着
作用域 Bean 必须至少实现一个接口,并且所有协作者
作用域 bean 注入到其中,必须通过其
接口。以下示例显示了基于接口的代理:false
proxy-target-class
<aop:scoped-proxy/>
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
有关选择基于类或基于接口的代理的更多详细信息, 请参见代理机制。
自定义范围
Bean 范围机制是可扩展的。您可以定义自己的
范围,甚至重新定义现有范围,尽管后者被认为是不好的做法
并且你不能覆盖 built-in 和 scopes。singleton
prototype
创建自定义范围
要将自定义范围集成到 Spring 容器中,您需要实现接口,如下所述
部分。有关如何实现自己的范围的想法,请参阅 Spring 框架本身提供的实现和 Scope
javadoc。
其中更详细地解释了您需要实现的方法。org.springframework.beans.factory.config.Scope
Scope
该接口有四种方法从 scope 中获取对象,从
范围,然后销毁它们。Scope
例如,会话范围的实现返回会话范围的 Bean(如果它 不存在,则该方法在将 Bean 绑定到 会话以供将来参考)。以下方法从 底层范围:
-
Java
-
Kotlin
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
例如,会话范围实现从
基础会话。应该返回该对象,但如果
找不到具有指定名称的对象。以下方法从
底层范围:null
-
Java
-
Kotlin
Object remove(String name)
fun remove(name: String): Any
以下方法注册一个回调,范围应在 destroyed 或当 scope 中的指定对象被销毁时:
-
Java
-
Kotlin
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
有关销毁回调的更多信息,请参见 javadoc 或 Spring 范围实现。
以下方法获取基础范围的聊天标识符:
-
Java
-
Kotlin
String getConversationId()
fun getConversationId(): String
此标识符对于每个范围都不同。对于会话范围的实现,此 identifier 可以是会话标识符。
使用自定义范围
在编写和测试一个或多个自定义实现后,您需要将
Spring 容器知道您的新范围。以下方法是中心
向 Spring 容器注册 new 的方法:Scope
Scope
-
Java
-
Kotlin
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
此方法在接口上声明,该接口可用
通过 Spring 附带的大多数具体实现上的属性。ConfigurableBeanFactory
BeanFactory
ApplicationContext
该方法的第一个参数是与
一个范围。Spring 容器本身中此类名称的示例是 和 。该方法的第二个参数是实际实例
。registerScope(..)
singleton
prototype
registerScope(..)
Scope
假设您编写了自定义实现,然后按所示注册它
在下一个示例中。Scope
下一个示例使用 Spring 中包含的 ,但不是
registered (默认)。对于您自己的自定义实现,说明是相同的。SimpleThreadScope Scope |
-
Java
-
Kotlin
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
然后,您可以创建符合 custom 的 scopeping rules 的 bean 定义,如下所示:Scope
<bean id="..." class="..." scope="thread">
使用自定义实现,您不仅限于编程注册
的范围。您还可以使用类以声明方式进行注册,如下例所示:Scope
Scope
CustomScopeConfigurer
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
当你放置在实现的声明中时,确定范围的是工厂 Bean 本身,而不是对象
从 返回。<aop:scoped-proxy/> <bean> FactoryBean getObject() |