现在的位置: 首页 > 综合 > 正文

Spring 中的 Bean Scope

2013年08月26日 ⁄ 综合 ⁄ 共 21866字 ⁄ 字号 评论关闭

3.4. Bean scopes

When you create a bean definition what you are actually creating is a
recipe for creating actual instances of the class
defined by that bean definition. The idea that a bean definition is a recipe
is important, because it means that, just like a class, you can potentially
have many object instances created from a single recipe.

You can control not only the various dependencies and
configuration values that are to be plugged into an object that is created
from a particular bean definition, but also the scope
of the objects created from a particular bean definition. This approach is very
powerful and gives you the flexibility to choose the scope
of the objects you create through configuration instead of having to 'bake in'
the scope of an object at the Java class level. Beans can be defined to be
deployed in one of a number of scopes: out of the box, the Spring Framework
supports exactly five scopes (of which three are available only if you are using
a web-aware ApplicationContext).

The scopes supported out of the box are listed below:

Table 3.4. Bean scopes

Scope Description

singleton

Scopes a single bean definition to a single object instance per Spring IoC container.

prototype

Scopes a single bean definition to any number of object instances.

request

Scopes a single bean definition to the lifecycle of
a single HTTP request; that is each and every HTTP request will
have its own instance of a bean created off the back of a
single bean definition. Only valid in the context of a
web-aware Spring ApplicationContext.

session

Scopes a single bean definition to the lifecycle of
a HTTP Session. Only valid in
the context of a web-aware Spring
ApplicationContext.

global session

Scopes a single bean definition to the lifecycle of a
global HTTP Session. Typically
only valid when used in a portlet context. Only valid in the
context of a web-aware Spring
ApplicationContext.

3.4.1. The singleton scope

When a bean is a singleton, only one shared
instance of the bean will be managed, and all requests for beans with
an id or ids matching that bean definition will result in that one
specific bean instance being returned by the Spring container.

To put it another way, when you define a bean definition and it
is scoped as a singleton, then the Spring IoC container will create
exactly one instance of the object defined by
that bean definition. This single instance will be
stored in a cache of such singleton beans, and all subsequent
requests and references
for that named bean will result in
the cached object being returned.

Please be aware that Spring's concept of a singleton bean is quite
different from the Singleton pattern as defined in the seminal Gang of
Four (GoF) patterns book. The GoF Singleton hardcodes the scope of
an object such that one and only one instance of a
particular class will ever be created per
ClassLoader
. The scope of the Spring singleton
is best described as per container and per bean.
This means that if you define one bean for a particular class in a single
Spring container, then the Spring container will create one
and only one instance of the class defined by that
bean definition. The singleton scope is the default scope in Spring.
To define a bean as a singleton in XML, you would write configuration like so:

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default); using spring-beans-2.0.dtd -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

<!-- the following is equivalent and preserved for backward compatibility in spring-beans.dtd -->
<bean id="accountService" class="com.foo.DefaultAccountService" singleton="true"/>

3.4.2. The prototype scope

The non-singleton, prototype scope of bean deployment results in
the creation of a new bean instance every time a
request for that specific bean is made (that is, it is injected into another
bean or it is requested via a programmatic getBean()
method call on the container). As a rule of thumb, you should
use the prototype scope for all beans that are stateful, while the
singleton scope should be used for stateless beans.

The following diagram illustrates the Spring prototype scope.
Please note that a DAO would not typically be configured as a
prototype, since a typical DAO would not hold any conversational state;
it was just easier for this author to reuse the core of the singleton
diagram.

To define a bean as a prototype in XML, you would write configuration like so:

<!-- using spring-beans-2.0.dtd -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

<!-- the following is equivalent and preserved for backward compatibility in spring-beans.dtd -->
<bean id="accountService" class="com.foo.DefaultAccountService" singleton="false"/>

There is one quite important thing to be aware of when deploying a bean
in the prototype scope, in that the lifecycle of the bean changes slightly.
Spring does not manage the complete lifecycle of a prototype bean: the
container instantiates, configures, decorates and otherwise assembles a
prototype object, hands it to the client and then has no further knowledge
of that prototype instance. This means that while initialization
lifecycle callback methods will be called on all objects regardless of scope,
in the case of prototypes, any configured destruction
lifecycle callbacks will not be called. It is the
responsibility of the client code to clean up
prototype scoped objects and release any expensive resources that the prototype
bean(s) are holding onto. (One possible way to get the Spring container
to release resources used by prototype-scoped beans is through the use
of a custom bean post-processor
which would hold a reference to the beans that need to be cleaned up.)

