官网说明: 

Bean作用域

创建 bean 定义时,将创建一个配方,用于创建由 bean 定义定义的 class 的实际实例。 bean 定义是配方的 idea 很重要,因为它意味着,与 class 一样,您可以从单个配方创建许多 object 实例。

您不仅可以控制要插入到从特定 bean 定义创建的 object 的各种依赖项和 configuration 值,还可以控制从特定 bean 定义创建的 objects 的范围。这种方法功能强大且灵活,因为您可以选择通过 configuration 创建的 objects 的范围,而不必在 Java class level 的 object 范围内进行烘焙。 Beans 可以定义为部署在多个范围之一中。 Spring Framework 支持六个范围,其中四个范围仅在您使用 web-aware ​​ApplicationContext​​时可用。你也可以创建自定义范围。

以下 table 描述了支持的范围:

作用域

描述

singleton

(默认)为每个 Spring IoC 容器的单个 object 实例定义单个 bean 定义。

原型

为任意数量的 object 实例定义单个 bean 定义。

请求

将单个 bean 定义范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。仅在 web-aware Spring ​​ApplicationContext​​的 context 中有效。

session

将单个 bean 定义范围限定为 HTTP ​​Session​​的生命周期。仅在 web-aware Spring ​​ApplicationContext​​的 context 中有效。

应用

将单个 bean 定义范围限定为​​ServletContext​​的生命周期。仅在 web-aware Spring ​​ApplicationContext​​的 context 中有效。

WebSocket

将单个 bean 定义范围限定为​​WebSocket​​的生命周期。仅在 web-aware Spring ​​ApplicationContext​​的 context 中有效。

从 Spring 3.0 开始,线程范围可用,但默认情况下未注册。有关更多信息,请参阅SimpleThreadScope的文档。有关如何注册此范围或任何其他自定义范围的说明,请参阅使用自定义范围。

 1.5.1. Singleton 作用域

只管理 singleton bean 的一个共享实例,并且 beans 的所有请求都带有一个或多个 match bean 定义的 ID 导致 Spring 容器返回的一个特定 bean 实例。

换句话说,当您定义 bean 定义并将其范围限定为 singleton 时,Spring IoC 容器只创建该 bean 定义定义的 object 的一个实例。此单个实例存储在此类 singleton beans 的缓存中,并且所有后续请求和 references 都指向 bean return 缓存的 object。下图显示了 singleton 范围的工作原理:

Bean的作用域_xml

Spring 的 singleton bean 概念与 singleton pattern 的概念不同,如四人帮(GoF)模式书中所定义的那样。 GoF singleton hard-codes object 的范围,使得每个 ClassLoader 创建一个且只有一个特定 class 的实例。 Spring singleton 的范围最好描述为 per-container 和 per-bean。这意味着,如果在单个 Spring 容器中为特定 class 定义一个 bean,则 Spring 容器将创建该_ bean 定义所定义的 class 的一个且仅一个实例。 singleton 范围是 Spring 中的默认范围。要在中将 bean 定义为 singleton,您可以定义 bean,如下面的 example 所示:



<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"/>


 

 1.5.2. 原型作用域

bean 部署的 non-singleton 原型范围导致每隔 time 创建一个新的 bean 实例,并对该特定 bean 发出请求。也就是说,bean 被注入到另一个 bean 中,或者通过容器上的​​getBean()​​方法调用来请求它。通常,您应该为所有有状态 beans 使用原型范围,为 stateless beans 使用 singleton 范围。

下图说明了 Spring 原型范围:

Bean的作用域_spring_02

(数据访问 object(DAO)通常不配置为原型,因为典型的 DAO 不包含任何会话 state。我们更容易重用 singleton diagram.)的核心

以下 example 将 bean 定义为 XML 中的原型:



<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>


 

与其他范围相比,Spring 不管理原型 bean 的完整生命周期。容器实例化,配置和组装原型 object 并将其交给 client,没有该原型实例的进一步 record。因此,尽管无论范围如何都在所有 object 上调用初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。 client code 必须清理 prototype-scoped object 并释放原型 beans 所拥有的昂贵资源。要让 Spring 容器释放 prototype-scoped beans 所拥有的资源,请尝试使用自定义bean post-processor,它包含需要清理的 beans 的 reference。

