Injecting a prototype/Session bean into a singleton bean
In Spring, most of the beans we work with are Singletons. If a singleton bean is wired with yet another singleton bean, there is absolutely no problem. But if it is wired with a bean which is of different scope, say prototype, how does it work? Here is the example:
public
class
RequestProcessor
{
private
RequestValidator validator;
public
void
handleRequest(String
requestId){
validator.validate(requestId);
//
Process the request and update
}
public
RequestValidator getValidator() {
return
validator;
}
public
void
setValidator(RequestValidator
validator) {
this.validator=
validator;
}
}
public
class
RequestValidator
{
private
List<String> errorMessages = new
ArrayList<String>();
public
RequestValidator() {
System.out.println("Validator
instance created!");
}
//
Validates the request and populates error messages
public
void
validate(String
requestId){
}
public
List<String> getErrorMessages() {
return
errorMessages;
}
}
And here is the spring configuration:
<bean
id="requestProcessor"
class="com.pramati.spring.RequestProcessor">
<property
name="validator"
ref="validator"/>
</bean>
<bean
id="validator"
scope="prototype"
class="com.pramati.spring.RequestValidator"/>
With this configuration, it is expected that when
ever I fetch requestProcessor from application context, it
will be wired with a new validator as we declared the validator
bean is of prototype scope. But this does not happen.
When the application context gets initialized, it
sees that requestProcessor is a singleton bean and
initializes it to the context after wiring it with all the
dependencies set. So from then onwards when we request context for
requestProcessor, it return the same bean every time. To
solve this issue, we have 2 approaches:
- Lookup Method injection: For this, we have to declare the beans as follows:
<bean
id="requestProcessor"
class="com.pramati.spring.RequestProcessor">
<lookup-method
name="getValidator"
bean="validator"/>
</bean>
<bean
id="validator"
scope="prototype"
class="com.pramati.spring.RequestValidator"/>
Whenever we define a bean with lookup methods,
Spring creates a subclass of the bean and overrides those methods
which are marked as lookup-methods. And this subclassed bean gets
registered into the context. The subclass delegates all the
non-lookup methods to the original class. For the lookup methods, it
overrides the implementation. So in our example, when getValidator()
is called, it returns a new validator instance.
We can roughly imagine our new subclass(registered
in container) like this:
requestProcessor
= new
RequestProcessor(){
public
RequestValidator getValidator(){
return
context.getBean("validator");
}
};
We could have directly fetched the bean
from application context in RequestProcessor itself. But this would
mean that the class is directly coupled to Spring framework. To do
this in a cleaner way, we can use lookup injection. This puts all the
spring related stuff at one place.
- Scoped Proxies: This can be implemented as:
<bean
id="requestProcessor"
class="com.pramati.spring.RequestProcessor">
<property
name="validator"
ref="validator"/>
</bean>
<bean
id="validator"
scope="prototype"
class="com.pramati.spring.RequestValidator">
<!--
This instructs the container to proxy the current bean-->
<aop:scoped-proxy/>
</bean>
Remember, in case of look up method
injection, proxy is created for singleton bean. But in case of scoped
proxies, proxy is created for prototype bean and wired into the
singleton bean during the process of registering the singleton bean
in the context. The proxy thus created understands the scope and
returns instances based on the requirements of the scope. So in our
case,
requestProcessor holds a
reference to proxy in place of
validator.
And in case of lookup method injection, when
requestProcessor gets loaded into the context, validator
will not be initialized at all. And when we call the look up method,
it returns the prototype bean. But instead of calling the method, if
you try to directly access the prototype bean(assuming it is
accessible), it gives a Nullpointer Exception as it didn’t get
initialized(We are not wiring it using property tag of bean)
In case of this, we can also configure how a proxy
can be created. It can be done in 2 ways
- CGLIB library which directly subclasses the object. This is the default option of Spring. For this, we must have CGLIB library our class path.
- Java Dynamic Proxies. For this to be activated, we have to call:
<aop:scoped-proxy
proxy-target-class="false"/>
Here in this
case, we don’t need any additional libraries in our class path. But
the scoped bean must implement at least one interface and it has to
be referred through the same interface at all places in order to get
itself wired.
Few points to note:
1.
Both method injection and scoped proxies work not only for prototype
beans. This works more generic. Whenever a bean of different scope is
injected into a singleton bean, we can use any of these techniques to
ensure that we get a corresponding scope object.
2. Note that in
the proxy, the method returning the prototype bean is overridden to
return a new instance for every single call.
Suppose we want to
display the error messages that we have got after validation:
requestProcessor.getValidator().validate();
for(String
message: requestProcessor.getValidator().getErrorMessages()){
logger.log(LogLevel.ERROR,
message);
}
This code seems
to print the error messages we have got after validation process. But
this will never print any error messages even if there are many
validation failures. This happens because
requestProcessor.getValidator() returns a new validator instance
every time it is called. So for this to work, the code has to be
modified as:
RequestValidator
validator = requestProcessor.getValidator();
validator.validate();
for(String
message: validator.getErrorMessages()){
logger.log(LogLevel.ERROR,
message);
}
This happens only
in case of prototype beans but works perfectly in case of other
non-singleton scopes(request, session, global-session).
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.The session scope
With the above bean definition in place, the Spring container will create a brand new instance of the bean , for the
lifetime
of a single HTTP Session
.According to Spring framework reference, a different approach needs to be followed in cases where a class which "
lives
longer
"(singleton bean in this case) needs to be injected
with another class having a comparatively shorter
life-span(session-scoped bean). The approach is different for
prototype & singleton scope though.In your XML, what we want is that the singletonBean instance should be instantiated only once, and it should be injected with sessionBean. But since
sessionBean
is
session-scoped(which means it should be re-instantiated for every
session), the configuration is ambiguous(as the dependencies are set
at instantiation time and the session scoped value can change later
also).So instead of injecting with that class, its injected with a proxy that exposes the exact same public interface as sessionBean. The container injects this proxy object into the singletonBean bean, which is unaware that this sessionBean reference is a proxy. Its specified by writing this tag in the sessionBean:
<aop:scoped-proxy/>
XML Configuration:
<bean name="singletonBean" class="somepkg.SingletonBean">
<property name="someProperty" ref="sessionBean"/>
</bean>
<bean name="sessionBean" class="somepkg.SessionBean" scope="session">
<aop:scoped-proxy/>
</bean>
When a
singletonBean
instance invokes a method on the dependency-injected sessionBean
object, it actually is invoking a method on the proxy. The proxy then
fetches the real sessionBean object from (in this case) the HTTP
Session, and delegates the method invocation onto the retrieved real
sessionBean object.
Lookup Method Injection
When you use
singleton-scoped
beans
with dependencies on prototype beans
, be
aware that dependencies are resolved at instantiation time. Thus if
you dependency-inject a prototype-scoped
bean into a singleton-scoped bean, a new prototype bean is
instantiated and then dependency-injected into the singleton bean.
The prototype instance is the sole instance that is ever supplied to
the singleton-scoped bean.However, suppose you want the singleton-scoped bean to acquire a new instance of the prototype-scoped bean repeatedly at runtime. You cannot dependency-inject a prototype-scoped bean into your singleton bean, because that injection occurs only once, when the Spring container is instantiating the singleton bean and resolving and injecting its dependencies.
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
Lookup method
injection is the ability of the container to override
methods on container
managed beans, to return the lookup
result for another named bean in the container. The lookup
typically involves a prototype bean
as
in the scenario described in the preceding section. The Spring
Framework implements this method injection by using bytecode
generation from the CGLIB library
to
generate dynamically a subclass that overrides the method.
No comments:
Post a Comment