Spring Bean作用域【译】

官方的文档有些说明的很清楚,大致浏览一遍很有收获。

地址:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes

1.5. Bean Scopes

      创建bean定义时,将创建一个配方(recipe,怎么翻译好呢?)来创建该bean定义所定义的类的实际实例。 bean定义是配方的想 法很重要,因为它意味着与类一样,您可以从一个配方中创建许多对象实例。

       您不仅可以控制要插入到从特定be an定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的作用域。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的作用域,而不必在Java类级别烘焙对象的作用域。bean可以定义为部署在多个作用域中的一个。Spring框架支持六个作用域,其中四个作用域只有在使用支持web的ApplicationContext时才可用。您还可以创建自定义作用域(a custom scope.)。

下表描述了受支持的作用域作用域:

Scope

Description

singleton

(默认值)将每个Spring IoC容器的单个bean定义作用域限定为单个对象实例。

prototype

将单个bean定义的作用域限定为任意数量的对象实例。

request

将单个bean定义限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的后面创建的。 仅在可支持web的Spring ApplicationContext上下文中有效。

session

将单个bean定义的作用域限定为HTTP会话的生命周期。 仅在可支持web的Spring ApplicationContext上下文中有效。

application

将单个bean定义的作用域限定为ServletContext的生命周期。 仅在可支持web的Spring ApplicationContext上下文中有效。

websocket

将单个bean定义限定为WebSocket的生命周期。仅在支持web的Spring应用程序上下文中有效。

注:

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

1.5.1. The Singleton Scope

       只管理一个单例bean的共享实例,所有对ID或ID与该bean定义匹配的bean的请求都会导致Spring容器返回一个特定的bean实例。

       换句话说,当您定义一个bean定义并且其作用域为单例时,Spring IoC容器将为该bean定义所定义的对象创建一个实例。 该单个实例存储在此类单例bean的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象。 下图显示了单例作用域的工作方式:

singleton

       Spring的singleton bean概念不同于Gang of Four(GoF)模式书中定义的singleton模式。GoF singleton硬编码对象的作用域,以便每个类加载器只创建一个特定类的实例。Spring singleton的作用域最好描述为每个容器和每个bean。这意味着,如果为单个Spring容器中的特定类定义一个bean,那么Spring容器将创建该bean定义的类的一个且仅创建一个实例。singleton作用域是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"/>

1.5.2. The Prototype Scope

       每次对特定bean提出请求时,非单例原型bean都会导致创建一个新bean实例。 也就是说,该Bean被注入到另一个Bean中,或者您可以通过容器上的getBean()方法调用来请求它。 通常,应将原型作用域用于所有有状态Bean,将单例作用域用于无状态Bean。

下图说明了Spring原型作用域:

prototype

(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不拥有任何对话状态。对于我们而言,重用单例图的核心更为容易。)

以下XML示例将bean定义为原型bean:

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

与其他作用域相比,Spring不能管理原型Bean的完整生命周期。 容器实例化,配置或组装原型对象,然后将其交给客户端,而没有该原型实例的进一步记录。 因此,尽管在不考虑作用域的情况下在所有对象上都调用了初始化生命周期回调方法,但对于原型而言,不会调用已配置的销毁生命周期回调。 客户端代码必须清除原型作用域内的对象并释放原型Bean拥有的昂贵资源。 为了使Spring容器释放原型作用下的bean所拥有的资源,请尝试使用自定义bean后置处理器,该处理器具有对需要清理的bean的引用。

       在某些方面,Spring容器在原型作用域Bean方面的角色是Java new运算符的替代。 超过该时间点的所有生命周期管理必须由客户端处理。 (有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)

1.5.3 具有原型bean依赖项的单例bean

       当您使用对原型bean有依赖性的单例作用域Bean时,请注意,依赖关系在实例化时已解决。 因此,如果将依赖项原型的bean依赖项注入到单例作用域的bean中,则将实例化新的原型bean,然后将依赖项注入到单例bean中。 该原型实例是曾经提供给单例作用域的bean的唯一实例。

      但是,假设您希望单例作用域的bean在运行时重复获取原型作用域的bean的新实例。 您不能将原型作用域的bean依赖项注入到您的单例bean中,因为当Spring容器实例化单例bean并解析并注入其依赖项时,该注入仅发生一次。 如果在运行时不止一次需要原型bean的新实例,请参见方法注入