在某些方面,Spring 容器关于 prototype-scoped bean 的角色是 Java ​​new​​ operator 的替代品。超过该点的所有生命周期管理必须由 client 处理。 (有关 Spring 容器中 bean 生命周期的详细信息,请参阅生命周期回调 .)

 1.5.3. Singleton Beans with Prototype-bean Dependencies

当您对原型 beans 使用带有依赖关系的 singleton-scoped beans 时,请注意在实例化 time 时解析依赖关系。因此,如果原型实例是提供给 singleton-scoped bean 的唯一实例。

但是,假设您希望 singleton-scoped bean 在运行时重复获取 prototype-scoped bean 的新实例。你不能@ 如果您需要在运行时多次使用原型 bean 的新实例,请参阅方法注入

 1.5.4. Request,Session,Application 和 WebSocket Scopes

仅当您使用 web-aware Spring ​​ApplicationContext​​ implementation(例如​​XmlWebApplicationContext​​)时,​​request​​,​​session​​,​​application​​和​​websocket​​范围才可用。如果将这些范围与常规 Spring IoC 容器(例如​​ClassPathXmlApplicationContext​​)一起使用,则会抛出抱怨未知 bean 范围的​​IllegalStateException​​。

初始 Web Configuration

要在​​request​​,​​session​​,​​application​​和​​websocket​​级别(web-scoped beans)支持 beans 的范围设定,在定义 beans 之前需要一些小的初始 configuration。 (标准范围不需要此初始设置:​​singleton​​和​​prototype​​ .)

如何完成此初始设置取决于您的特定 Servlet 环境。

如果在 Spring Web MVC 中访问范围 beans,实际上是在 Spring ​​DispatcherServlet​​处理的请求中,则无需进行特殊设置。 ​​DispatcherServlet​​已经暴露了所有相关的 state。

如果使用 Servlet 2.5 web 容器,并且在 Spring 的​​DispatcherServlet​​之外处理请求(对于 example,当使用 JSF 或 Struts 时),则需要注册​​org.springframework.web.context.request.RequestContextListener​​ ​​ServletRequestListener​​。对于 Servlet 3.0,可以使用​​WebApplicationInitializer​​接口以编程方式完成此操作。或者,或者对于旧容器,将以下声明添加到 web application 的​​web.xml​​文件中:



<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>


 

或者,如果 listener 设置存在问题,请考虑使用 Spring 的​​RequestContextFilter​​。过滤器映射取决于周围的 web application configuration,因此您必须根据需要进行更改。以下清单显示了 web application 的过滤器部分:



<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 请求 object 绑定到为该请求提供服务的​​Thread​​。这使 beans 在请求和 session-scoped 可进一步在调用链中可用。

请求作用域

考虑以下针对 bean 定义的 XML configuration:



<bean id="loginAction" class="com.something.LoginAction" scope="request"/>


 

Spring 容器通过对每个 HTTP 请求使用​​loginAction​​ bean 定义来创建​​LoginAction​​ bean 的新实例。也就是说,​​loginAction​​ bean 的作用域是 HTTP 请求 level。您可以根据需要更改创建的实例的内部 state,因为从同一​​loginAction​​ bean 定义创建的其他实例在 state 中看不到这些更改。它们特别针对个人要求。当请求完成处理时,将放弃作用于请求的 bean。

使用 annotation-driven 组件或 Java configuration 时,​​@RequestScope​​ annotation 可用于将 component 分配给​​request​​范围。以下 example 显示了如何执行此操作:



@RequestScope
@Component
public class LoginAction {
// ...
}


 

 Session 作用域

考虑以下针对 bean 定义的 XML configuration:



<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>


 