In some respects, you can think of the Spring containers role when talking
about a prototype-scoped bean as somewhat of a replacement for the Java
'new' operator. All lifecycle aspects past that point
have to be handled by the client. (The lifecycle of a bean in the Spring
container is further described in the section entitled
Section 3.5.1, “Lifecycle interfaces”.)

3.4.3. Singleton beans with prototype-bean dependencies

When using singleton-scoped beans that have dependencies on
beans that are scoped as prototypes, please be aware that
dependencies are resolved at instantiation time.
This means that if you dependency inject a prototype-scoped
bean into a singleton-scoped bean, a brand new prototype bean
will be instantiated and then dependency injected into the singleton
bean... but that is all. That exact same
prototype instance will be the sole instance that is ever supplied
to the singleton-scoped bean, which is fine if that is what you want.

However, sometimes what you actually want is for the
singleton-scoped bean to be able to acquire a brand new instance of
the prototype-scoped bean again and again and again at runtime.
In that case it is no use just dependency injecting a prototype-scoped
bean into your singleton bean, because as explained above, that
only happens once when the Spring container
is instantiating the singleton bean and resolving and injecting
its dependencies. If you are in the scenario where you need to get
a brand new instance of a (prototype) bean again and again and again
at runtime, you are referred to the section entitled
Section 3.3.8, “Method Injection”

[Note] Backwards compatibility note: specifying the lifecycle scope in XML

If you are referencing the 'spring-beans.dtd' DTD in
a bean definition file(s), and you are being explicit about the lifecycle scope
of your beans you must use the "singleton" attribute to
express the lifecycle scope (remembering that the
singleton lifecycle scope is
the default). If you are referencing the 'spring-beans-2.0.dtd'
DTD or the Spring 2.0 XSD schema, then you will need to use the "scope"
attribute (because the "singleton" attribute was removed from the
definition of the new DTD and XSD files in favour of the "scope"
attribute).

To be totally clear about this, this means that if you use the
"singleton" attribute in an XML bean definition then you
must be referencing the 'spring-beans.dtd'
DTD in that file. If you are using the "scope"
attribute then you must be referencing either the
'spring-beans-2.0.dtd' DTD or the
'spring-beans-2.0.xsd' XSD in that file.

3.4.4. The other scopes

The other scopes, namely request, session,
and global session are for use only in web-based applications
(and can be used irrespective of which particular web application framework you are
using, if indeed any). In the interest of keeping related concepts together in
one place in the reference documentation, these scopes are described here.

[Note] Note

The scopes that are described in the following paragraphs are
only available if you are using a web-aware
Spring ApplicationContext implementation
(such as XmlWebApplicationContext).
If you try using these next scopes with regular Spring IoC containers such as
the XmlBeanFactory or
ClassPathXmlApplicationContext, you will
get an IllegalStateException complaining about
an unknown bean scope.

3.4.4.1. Initial web configuration

In order to support the scoping of beans at the request,
session, and global session levels
(web-scoped beans), some minor initial configuration is required
before you can set about defining your bean definitions. Please note that
this extra setup is not required if you just want to
use the 'standard' scopes (namely singleton and prototype).

Now as things stand, there are a couple of ways to effect this
initial setup depending on your particular Servlet environment...

If you are accessing scoped beans within Spring Web MVC, i.e.
within a request that is processed by the Spring DispatcherServlet,
or DispatcherPortlet, then no special setup is necessary:
DispatcherServlet and DispatcherPortlet
already expose all relevant state.

When using a Servlet 2.4+ web container, with requests processed
outside of Spring's DispatcherServlet (e.g. when using JSF or Struts),
you need to add the following javax.servlet.ServletRequestListener
to the declarations in your web application's 'web.xml' file.

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

If you are using an older web container (Servlet 2.3), you will need to use
the provided javax.servlet.Filter implementation.
Find below a snippet of XML configuration that has to be included
in the 'web.xml' file of your web application if you
want to have access to web-scoped beans in requests outside of Spring's
DispatcherServlet on a Servlet 2.3 container. (The filter mapping depends on
the surrounding web application configuration and so you will have to change
it as appropriate.)

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

That's it. DispatcherServlet,
RequestContextListener and
RequestContextFilter all do exactly the
same thing, namely bind the HTTP request object to the
Thread that is servicing that request. This makes
beans that are request- and session-scoped available further down the
call chain.

3.4.4.2. The request scope

Consider the following bean definition:

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