1.5.4. Request, Session, Application, and WebSocket Scopes

       请求、会话、应用程序和websocket作用域只有在使用支持web的Spring ApplicationContext实现(例如XmlWebApplicationContext)时才可用。如果将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会引发一个IllegalStateException,该异常抱怨未知的bean作用域。

初始化web配置

       为了在请求、会话、应用程序和websocket级别(web作用域的bean)支持bean的作用域,在定义bean之前需要一些小的初始配置。(对于标准作用域:singleton和prototype,此初始设置不是必需的。)

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

       如果在Spring Web MVC中访问作用域bean,实际上,在Spring DispatcherServlet处理的请求中,不需要特殊设置。DispatcherServlet已公开所有相关状态。 

       如果您使用Servlet 2.5 Web容器,并且在Spring的DispatcherServlet之外处理请求(例如,使用JSF或Struts时),则需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。 对于Servlet 3.0+,可以使用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请求对象绑定到为该请求提供服务的Thread。 这使得在请求链和会话作用域内的bean可以在调用链的更下游使用。

Request scope

考虑将以下XML配置用于bean定义:

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

 Spring容器通过为每个HTTP请求使用loginAction bean定义来创建LoginAction bean的新实例。 也就是说,loginAction bean的作用域为HTTP请求级别。 您可以根据需要更改创建实例的内部状态,因为从同一loginAction bean定义创建的其他实例看不到状态的这些变化。 它们特定於单个请求。 当请求完成处理时,将丢弃作用于该请求的Bean。

       当使用注解驱动的组件或Java配置时,@RequestScope注释可用于将组件分配给Request作用域。 以下示例显示了如何执行此操作:

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

Session Scope

考虑将以下XML配置用于bean定义:

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

       Spring容器通过在单个HTTP会话的生存期内使用userPreferences bean定义来创建UserPreferences bean的新实例。 换句话说,userPreferences bean有效地作用在HTTP会话级别。 与请求作用域的Bean一样,您可以根据需要任意更改所创建实例的内部状态,因为知道其他也在使用从同一userPreferences Bean定义创建的实例的HTTP Session实例也看不到这些状态变化 ,因为它们特定於单个HTTP会话。 当HTTP会话最终被丢弃时,作用于该特定HTTP会话的bean也将被丢弃。

       使用注释驱动的组件或Java配置时,可以使用@SessionScope注释将组件分配给Session作用域。

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

Application Scope

考虑将以下XML配置用于bean定义:

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

       Spring容器通过对整个Web应用程序使用一次appPreferences bean定义来创建AppPreferences bean的新实例。 也就是说,appPreferences bean的作用域位于ServletContext级别,并存储为常规ServletContext属性。 这有点类似于Spring单例bean,但是在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring'ApplicationContext'的单例(在任何给定的Web应用程序中可能都有多个),并且实际上是公开的,因此 可见为ServletContext属性。

        使用注解驱动的组件或Java配置时,可以使用@ApplicationScope注解将组件分配给Application作用域。 以下示例显示了如何执行此操作:

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

作用域bean作为依赖项

       Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖项)的装配。 如果要将(例如)HTTP请求作用域的Bean注入(例如)另一个作用域更长的Bean,则可以选择注入AOP代理来代替已定义作用域的Bean。 也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)中检索实际目标对象,并将方法调用委托给实际对象。

注:

      您还可以在作用域为单例的bean之间使用<aop:scoped-proxy />,然后引用通过经过可序列化的中间代理,从而能够在反序列化时重新获得目标单例bean。

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

      此外,作用域代理并不是以生命周期安全的方式从较短作用域访问bean的唯一方法。您还可以将注入点(即构造函数或setter参数或autowired字段)声明为ObjectFactory<MyTargetBean>,从而允许getObject()调用在每次需要时按需检索当前实例,而无需保留实例或单独存储实例。

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

     JSR-330的这种变体称为Provider,并与Provider <MyTargetBean>声明和每次检索尝试的相应get()调用一起使用。 有关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⃣️该行定义了代理。

 要创建这样的代理,请将子<aop:scoped-proxy />元素插入到作用域bean定义中(请参阅选择要创建的代理类型基于XML Schema的配置)。 为什么在请求,会话和自定义作用域级别定义的bean定义需要<aop:scoped-proxy />元素? 考虑以下单例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>

 在前面的示例中,单例bean(userManager)注入了对HTTP会话作用域bean(userPreferences)的引用。 这里的重点是userManager bean是单例的:每个容器仅实例化一次,并且其依赖项(在本例下,仅一个,userPreferences bean)也仅注入一次。 这意味着userManager bean仅在完全相同的userPreferences对象(即最初与之注入对象)上操作。

      将寿命较短的作用域bean注入寿命较长的作用域bean时,这不是您想要的行为(例如,将HTTP会话作用域的协作bean作为依赖项注入到singleton bean中)。相反,您只需要一个userManager对象,并且在HTTP会话的生存期内,您需要一个特定于HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想情况下是一个UserPreferences实例的对象),该对象可以从作用域机制(HTTP请求,Session等)中获取实际的UserPreferences对象。 。容器将此代理对象注入到userManager bean中,而后者不知道此UserPreferences引用是代理。在此示例中,当UserManager实例在注入依赖项的UserPreferences对象上调用方法时,实际上是在代理上调用方法。然后,代理从HTTP会话(在本例中)获取真实的UserPreferences对象,并将方法调用委托给检索到的真实的UserPreferences对象。

       因此,在将请求作用域和会话作用域的bean注入到协作对象中时,需要以下(正确和完整)配置,如以下示例所示:

