SpringFramework核心技术一(IOC:Bean的范围)

简介: Bean的范围当你创建一个bean定义时,你创建一个配方来创建由该bean定义定义的类的实际实例。bean定义是一个配方的想法很重要,因为它意味着,就像一个类一样,您可以从一个配方创建许多对象实例。

Bean的范围

当你创建一个bean定义时,你创建一个配方来创建由该bean定义定义的类的实际实例。bean定义是一个配方的想法很重要,因为它意味着,就像一个类一样,您可以从一个配方创建许多对象实例。
您不仅可以控制要插入到从特定的bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定的bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别上烘焙对象的范围。Bean可以被定义为部署在多个作用域中的一个:

Spring框架支持六个作用域,其中四个作用域仅在使用Web感知时才可用ApplicationContext。

一、Bean的范围域

Scope(作用域) Description(描述)
singleton(单例) (默认)每个Spring IoC容器将单个bean定义作用于单个对象实例。
prototype(原型) Scopes a single bean definition to any number of object instances.将任何数量的对象实例的单个bean定义作用域。
request(请求) 将单个bean定义作用于单个HTTP请求的生命周期; 也就是说,每个HTTP请求都有自己的实例,这个实例是在单个bean定义的背后创建的。只有在Web认知的Spring环境中才有效ApplicationContext。
session(会话作用域) 将一个单一的bean定义作用于HTTP的生命周期Session。只有在Web认知的Spring环境中才有效ApplicationContext。
application(应用作用域) 将范围定义为a的生命周期的单个bean定义ServletContext。只有在Web认知的Spring环境中才有效ApplicationContext。
websocket(websocket作用域) 将范围定义为a的生命周期的单个bean定义WebSocket。只有在Web认知的Spring环境中才有效ApplicationContext。

二、singleton单例作用域

  • 只管理单个bean的一个共享实例,并且具有与该bean定义匹配的id或id的bean的所有请求都会导致Spring容器返回一个特定的bean实例。
    换句话说,当你定义一个bean定义并将其作为一个singleton作用域时,Spring IoC容器恰好创建了该bean定义的对象的一个实例。这个单实例存储在这些单例bean的缓存中,并且该命名bean的所有后续请求和引用都会返回缓存的对象。
    这里写图片描述
  • Spring的单例bean概念与四人帮(GoF)模式书中定义的Singleton模式不同。GoF Singleton对对象的范围进行硬编码,以便每个ClassLoader创建一个特定类的唯一实例。Spring单例的范围最好按容器和每个bean来描述。这意味着如果您为单个Spring容器中的特定类定义一个bean,那么Spring容器将创建该bean定义所定义的类的一个且仅有的一个实例。单例作用域是Spring中的默认作用域。要将一个bean定义为XML中的单例,您可以编写,例如:
<!-- bean默认就是单例的-->
<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) 这个和上面一个是一样的结果,甚至是多余了,singleton域是默认的-->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

三、The prototype scope(原型作用域)

  • 生命周期不由Spring容器来管理
  • bean的部署的非单实例原型范围导致每次创建一个新的bean实例时,都会创建一个对该特定bean的请求。也就是说,该bean被注入到另一个bean中,或者通过getBean()容器上的方法调用来请求它。通常,为所有有状态bean和无状态bean的单例作用域使用原型作用域。
  • 下图说明了Spring原型范围。数据访问对象(DAO)通常不配置为原型,因为典型的DAO不具有任何对话状态; 这位作者更容易重用单体图的核心。
    这里写图片描述
  • 以下示例在XML中将bean定义中的原型作用域:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
  • 与其他范围相比,Spring不管理原型bean的完整生命周期:容器实例化,配置和以其他方式组装原型对象,并将其交给客户端,而不再记录该原型实例。因此,尽管在所有对象上调用初始化生命周期回调方法而不管范围,但在原型的情况下,不调用配置的销毁 生命周期回调。客户端代码必须清理原型范围的对象并释放原型bean持有的昂贵资源。为了让Spring容器释放原型范围bean所拥有的资源,请尝试使用自定义bean后期处理器,它拥有对需要清理的bean的引用。
    在某些方面,Spring容器在原型范围bean方面的作用是Java new操作符的替代。所有生命周期管理过去都必须由客户来处理。(有关Spring容器中bean的生命周期的详细信息,请参阅生命周期回调。)

四、Singleton beans with prototype-bean dependencies(具有原型bean依赖关系的单例bean)

  • 当您使用具有原型bean依赖关系的单一范围bean时,请注意, 在实例化时会解析依赖关系。因此,如果您将原型范围的bean依赖注入到单例范围的bean中,则将实例化新的原型bean,然后将依赖注入到单例bean中。原型实例是唯一提供给单例范围bean的唯一实例。
  • 但是,假设您希望单例范围的bean在运行时重复获取原型范围的bean的新实例。你不能依赖注入一个原型范围的bean到你的单例bean中,因为这个注入只发生 一次,当Spring容器实例化单例bean并解析和注入它的依赖关系时。如果您不止一次在运行时需要一个原型bean的新实例,请参阅方法注入。