With the above bean definition in place, the Spring container will create
a brand new instance of the LoginAction bean
using the 'loginAction' bean definition for each and
every HTTP request. That is, the 'loginAction' bean will be
effectively scoped at the HTTP request level. You can change or dirty
the internal state of the instance that is created as much as you want,
safe in the knowledge that other requests that are also using instances created
off the back of the same 'loginAction' bean definition
will not be seeing these changes in state since they are particular to an individual
request. When the request is finished processing, the bean that is scoped
to the request will be discarded.

3.4.4.3. The session scope

Consider the following bean definition:

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

With the above bean definition in place, the Spring container will create
a brand new instance of the UserPreferences bean
using the 'userPreferences' bean definition for the
lifetime of a single HTTP Session.
In other words, the 'userPreferences' bean will be
effectively scoped at the HTTP Session level.
Just like request-scoped beans, you can change the internal
state of the instance that is created as much as you want, safe in the
knowledge that other HTTP Session instances
that are also using instances created off the back of the same
'userPreferences' bean definition
will not be seeing these changes in state since they are particular to an individual
HTTP Session. When the HTTP
Session is eventually discarded, the bean
that is scoped to that particular HTTP Session
will also be discarded.

3.4.4.4. The global session scope

Consider the following bean definition:

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

The global session scope is similar to the
standard HTTP Session scope
(described immediately above),
and really only makes sense in the context of portlet-based web applications.
The portlet specification defines the notion of a global
Session that is shared amongst all
of the various portlets that make up a single portlet web application.
Beans defined at the global session scope are scoped
(or bound) to the lifetime of the global portlet
Session.

Please note that if you are writing a standard Servlet-based
web application and you define one or more beans as having
global session scope, the standard HTTP
Session scope will be used, and no
error will be raised.

3.4.4.5. Scoped beans as dependencies

Being able to define a bean scoped to a HTTP request or
Session (or indeed
a custom scope of
your own devising) is all very well, but one of the
main value-adds of the Spring IoC container is that it manages
not only the instantiation of your objects (beans), but also
the wiring up of collaborators (or dependencies). If you want to inject
a (for example) HTTP request scoped bean into another bean, you will need
to inject an AOP proxy in place of the scoped bean. That is, you need to
inject a proxy object that exposes the same public interface as the scoped
object, but that is smart enough to be able to retrieve the real, target
object from the relevant scope (for example a HTTP request) and delegate
method calls onto the real object.

[Note] Note

You do not need to use the
<aop:scoped-proxy/> in conjunction with
beans that are scoped as singletons or
prototypes. It is an error to try to create a
scoped proxy for a singleton bean (and the resulting
BeanCreationException will certainly set
you straight in this regard).

Let's look at the configuration that is required to effect this;
the configuration is not hugely complex (it takes just one line), but it
is important to understand the “why” as well as the
how” behind it.

<?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 HTTP Session-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>

To create such a proxy, you need only to insert a child
<aop:scoped-proxy/> element into a scoped bean definition
(you may also need the CGLIB library on your classpath so that the container can
effect class-based proxying; you will also need to be using Appendix A, XML Schema-based configuration).
So, just why do you need this <aop:scoped-proxy/> element in the
definition of beans scoped at the request,
session, globalSession and
'insert your custom scope here' level? The reason is best explained
by picking apart the following bean definition (please note that the following
'userPreferences' bean definition as it stands is
incomplete):

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

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

From the above configuration it is evident that the singleton bean
'userManager' is being injected with a reference to the HTTP
Session-scoped bean 'userPreferences'.
The salient point here is that the 'userManager' bean is a
singleton... it will be instantiated exactly once per container,
and its dependencies (in this case only one, the 'userPreferences'
bean) will also only be injected (once!). This means that the
'userManager' will (conceptually) only ever operate on the exact
same 'userPreferences' object, that is the one that it was originally
injected with. This is not what you want when you inject a HTTP
Session-scoped bean as a dependency into a
collaborating object (typically). Rather, what we do want is a
single 'userManager' object, and then, for the lifetime of a HTTP
Session, we want to see and use a
'userPreferences' object that is specific to said HTTP
Session.

Rather what you need then is to inject some sort of object that exposes the
exact same public interface as the UserPreferences class (ideally
an object that is a UserPreferences instance)
and that is smart enough to be able to go off and fetch the
real UserPreferences object from
whatever underlying scoping mechanism we have chosen (HTTP request,
Session, etc.). We can then safely inject this proxy
object into the 'userManager' bean, which will be blissfully unaware
that the UserPreferences reference that it is holding onto is a
proxy. In the case of this example, when a UserManager
instance invokes a method on the dependency-injected UserPreferences
object, it is really invoking a method on the proxy... the proxy will then go off and
fetch the real UserPreferences object from (in this case) the HTTP
Session, and delegate the method invocation onto the
retrieved real UserPreferences object.