Spring 容器通过在单个 HTTP ​​Session​​的生命周期中使用​​userPreferences​​ bean 定义来创建​​UserPreferences​​ bean 的新实例。换句话说,​​userPreferences​​bean 有效地限定在 HTTP ​​Session​​ level。与 request-scoped beans 一样,您可以根据需要更改创建的实例的内部 state,因为知道同样使用从同一​​userPreferences​​ bean 定义创建的实例的其他 HTTP ​​Session​​实例在 state 中看不到这些更改,因为它们特定于单个 HTTP ​​Session​​。当最终丢弃 HTTP ​​Session​​时,也将丢弃作用于该特定 HTTP ​​Session​​的 bean。

使用 annotation-driven 组件或 Java configuration 时,可以使用​​@SessionScope​​ annotation 将 component 分配给​​session​​范围。



@SessionScope
@Component
public class UserPreferences {
// ...
}


 

 Application Scope

考虑以下针对 bean 定义的 XML configuration:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>


Spring 容器通过对整个 web application 使用​​appPreferences​​ bean 定义一次来创建​​AppPreferences​​ bean 的新实例。也就是说,​​appPreferences​​ bean 的作用域为​​ServletContext​​ level 并存储为常规​​ServletContext​​属性。这有点类似于 Spring singleton bean 但在两个重要方面有所不同:它是 singleton 每​​ServletContext​​,而不是 Spring'ApplicationContext'(在任何给定的 web application 中可能有几个),它实际上是暴露的,因此作为​​ServletContext​​属性可见。

使用 annotation-driven 组件或 Java configuration 时,可以使用​​@ApplicationScope​​ annotation 将 component 分配给​​application​​范围。以下 example 显示了如何执行此操作:



@ApplicationScope
@Component
public class AppPreferences {
// ...
}


 

 Scoped Beans as Dependencies

Spring IoC 容器不仅管理 objects(beans)的实例化,还管理协作者(或依赖项)的连接。如果要将 HTTP request-scoped bean 注入(对于 example)到 longer-lived 范围的另一个 bean,您可以选择 inject AOP 代理来代替作用域 bean。也就是说,您需要 inject 一个代理 object,它暴露与作用域 object 相同的公共接口,但也可以从相关范围(例如 HTTP 请求)中检索真实目标 object,并将方法 calls 委托给真实的 object。

您也可以在作为​​singleton​​的 beans 之间使用​​<aop:scoped-proxy/>​​,然后 reference 将通过可序列化的中间代理,因此能够在反序列化时 re-obtain 目标 singleton bean。

当针对范围​​prototype​​的 bean 声明​​<aop:scoped-proxy/>​​时,共享代理上的每个方法调用都会导致创建一个新的目标实例,然后将该呼叫转发到该目标实例。

此外,范围代理不是以 lifecycle-safe 方式从较短范围访问 beans 的唯一方法。您还可以将注入点(即构造函数或 setter 参数或自动装配字段)声明为​​ObjectFactory<MyTargetBean>​​,允许​​getObject()​​调用在每次需要时按需检索当前实例 - 无需保留实例或存储它分别。

作为扩展变体,您可以声明​​ObjectProvider<MyTargetBean>​​,它提供了几个额外的访问变体,包括​​getIfAvailable​​和​​getIfUnique​​。

这个的 JSR-330 变体称为​​Provider​​,并且每次检索尝试都会使用​​Provider<MyTargetBean>​​声明和相应的​​get()​​调用。有关 JSR-330 整体的更多详细信息,请参阅这里。

以下 example 中的 configuration 只有一个 line,但了解“为什么”以及它背后的“如何”非常重要:



<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://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>


 

要创建这样的代理,请将 child ​​<aop:scoped-proxy/>​​元素插入到作用域 bean 定义中(请参阅选择要创建的代理类型和XML Schema-based configuration)。为什么在​​request​​,​​session​​和 custom-scope 级别定义 beans 的定义需要​​<aop:scoped-proxy/>​​元素?考虑以下 singleton bean 定义,并将其与您需要为上述范围定义的内容进行对比(请注意,以下​​userPreferences​​ bean 定义不完整):



<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>


 