<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的类代理。
注意:CGLIB代理仅拦截public方法调用! 不要在此类代理上调用非public方法。 它们没有被委派给实际的作用域目标对象。
       另外,您可以通过为<aop:scoped-proxy />元素的proxy-target-class属性值指定false,来配置Spring容器为此类作用域的bean创建基于标准JDK接口的代理。 使用基于JDK接口的代理意味着您不需要应用程序类路径中的其他库即可影响此类代理。 但是,这也意味着作用域Bean的类必须实现至少一个接口,并且作用域Bean注入到其中的所有协作者必须通过其接口之一引用该Bean。 以下示例显示基于接口的代理:
<!-- 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>

有关选择基于类或基于接口的代理的更多详细信息,请参阅代理机制

1.5.5 自定义bean作用域

        Bean作用域机制是可扩展的。 您可以定义自己的作用域,甚至重新定义现有的作用域,尽管后者被认为是不好的做法,并且您不能覆盖内置的singleton和prototype作用域。

创建自定义作用域

       要将自定义作用域集成到Spring容器中,需要实现org.springframework.beans.factory.config.Scope接口,本节对此进行了介绍。 有关如何实现自己的作用域的想法,请参见Spring框架本身提供的Scope实现和Scope javadoc,其中详细说明了需要实现的方法。

Scope接口有四个方法可以从作用域中获取对象,从作用域中删除对象,然后销毁它们。

例如,Session 作用域实现返回会话作用域的Bean(如果不存在,则该方法将其绑定到会话上以供将来参考之后,将返回该Bean的新实例)。 以下方法从基础作用域返回对象:

Object get(String name, ObjectFactory<?> objectFactory)

例如,会话作用域实现从底层会话中移除会话作用域bean。应该返回对象,但如果找不到具有指定名称的对象,则可以返回null。以下方法将对象从基础作用域中移除:

Object remove(String name)

以下方法注册在作用域被销毁或作用域中的指定对象被销毁时应执行的回调:

void registerDestructionCallback(String name, Runnable destructionCallback)

有关销毁回调的更多信息,请参见javadoc或Spring作用域实现。

以下方法获取基础作用域的会话标识符:

String getConversationId()

每个作用域的标识符都不同。对于会话作用域的实现,此标识符可以是会话标识符。

使用自定义作用域:

       在编写和测试一个或多个自定义作用域实现之后,需要使Spring容器意识到您的新作用域。 以下方法是在Spring容器中注册新作用域的主要方法:

void registerScope(String scopeName, Scope scope);

       此方法在ConfigurableBeanFactory接口上声明,该接口可通过Spring附带的大多数具体ApplicationContext实现上的BeanFactory属性获得。

       registerScope(..)方法的第一个参数是与作用域关联的唯一名称。Spring容器本身中的这些名称的例子包括singleton和prototype。registerScope(..)方法的第二个参数是要注册和使用的自定义作用域实现的实际实例。

      假设您编写了自定义的Scope实现,然后注册它,如下面的示例所示:

(注意:下一个例子使用SimpleThreadScope,它包含在Spring中,但默认情况下没有注册。对于您自己的自定义作用域实现,这些说明将是相同的。)

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

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

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

        对于自定义作用域实现,您不限于作用域的编程注册。您还可以使用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>
注意:当将<aop:scoped-proxy />放置在FactoryBean实现中时,作用域是工厂bean本身,而不是从getObject()返回的对象。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章