That is why you need the following, correct and complete, configuration
when injecting request-, session-, and
globalSession-scoped beans into collaborating objects:

<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>
3.4.4.5.1. Choosing the type of proxy created

By default, when the Spring container is creating a proxy for a bean
that is marked up with the <aop:scoped-proxy/>
element, a CGLIB-based class proxy will be created.
This means that you need to have the CGLIB library on the classpath
of your application.

Note: CGLIB proxies will only intercept public method
calls!
Do not call non-public methods on such a proxy;
they will not be delegated to the scoped target object.

You can choose to have the Spring container create 'standard'
JDK interface-based proxies for such scoped beans by specifying
'false' for the value of the 'proxy-target-class'
attribute of the <aop:scoped-proxy/> element.
Using JDK interface-based proxies does mean that you don't need any additional
libraries on your application's classpath to effect such proxying, but it
does mean that the class of the scoped bean must implement at least one
interface, and all of the collaborators into which
the scoped bean is injected must be referencing the bean via one of its
interfaces.

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

The section entitled Section 6.6, “Proxying mechanisms”
may also be of some interest with regard to understanding the nuances
of choosing whether class-based or interface-based proxying is right
for you.

3.4.5. Custom scopes

As of Spring 2.0, the bean scoping mechanism in Spring is extensible. This means
that you are not limited to just the bean scopes that Spring provides out of the box;
you can define your own scopes, or even redefine the existing scopes (although that last
one would probably be considered bad practice - please note that you
cannot override the built-in singleton and
prototype scopes).

3.4.5.1. Creating your own custom scope

Scopes are defined by the
org.springframework.beans.factory.config.Scope
interface. This is the interface that you will need to implement in order
to integrate your own custom scope(s) into the Spring container, and is described
in detail below. You may wish to look at the Scope
implementations that are supplied with the Spring Framework itself for an idea of
how to go about implementing your own. The Scope JavaDoc
explains the main class to implement when you need your own scope in more detail too.

The Scope interface has four methods dealing with getting objects
from the scope, removing them from the scope and allowing them to be 'destroyed' if needed.

The first method should return the object from the underlying scope. The session scope
implementation for example will return the session-scoped bean (and if it does not exist, return a
new instance of the bean, after having bound it to the session for future reference).

Object get(String name, ObjectFactory objectFactory)

The second method should remove the object from the underlying scope. The session scope implementation
for example, removes the session-scoped bean from the underlying session. The object should be returned
(you are allowed to return null if the object with the specified name wasn't found)

Object remove(String name)

The third method is used to register callbacks the scope should execute when it is destroyed or
when the specified object in the scope is destroyed. Please refer to the JavaDoc or a Spring scope
implementation for more information on destruction callbacks.

void registerDestructionCallback(String name, Runnable destructionCallback)

The last method deals with obtaining the conversation identifier for the underlying scope. This identifier is
different for each scope. For a session for example, this can be the session identifier.

String getConversationId()

SPR-2600 - TODO

3.4.5.2. Using a custom scope

After you have written and tested one or more custom Scope
implementations, you then need to make the Spring container aware of your new scope(s).
The central method to register a new Scope with the
Spring container is declared on the ConfigurableBeanFactory
interface (implemented by most of the concrete BeanFactory
implementations that ship with Spring); this central method is displayed below:

void registerScope(String scopeName, Scope scope);

The first argument to the registerScope(..) method is the
unique name associated with a scope; examples of such names in the Spring container
itself are 'singleton' and 'prototype'.
The second argument to the registerScope(..) method is an
actual instance of the custom Scope implementation
that you wish to register and use.

Let's assume that you have written your own custom
Scope implementation, and you have registered it
like so:

// note: the ThreadScope class does not ship with the Spring Framework
Scope customScope = new ThreadScope();
beanFactory.registerScope("thread", scope);

You can then create bean definitions that adhere to the scoping rules of your
custom Scope like so:

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

If you have your own custom Scope implementation(s),
you are not just limited to only programmatic registration of the custom scope(s).
You can also do the Scope registration declaratively,
using the CustomScopeConfigurer class.

The declarative registration of custom Scope
implementations using the CustomScopeConfigurer class is
shown below:

<?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">
<bean class="com.foo.ThreadScope"/>
</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>
需要研究一下Bean Scope问题, 留个记号

抱歉!评论已关闭.