在前面的 example 中,singleton bean(​​userManager​​)注入一个 reference 到 HTTP ​​Session​​ -scoped bean(​​userPreferences​​)。这里的重点是​​userManager​​ bean 是 singleton:它每个容器只实例化一次,它的依赖项(在这种情况下只有一个,​​userPreferences​​ bean)也只注入一次。这意味着​​userManager​​ bean 仅在完全相同的​​userPreferences​​ object(即最初注入它的那个)上运行。

这不是将 shorter-lived 作用域 bean 注入 longer-lived 作用域 bean 时所需的行为(对于 example,将-scoped 协作 bean 作为依赖项注入 singleton bean)。相反,您需要一个​​userManager​​ object,并且,对于 HTTP ​​Session​​的生命周期,您需要一个特定于 HTTP ​​Session​​的​​userPreferences​​ object。因此,容器创建一个 object,它公开与​​UserPreferences​​ class 完全相同的公共接口(理想情况下是一个​​UserPreferences​​实例的 object),它可以从作用域机制中获取真正的​​UserPreferences​​ object(HTTP 请求,​​Session​​等) 。容器将此代理 object 注入​​userManager​​ bean,它不知道此​​UserPreferences​​ reference 是代理。在此 example 中,当​​UserManager​​实例在 dependency-injected ​​UserPreferences​​ object 上调用方法时,它实际上是在代理上调用方法。然后代理从(在这种情况下)HTTP ​​Session​​中获取真实​​UserPreferences​​ object,并将方法调用委托给检索到的真实​​UserPreferences​​ object。

因此,在将​​request-​​和​​session-scoped​​ beans 注入协作 objects 时,需要以下(正确和完整)configuration,如下面的 example 所示:



<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 容器为使用​​<aop:scoped-proxy/>​​元素标记的 bean 创建代理时,会创建 CGLIB-based class 代理。

CGLIB 代理只拦截公共方法 calls!不要在这样的代理上调用 non-public 方法。它们不会委托给实际作用域的目标 object。

或者,您可以通过为​​<aop:scoped-proxy/>​​元素的​​proxy-target-class​​属性的 value 指定​​false​​来配置 Spring 容器以为此类作用域 beans 创建标准 JDK interface-based 代理。使用 JDK interface-based 代理意味着您不需要 application classpath 中的其他 libraries 来影响此类代理。但是,它还意味着作用域 bean 的 class 必须至少实现一个接口,并且注入了作用域 bean 的所有协作者必须通过其中一个接口引用 bean。以下 example 显示了基于接口的代理:



<!-- 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>


 

有关选择 class-based 或 interface-based 代理的更多详细信息,请参阅代理机制。

 1.5.5. 自定义作用域

bean 作用域机制是可扩展的。您可以定义自己的范围,甚至可以重新定义现有范围,但后者被认为是不好的做法,您无法覆盖 built-in ​​singleton​​和​​prototype​​范围。

 创建自定义作用域

要将自定义作用域集成到 Spring 容器中,需要实现​​org.springframework.beans.factory.config.Scope​​接口,本节将对此进行介绍。有关如何实现自己的范围的 idea,请参阅 Spring Framework 本身和范围 javadoc 提供的​​Scope​​ implementations,它们解释了您需要更详细地实现的方法。

​Scope​​接口有四种方法可以从作用域中获取 objects,将它们从作用域中删除,然后将它们销毁。

session 范围 implementation,对于 example,返回 session-scoped bean(如果它不存在,则该方法在将__ssion 绑定到 session 以用于将来 reference 之后返回 bean 的新实例)。以下方法从基础范围返回 object:

Object get(String name, ObjectFactory objectFactory)


session 范围 implementation,对于 example,从基础 session 中删除 session-scoped bean。应返回 object,但如果找不到具有指定 name 的 object,则可以 return null。以下方法从基础范围中删除 object:

Object remove(String name)


以下方法记录范围在销毁时或范围中指定的 object 被销毁时应执行的回调:

void registerDestructionCallback(String name, Runnable destructionCallback)


有关销毁回调的更多信息,请参阅javadoc或 Spring 范围 implementation。

