Tuesday, January 23, 2007

Spring: Prototype beans with Singletons - 1

In a Spring application, when a singleton bean uses another singleton bean, or a non-singleton bean uses another non-singleton bean, setting one bean as a property of the other is quite adequate. But when the two beans are not of the same Scope i.e. When a singleton bean has to use a non-singleton (prototype) bean, the container will create the singleton bean just once, and the prototype bean that it depends on will also be set only once. The container cannot provide the Singleton bean with a new instance of the prototype bean each time it is needed. This post and the next describe four different approaches to address this problem.
Using BeanFactoryAwareForgo dependency injection.
Using Lookup Method InjectionDynamically generate a subclass overriding the method, using bytecode generation via the CGLIB library
Using ServiceLocatorFactoryBeanA custom locator interface.
In Web ApplicationsUses RequestContextListener in addition to ContextLoaderListener

The Command
The following is the code fo the Command class that will be used by the different Command Managers in the example.
package beans;

public class Command {
private String someProperty;

public String getSomeProperty() {
return someProperty;
}

public void setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}

public String execute() {
return "Command property : " + someProperty;
}
}
Command.java

Using BeanFactory Aware

In this approach, we delegate the creation of the prototype bean to the Singleton itself. This can be achieved by implementing the BeanFactoryAware interface as shown below.
package beans;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

public class CommandManager implements BeanFactoryAware {

private BeanFactory beanFactory;

public Object process(String prop) {
Command command = createCommand();
command.setSomeProperty(prop);
return command.execute();
}

protected Command createCommand() {
return (Command) this.beanFactory.getBean("command");
}

public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
CommandManager.java

You can see here that the createCommand method uses the beanFactory.getBean() method to get the Command bean. This dependency on the Spring API is not that desirable.

Lookup Method Injection

Lookup method injection refers to the ability of the container to override methods on container managed beans, to return the result of looking up another named bean in the container.The Spring Framework implements this method injection by dynamically generating a subclass overriding the method, using bytecode generation via the CGLIB library. The command manager of the above implementation is changed in the following to allow lookup method injection.
package beans;

public abstract class CmdMgrNoBFA {
public Object process(String prop) {
Command command = createCommand();
command.setSomeProperty(prop);
return command.execute();
}

protected abstract Command createCommand();
}
CmdMgrNoBFA.java

You can see here that the Command Manager does not implement the BeanFactoryAware interface. The createCommand was declared as abstract method, but it may also be defined here (the container will override it any way). The signature of the lookup method must have the following form
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
Note that beans that have been the target of method injection cannot be serialized.

Using ServiceLocatorFactoryBean

Service locators help avoid the usage of the BeanFactory API using a custom locator interface. Service locators are typically used for prototype beans. Setter or constructor injection of the target bean is preferable, where the target bean is a Singleton. The custom locator interface shown below does not depend on the bean factory API.
package beans;

public interface ServiceFactory {
public Command getCommand();
public Command getCommand(String beanName);
}
ServiceFactory.java

The corresponding Command Manager must have a ServiceFactory property that will be injected by the container. This is the code for the CommandManager used here.
package beans;

public class CmdMgrServiceFactory {
public ServiceFactory serviceFactory;

public Object process(String prop) {
Command command = serviceFactory.getCommand("command");
command.setSomeProperty(prop);
return command.execute();
}

public ServiceFactory getServiceFactory() {
return serviceFactory;
}

public void setServiceFactory(ServiceFactory serviceFactory) {
this.serviceFactory = serviceFactory;
}

}
CmdMgrServiceFactory.java

Invoking the no-arg getCommand() method or the single-arg method with null, will return an instance of the Command object by matching it's type if there is only one bean of the type beans.Command. If there are more then beans.Command beans, then a NoSuchBeanDefinitionException is thrown. By default, the single string argument is matched to the bean names defined in the applicationContext.xml file. Alternatively, you can set a mapping of the string argument to bean names using a Properties object for mapping. This Properties object can be injected in using the serviceMappings property. Using with Web applications and rest of the definitions are in the next post.

4 comments:

  1. hi, im new to Spring, i have a doubt regarding the Prototype beans with singleton .As u mentioned that servicelocatorbean wiill take care, but we have here the interface servicelocator,how do spring container will initialize this and give getCommand implementation,it is better to give full implementation.

    ReplyDelete
  2. You could also use ObjectFactoryCreatingFactoryBean as described in the Spring Javadoc for this purpose.

    ReplyDelete
  3. I had a situation where both the beans had prototype scope, but since the container bean was wired through a proxy , it instantiated with the same instance of the contained bean on each call of getBean().

    Like
    bean id="a" class="foo.ClassA" scope="prototype"

    bean id="b" class="foo.ClassB" scope="prototype"
    constructor-arg ref="a"


    bean id="bProxy" parent="dataAdviceProxy"
    property name="target" ref="b"


    Now evry call to getBean("bProxy"), gives me a new instance of B but it is constructed with the same instance of A (since there is no explicit call to getBean(a).

    Is there a way to get around this problem , basically , any way to specify transitively that a new instance of B should always instantiate with new instances of A if both of them are non singletons?

    ReplyDelete

Popular Posts