<bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton"/>
这里的scope就是用来配置spring bean的作用域,它标识bean的作用域。在spring2.0之前bean只有2种作用域即:singleton(单例)、non-singleton(也称prototype), Spring2.0以后,增加了session、request、global session三种专用于Web应用程序上下文的Bean。因此,默认情况下Spring2.0现在有五种类型的Bean。当然,Spring2.0对Bean的类型的设计进行了重构,并设计出灵活的Bean类型支持,理论上可以有无数多种类型的Bean,用户可以根据自己的需要,增加新的Bean类型,满足实际应用需求。!
1、singleton作用域
当一个bean的作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把一个bean定义设置为singleton作用域时,Spring IOC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例,这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时候,spring的IOC容器中只会存在一个该bean。
配置实例:
<bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton"/>
或者
<bean id="role" class="spring.chapter2.maryGame.Role" singleton="true"/>
2、prototype
prototype作用域部署的bean,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被singleton作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用。)
3、request
request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,配置实例: request、session、global session使用的时候首先要在初始化web的web.xml中做如下配置:如果你使用的是Servlet 2.4及以上的web容器,那么你仅需要在web应用的XML声明文件web.xml中增加下述ContextListener即可:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
4、session
session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
5、global session
global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果你在web中使用global session作用域来标识bean,那么web会自动当成session类型来使用
6、自定义bean装配作用域在spring2.0中作用域是可以任意扩展的,你可以自定义作用域,甚至你也可以重新定义已有的作用域(但是你不能覆盖singleton和prototype),spring的作用域由接口org.springframework.beans.factory.con**.Scope来定义,自定义自己的作用域只要实现该接口即可
再议singleton与prototype:
scope="prototype"没写的问题,项目中对一个表的增删该操作是用一个action,这个 actionadd,update,delete,save这些方法, 添加和修改是共用一个页面,当页面得到id时代表进行的修改操作,反之是添加操作。因为在配置spring的bean是忘了写 scope="prototype" 所以每次添加时都显示最后一次访问过的记录,scope="prototype" 会在该类型的对象被请求 时创建一个新的action对象。如果没有配置scope=prototype则添加的时候不会新建一个action,他任然会保留上次访问的过记录的信息 webwork的Action不是线程安全的,要求在多线程环境下必须是一个线程对应一个独立的实例,不能使用 singleton。所以,我们在Spring配置Webwork Action Bean时,需要加上属性scope=”prototype”或singleton=”false”。
singleton模式指的是对某个对象的完全共享,包括代码空间和数据空间,说白了,如果一个类是singleton的,假如这个类有成员变量,那么这个成员变量的值是各个线程共享的(有点类似于static的样子了),当线程A往给变量赋了一个值以后,线程B就能读出这个值。因此,对于前台Action,肯定不能使用singleton的模式,必须是一个线程请求对应一个独立的实例。推而广之,只要是带数据成员变量的类,为了防止多个线程混用数据,就不能使用singleton。对于我们用到的Service、Dao,之所以用了singleton,就是因为他们没有用到数据成员变量,如果谁的 Service需要数据成员变量,请设置singleton=false。 有状态的bean都使用Prototype作用域,而对无状态的bean则应该使用singleton作用域。
在 Spring2.0中除了以前的Singleton和Prototype外又加入了三个新的web作用域,分别为request、session和 global session。如果你希望容器里的某个bean拥有其中某种新的web作用域,除了在bean级上配置相应的scope属性,还必须在容器级做一个额外的初始化配置。即在web应用的web.xml中增加这么一个ContextListener: org.springframework.web.context.request.RequestContextListener 以上是针对Servlet 2.4以后的版本。比如Request作用域。
如果在<bean>中指定Bean的作用范围为scope="prototype",将Bean返回给调用者,调用者负责Bean
后续生命的管理,spring不再管理这些Bean的生命周期.如果作用范围设置为scope="singleton",则将
Bean放入Spring IOC容器的缓存池中,并将Bean引用返回给调用者,spring继续对这些Bean进行后续
的生命管理. 默认属性是singleton
Bean以两种形态存在:singletons形式和prototypes形式。当bean以singletons形态存在时,BeanFactory只管理一个共享的实例。所有对这个特定bean的实例请求,都导致返回这个唯一bean实例的引用。
当bean以prototype形态存在时,每次对这个bean的实例请求都会导致一个新的实例的创建。当用户需要不受其他用户对象影响的对象或有类似的需求时,这是一个较理想的解决办法。
Bean默认是以singleton形态存在的,除非你另外显式加以指定。留神,当把bean的设置改为prototype模式时,每次对这个bean的实例请求都会导致一个新的bean实例被创建,而这可能并不是你所期望的。所以,只应该在确实需要的情况下把bean设置为prototype模式。
在创建一个bean定义(通常为XML配置文件)时,你可以简单的将其理解为:用以创建由该bean定义所决定的实际对象实例的一张“处方(recipe)”或者模板。就如class一样,根据一张“处方”你可以创建多个对象实例。
你不仅可以控制注入到对象(bean定义)中的各种依赖和配置值,还可以控制该对象的作用域。这样你可以灵活选择所建对象的作用域,而不必在Java Class级定义作用域。Spring Framework支持五种作用域(其中有三种只能用在基于web的Spring ApplicationContext
)。
内置支持的作用域分列如下:
表 3.4. Bean作用域
作用域 | 描述 |
---|---|
singleton |
在每个Spring IoC容器中一个bean定义对应一个对象实例。 |
prototype |
一个bean定义对应多个对象实例。 |
request |
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring |
session |
在一个HTTP |
global session |
在一个全局的HTTP |
当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
换言之,当把一个bean定义设置为singlton作用域时,Spring IoC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例。
下图演示了Spring的singleton作用域。
请注意Spring的singleton bean概念与“四人帮”(GoF)模式一书中定义的Singleton模式是完全不同的。经典的GoF Singleton模式中所谓的对象范围是指在每一个ClassLoader
中指定class创建的实例有且仅有一个。把Spring的singleton作用域描述成一个container
对应一个bean实例最为贴切。亦即,假如在单个Spring容器内定义了某个指定class的bean,那么Spring容器将会创建一个且仅有一个由该bean定义指定的类实例。
Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/> <!-- the following is equivalent, though redundant (and preserved for backward compatibility) --> <bean id="accountService" class="com.foo.DefaultAccountService" singleton="true"/>
Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()
方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
下图演示了Spring的prototype作用域。请注意,典型情况下,DAO不会被配置成prototype,因为一个典型的DAO不会持有任何会话状态,因此应该使用singleton作用域。
要在XML中将bean定义成prototype,可以这样配置:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
<!-- the following is equivalent too (and preserved for backward compatibility) -->
<bean id="accountService" class="com.foo.DefaultAccountService" singleton="false"/>
对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被singleton作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用。)
谈及prototype作用域的bean时,在某些方面你可以将Spring容器的角色看作是Java new
操作符的替代者。任何迟于该时间点的生命周期事宜都得交由客户端来处理。在第 3.5.1 节 “Lifecycle接口”一节中会进一步讲述Spring IoC容器中的bean生命周期。
如果你在bean定义文件中引用'spring-beans.dtd'
DTD,要显式说明bean的生命周期作用域你必须使用"singleton
"属性(记住singleton生命周期作用域是默认的)。 如果引用的是'spring-beans-2.0.dtd'
DTD或者是Spring 2.0 XSD schema,那么需要使用"scope
"属性(因为"singleton
"属性被删除了,新的DTD和XSD文件使用"scope
"属性)。
简单地说,如果你用"singleton
"属性那么就必须在那个文件里引用'spring-beans.dtd'
DTD。 如果你用"scope
"属性那么必须 在那个文件里引用'spring-beans-2.0.dtd'
DTD 或'spring-beans-2.0.xsd'
XSD。
其他作用域,即request
、session
以及global session
仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架)。
下面介绍的作用域仅仅在使用基于web的Spring ApplicationContext
实现(如XmlWebApplicationContext
)时有用。如果在普通的Spring IoC容器中,比如像XmlBeanFactory
或ClassPathXmlApplicationContext
,尝试使用这些作用域,你将会得到一个IllegalStateException
异常(未知的bean作用域)。
要使用request
、session
和 global session
作用域的bean(即具有web作用域的bean),在开始设置bean定义之前,还要做少量的初始配置。请注意,假如你只想要“常规的”作用域,也就是singleton和prototype,就不需要这一额外的设置。
在目前的情况下,根据你的特定servlet环境,有多种方法来完成这一初始设置。如果你使用的是Servlet 2.4及以上的web容器,那么你仅需要在web应用的XML声明文件web.xml
中增加下述ContextListener
即可
<web-app> ... <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> ... </web-app>
如果你用的是早期版本的web容器(Servlet 2.4以前),那么你要使用一个javax.servlet.Filter
的实现。请看下面的web.xml配置片段:
<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>
RequestContextListener
和RequestContextFilter
两个类做的都是同样的工作:将HTTP request对象绑定到为该请求提供服务的Thread
。这使得具有request和session作用域的bean能够在后面的调用链中被访问到。
考虑下面bean定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction
bean定义创建一个全新的LoginAction
bean实例,且该loginAction
bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction
bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session
,Spring容器会根据userPreferences
bean定义创建一个全新的userPreferences
bean实例,且该userPreferences
bean仅在当前HTTP Session
内有效。与request作用域
一样,你可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session
中根据userPreferences
创建的实例,将不会看到这些特定于某个HTTP Session
的状态变化。当HTTP Session
最终被废弃的时候,在该HTTP Session
作用域内的bean也会被废弃掉。
考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
global session
作用域类似于标准的HTTP Session
作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session
的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session
作用域中定义的bean被限定于全局portlet Session
的生命周期范围内。
请注意,假如你在编写一个标准的基于Servlet的web应用,并且定义了一个或多个具有global session
作用域的bean,系统会使用标准的HTTP Session
作用域,并且不会引起任何错误。
能够在HTTP request或者Session
(甚至自定义)作用域中定义bean固然很好,但是Spring IoC容器除了管理对象(bean)的实例化,同时还负责协作者(或者叫依赖)的实例化。如果你打算将一个Http request范围的bean注入到另一个bean中,那么需要注入一个AOP代理来替代被注入的作用域bean。也就是说,你需要注入一个代理对象,该对象具有与被代理对象一样的公共接口,而容器则可以足够智能的从相关作用域中(比如一个HTTP request)获取到真实的目标对象,并把方法调用委派给实际的对象。
<aop:scoped-proxy/>
不能和作用域为singleton
或prototype
的bean一起使用。为singleton bean创建一个scoped proxy将抛出BeanCreationException
异常。
让我们看一下将相关作用域bean作为依赖的配置,配置并不复杂(只有一行),但是理解“为何这么做”以及“如何做”是很重要的。
<?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-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- a HTTPSession
-scoped bean exposed as a proxy --> <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <!-- this next element effects the proxying of 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>
在XML配置文件中,要创建一个作用域bean的代理,只需要在作用域bean定义里插入一个<aop:scoped-proxy/>
子元素即可(你可能还需要在classpath里包含CGLIB库,这样容器就能够实现基于class的代理;还可能要使用基于XSD的配置)。上述XML配置展示了“如何做”,现在讨论“为何这么做”。在作用域为request
、session
以及globalSession
的bean定义里,为什么需要这个<aop:scoped-proxy/>
元素呢?下面我们从去掉<aop:scoped-proxy/>
元素的XML配置开始说起:
<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 Session
作用域bean userPreferences
的引用。singleton userManager
bean会被容器仅实例化一次,并且其依赖(userPreferences
bean)也仅被注入一次。这意味着,userManager
在理论上只会操作同一个userPreferences
对象,即原先被注入的那个bean。而注入一个HTTP Session
作用域的bean作为依赖,有违我们的初衷。因为我们想要的只是一个userManager
对象,在它进入一个HTTP Session
生命周期时,我们希望去使用一个HTTPSession
的userPreferences
对象。
当注入某种类型对象时,该对象实现了和UserPreferences
类一样的公共接口(即UserPreferences
实例)。并且不论我们底层选择了何种作用域机制(HTTP request、Session
等等),容器都会足够智能的获取到真正的
UserPreferences
对象,因此我们需要将该对象的代理注入到userManager
bean中, 而userManager
bean并不会意识到它所持有的是一个指向UserPreferences
引用的代理。在本例中,当UserManager
实例调用了一个使用UserPreferences
对象的方法时,实际调用的是代理对象的方法。随后代理对象会从HTTP Session
获取真正的UserPreferences
对象,并将方法调用委派给获取到的实际的UserPreferences
对象。
这就是为什么当你将request
、session
以及globalSession
作用域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 2.0中,Spring的bean作用域机制是可以扩展的。这意味着,你不仅可以使用Spring提供的预定义bean作用域; 还可以定义自己的作用域,甚至重新定义现有的作用域(不提倡这么做,而且你不能覆盖内置的singleton
和prototype
作用域)。
作用域由接口org.springframework.beans.factory.config.Scope
定义。要将你自己的自定义作用域集成到Spring容器中,需要实现该接口。它本身非常简单,只有两个方法,分别用于底层存储机制获取和删除对象。自定义作用域可能超出了本参考手册的讨论范围,但你可以参考一下Spring提供的Scope
实现,以便于去如何着手编写自己的Scope实现。
在实现一个或多个自定义Scope
并测试通过之后,接下来就是如何让Spring容器识别你的新作用域。ConfigurableBeanFactory
接口声明了给Spring容器注册新Scope
的主要方法。(大部分随Spring一起发布的BeanFactory
具体实现类都实现了该接口);该接口的主要方法如下所示:
void registerScope(String scopeName, Scope scope);
registerScope(..)
方法的第一个参数是与作用域相关的全局唯一名称;Spring容器中该名称的范例有singleton
和prototype
。registerScope(..)
方法的第二个参数是你打算注册和使用的自定义Scope
实现的一个实例。
假设你已经写好了自己的自定义Scope
实现,并且已经将其进行了注册:
// note: the ThreadScope
class does not exist; I made it up for the sake of this example
Scope customScope = new ThreadScope();
beanFactory.registerScope("thread", scope);
然后你就可以像下面这样创建与自定义Scope
的作用域规则相吻合的bean定义了:
<bean id="..." class="..." scope="thread"/>
如果你有自己的自定义Scope
实现,你不仅可以采用编程的方式注册自定义作用域,还可以使用BeanFactoryPostProcessor
实现:CustomScopeConfigurer
类,以声明的方式注册Scope
。BeanFactoryPostProcessor
接口是扩展Spring IoC容器的基本方法之一,在本章的BeanFactoryPostProcessor中将会介绍。
使用CustomScopeConfigurer
,以声明方式注册自定义Scope
的方法如下所示:
<?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-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="thread" value="com.foo.ThreadScope"/> </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>