以下方法获取基础范围的对话标识符:

String getConversationId()


每个范围的标识符都不同。对于 session 范围的 implementation,此标识符可以是 session 标识符。

使用自定义作用域

在编写并测试一个或多个自定义​​Scope​​ __mplement 之后,您需要让 Spring 容器知道您的新范围。以下方法是使用 Spring 容器注册新​​Scope​​的核心方法:

void registerScope(String scopeName, Scope scope);


此方法在​​ConfigurableBeanFactory​​接口上声明,在 Spring 附带的大多数具体​​ApplicationContext​​ implementations 上可通过​​BeanFactory​​ property 获得。

​registerScope(..)​​方法的第一个参数是与范围关联的唯一 name。 Spring 容器本身中此类名称的示例是​​singleton​​和​​prototype​​。 ​​registerScope(..)​​方法的第二个参数是您希望注册和使用的自定义​​Scope​​ implementation 的实际实例。

假设您编写自定义​​Scope​​ implementation,然后按照下一个 example 中的说明进行注册。



Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);


 

然后,您可以创建符合自定义​​Scope​​的作用域规则的 bean 定义,如下所示:



<bean id="..." class="..." scope="thread">


 

使用自定义​​Scope​​ implementation,您不仅限于范围的编程注册。您还可以使用​​CustomScopeConfigurer​​ class 以声明方式执行​​Scope​​注册,如下面的 example 所示:



<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://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>


代码测试: 

xml



<!--  
1. 默认情况下, IOC 容器中的 bean 是单例的! 若对象是单例的, 则在创建 IOC 容器时即创建 bean 的实例, 并对 bean 的属性进行初始化.
2. 可以通过 bean 的 scope 属性来修改 bean 的作用域. 若取值为 prototype, 则 bean 为原型的: 每次向容器获取实例, 得到的都是一个新的对象.
而且, 不在创建 IOC 容器时创建 bean 的实例了.
3. IOC 容器中 bean 的生命周期:
3.1 一般地, 讨论 bean 的生命周期, 是建立在 bean 是单例的基础上的.
3.2 可以为 bean 指定 init 和 destroy 方法
3.3 还可以通过 bean 的后置处理器来更加丰富 bean 的生命周期方法(面试时.).
-->
<bean id="helloWorld"
class="com.atguigu.spring.helloworld.HelloWorld"
scope="prototype"
init-method="init"
destroy-method="destroy">
<property name="userName" value="atguigu"></property>
</bean>


测试类



public class Main {

public static void main(String[] args) {

//1. 创建 IOC 容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("111111111");
//2. 从 IOC 容器中获取 bean 实例
HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");

//3. 调用 bean 的方法
helloWorld.hello();
System.out.println("222222222");
HelloWorld helloWorld2 = (HelloWorld) ctx.getBean("helloWorld");
System.out.println(helloWorld == helloWorld2);

//4. 关闭容器
ctx.close();
}

}



public class HelloWorld {

//字段
private String user;

public HelloWorld() {
System.out.println("HelloWorld's constructor...");
}

//JavaBean 使用 setter 和 getter 来定义属性
public void setUserName(String user) {
System.out.println("setUserName:" + user);
this.user = user;
}

public void hello(){
System.out.println("Hello:" + user);
}

public void init(){
System.out.println("init method...");
}

public void destroy(){
System.out.println("destroy method...");
}

}


测试结果:



111111111
HelloWorld's constructor...
setUserName:atguigu
init method...
Hello:atguigu
222222222
HelloWorld's constructor...
setUserName:atguigu
init method...
false


改成singleton



HelloWorld's constructor...
setUserName:atguigu
init method...
111111111
Hello:atguigu
222222222
true
十二月 10, 2019 3:34:55 下午 org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@3f91beef: startup date [Tue Dec 10 15:34:55 CST 2019]; root of context hierarchy
destroy method...


注:其实在IOC容器初始化的时候,会对bean进行判断,是否为单列,是的话会进行实例化,原型只有在获取的时候,才会实例化。