五、 Request, session, application, and WebSocket scopes(请求、会话、应用程序和WebSocket范围)

  • request,session,application,和websocket范围域仅在你如果使用**基于web的Spring容器**ApplicationContext实现(如 XmlWebApplicationContext)。
  • 如果你将这些范围用于常规的Spring IoC容器(非基于web的容器)(比如ClassPathXmlApplicationContext),那么IllegalStateException会抛出一个抱怨未知bean范围的问题。
    具体表现在以下五个方面:

1.初始网页配置

为了支持Bean的范围界定在request,session,application,和 websocket(即具有web作用域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’s RequestContextFilter(过滤器)。过滤器映射取决于周围的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可以在调用链的更下方进行访问。

2. Request scope(请求范围)

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

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

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

使用注释驱动组件或Java Config时,@RequestScope可以使用注释将组件分配给request作用域。

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

3.Session scope(会话范围)

考虑用以下的XML配置来定义一个会话范围:

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

Spring容器UserPreferences通过在userPreferences单个HTTP的生命周期中使用bean定义来创建bean 的新实例Session。换句话说,这个userPreferencesbean的有效范围在HTTP Session级别。和request-scopedbean一样,您可以根据需要更改创建的实例的内部状态,因为知道其他Session使用同一个userPreferencesbean定义创建的实例的HTTP 实例也不会看到这些状态变化,因为它们是特定的到一个单独的HTTP Session。当HTTP Session最终被丢弃时,作用于该特定HTTP的bean Session也被丢弃。

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

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

4.Application scope(应用范围)

考虑以下用以下的XML文件去给Bean定义一个应用程序的范围。

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

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

使用注释驱动组件或Java Config时,@ApplicationScope 可以使用注释将组件分配给application作用域。

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

5. 作为依赖关系的作用域bean

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

您也可以<aop:scoped-proxy/>在作为范围的bean之间使用singleton,然后通过可序列化的中间代理,因此可以在反序列化中重新获得目标单身bean。
<aop:scoped-proxy/>针对范围的bean进行声明prototype时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后将该呼叫转发给该实例。
此外,范围代理不是以生命周期安全的方式从较短范围访问Bean的唯一方法。您也可以简单地声明您的注入点(即构造函数/设置器参数或自动装配的字段)ObjectFactory<MyTargetBean>,从而允许getObject()每次需要时根据需要调用一次调用来检索当前实例 - 而无需保留实例或单独存储实例。
作为一个扩展的变体,你可以声明ObjectProvider<MyTargetBean>哪个提供了几个额外的访问变体,包括getIfAvailable和getIfUnique。
调用JSR-330变体Provider,与每次检索尝试的Provider<MyTargetBean> 声明和相应get()调用一起使用。

以下示例中的配置只有一行,但了解“原因”以及它“如何”很重要。

<?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.foo.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/>
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.foo.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

要创建这样一个代理,可以将一个子<aop:scoped-proxy/>元素插入到一个有作用域的bean定义中(请参阅选择要创建的代理类型和 基于XML模式的配置)。豆类的定义为何作用域的request,session和自定义范围水平要求<aop:scoped-proxy/>元素?让我们来看看下面的单例bean定义,并将其与您需要为上述范围定义的内容进行对比(请注意,下面的 userPreferencesbean定义是不完整的)。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在前面的例子中,singleton bean userManager注入了对HTTP- Sessionscoped bean 的引用userPreferences。这里的要点是 userManagerbean是一个单独的实例:每个容器只会实例化一次,并且它的依赖关系(在本例中只有一个userPreferencesbean)也只能被注入一次。这意味着这个userManagerbean只会在完全相同的userPreferences对象上运行,也就是它最初注入的那个对象。

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

因此,你注射时需要以下,准确,完整,配置 request-和session-scoped bean到协作对象:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

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

选择要创建的代理类型
默认情况下,当Spring容器为使用<aop:scoped-proxy/>元素标记的bean创建代理时,将创建一个基于CGLIB的类代理。

CGLIB代理只拦截公共方法调用!不要在这样的代理上调用非公共方法; 它们不会被委派给实际的作用域目标对象。

或者,可以通过指定元素false的proxy-target-class属性值来配置Spring容器,以便为此类范围的bean创建标准的基于JDK接口的代理<aop:scoped-proxy/>。使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来实现这种代理。但是,这也意味着作用域bean的类必须至少实现一个接口,并且注入了作用域bean的所有协作者都必须通过它的一个接口引用bean。

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

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

六、自定义范围

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

1 创建一个自定义范围

要将自定义作用域集成到Spring容器中,需要实现org.springframework.beans.factory.config.Scope本节所述的 接口。有关如何实现自己的作用域的想法,请参阅Scope 随Spring Framework本身和Scopejavadoc提供的实现 ,它解释了您需要更详细地实现的方法。
该Scope接口有四种方法来从作用域中获取对象,将它们从作用域中移除并允许它们被销毁。

以下方法从基础范围返回对象。例如,会话范围实现返回会话范围的bean(如果它不存在,该方法在绑定到会话以供将来参考之后返回该bean的新实例)。

Object get(String name, ObjectFactory objectFactory)

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

Object remove(String name)

以下方法注册范围在销毁时或范围内的指定对象被销毁时应执行的回调。有关销毁回调的更多信息,请参阅javadocs或Spring范围实现。

void registerDestructionCallback(String name, Runnable destructionCallback)

以下方法获取基础范围的对话标识符。这个标识符对于每个范围是不同的。对于会话范围的实现,这个标识符可以是会话标识符。

String getConversationId()

2 使用自定义范围

在你编写和测试一个或多个自定义Scope实现之后,你需要让Spring容器知道你的新的作用域。以下方法是Scope使用Spring容器注册新的核心方法:

void registerScope(String scopeName, Scope scope);

这个方法是在ConfigurableBeanFactory接口中声明的,该接口在ApplicationContextSpring通过BeanFactory属性提供的大多数具体实现中都可用。
该registerScope(..)方法的第一个参数是与范围关联的唯一名称; Spring容器本身的这些名字的例子是singleton和 prototype。该registerScope(..)方法的第二个参数是Scope您希望注册和使用的自定义实现的实际实例。

假设你编写你的自定义Scope实现,然后如下注册它。

下面的例子使用了SimpleThreadScope,这个已经被Spring包括了,但是默认是没有注册的。这些说明对您自己的自定义Scope 实现而言是相同的。

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

然后创建符合自定义的范围规则的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
        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="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="foo" class="x.y.Foo">
        <property name="bar" ref="bar"/>
    </bean>

</beans>

放置`<aop:scoped-proxy/>在FactoryBean实现中时,它是工厂bean本身的作用域,而不是从中返回的对象getObject()。
好啦,这一节的细节到这里就结束啦。

目录
相关文章
|
5月前
|
存储 Java C++
理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory
理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory
|
XML 存储 设计模式
Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
本文对BeanDefinition进行全面深入的探讨,涵盖BeanDefinition的接口方法、主要信息、类型以及生成过程等方面内容。旨在帮助读者全面理解BeanDefinition的各方面知识,并能够熟练应用。文章通俗易懂,具有很强的指导意义。
192 0
Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
|
6月前
|
XML Java 数据格式
探秘Spring Bean的秘境:作用域篇【beans 三】
探秘Spring Bean的秘境:作用域篇【beans 三】
61 0
|
XML Java 测试技术
Spring高手之路8——Spring Bean模块装配的艺术:@Import详解
本文将带你深入探索Spring框架的装配机制,以及它如何使你的代码更具模块化和灵活性。我们首先介绍Spring手动装配的基础知识,然后进一步解析@Import注解在模块装配中的关键角色。文章涵盖从导入普通类、配置类,到使用ImportSelector和ImportBeanDefinitionRegistrar进行动态和选择性装配等多个层次,旨在帮助读者全面理解和掌握Spring的装配技术。
288 0
Spring高手之路8——Spring Bean模块装配的艺术:@Import详解
|
XML Java 数据格式
Spring(Ioc基于xml操作Bean管理)
Spring(Ioc基于xml操作Bean管理)
89 0
|
Java Spring 容器
Spring Bean基础(中)
Spring Bean基础(中)
105 0
Spring Bean基础(中)
|
存储 Java 容器
Spring Bean基础(上)
Spring Bean基础(上)
99 0
Spring Bean基础(上)
|
XML Java 数据格式
Spring之路(13)--在JavaConfig配置中装配bean
本文目录 1. 概述 2. 按类型自动装配实例 2.1 干干净净的歌手类、舞者类、舞台类 2.2 配置bean并自动装配 2.3 测试 3. 按名称自动装配 4. 总结
100 0
|
XML Java 数据格式
Spring之路(12)--在注解配置中装配bean
本文目录 1. 背景 2. 按名称自动装配 2.1 定义歌手类,并通过@component注解为其生成对应bean。 2.2 定义舞台类,并指定名称装配其属性 2.3 配置xml文件,开启对bean的扫描 2.4 测试 3. 按类型自动装配 3.1 定义歌手类和舞者类 3.2 通过类型自动装配舞台bean 3.3 测试运行 4. 总结
126 0
|
Java Spring 数据格式
SpringFramework核心技术一(IOC:注册一个LoadTimeWeaver)
一、什么是LoadTimeWeaver 在LoadTimeWeaver用于由Spring动态变换的类,因为它们被装载到Java虚拟机(JVM)。
2140 0