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
|
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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<>
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<>
<aop:scoped-proxy/>
</bean>
<>
<bean id="userService" class="com.foo.SimpleUserService">
<>
<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
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>