This article shows different approaches of injecting prototype bean into a singleton bean in Spring so that new instance of prototype scoped bean is created every time singleton bean needs it.
Problem when Singleton bean collaborates with prototype bean
Suppose a singleton scoped bean has a dependency on a prototype scoped bean. Spring IOC container creates the Singleton bean only once so there is only one opportunity to set the properties. You cannot inject a prototype-scoped bean (new instance of bean) into your singleton bean every time one is needed.
Here is an example to understand the problem while injecting prototype bean into a singleton bean. There are two classes MsgManager and MsgHandler. MsgManager is configured as a singleton bean where as MsgHandler is defined with a prototype scope.
MsgManagerIn MsgManager class there is a dependency on MsgHandler instance which is then used to call a method.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MsgManager { @Autowired private MsgHandler msgHandler; public void handleRequest(){ msgHandler.handleMessage(); } }MsgHandler
MsgHandler is configured to have a prototype scope.
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("prototype") public class MsgHandler { MsgHandler(){ System.out.println("In MsgHandler Constructor"); } public void handleMessage(){ System.out.println("Handling message"); } }XML Configuration
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.knpcode" /> </beans>You can use the following class with main method to read the configuration and call the bean method.
public class App { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml"); MsgManager bean1 = context.getBean("msgManager", MsgManager.class); // calling method two times bean1.handleRequest(); MsgManager bean2 = context.getBean("msgManager", MsgManager.class); bean2.handleRequest(); context.close(); } }Output
19:43:15.557 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'msgManager' In MsgHandler Constructor Handling message Handling message
As you can see "In MsgHandler Constructor" is displayed only once that means only a single instance of MsgHandler is created not two as expected even when it has a scope of prototype.
Injecting prototype bean into singleton bean
Now when you have seen the problem that in a Singleton bean property is set only once so bean with a prototype scope is also set only once and the same instance is used rather than creating new instance every time let’s shift the focus on solutions we have for Injecting prototype bean into singleton bean in Spring framework.
1. By implementing ApplicationContextAware interface
One way to get a new bean is by implementing ApplicationContextAware interface and use that context to get the bean with in the class.
With the implementation of ApplicationContextAware interface, MsgManager class is updated as given below.
@Component public class MsgManager implements ApplicationContextAware{ private ApplicationContext applicationContext; public void handleRequest(){ getMsgHandler().handleMessage(); } // This method returns instance public MsgHandler getMsgHandler() { return applicationContext.getBean("msgHandler", MsgHandler.class); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
Now when you run the App class you get the output as-
In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
But this is not considered a good solution because the business code is aware of and coupled to the Spring Framework.
2. Using lookup method injection
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 Spring Framework implements this method injection by using bytecode generation from the CGLIB library to generate dynamically a subclass that overrides the method.
@Component public class MsgManager{ private ApplicationContext applicationContext; private MsgHandler msgHandler; public void handleRequest(){ msgHandler = getMsgHandler(); msgHandler.handleMessage(); } @Lookup public MsgHandler getMsgHandler() { return null; } }
Spring framework dynamically generates a subclass by extending MsgManager class and implements the method annotated with @Lookup annotation to return the lookup result.
OutputIn MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
As you can see now the constructor is called twice that means a new instance of MsgHandler is created every time.
3. Using scoped proxy
Another way to inject Prototype bean into a singleton bean is by using scoped proxy.
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; @Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class MsgHandler { MsgHandler(){ System.out.println("In MsgHandler Constructor"); } public void handleMessage(){ System.out.println("Handling message"); } }
With this change the container will create a proxy object of the MsgHandler that is used for wiring. This proxy object fetches the real MsgHandler class object from the defined scoping mechanism (prototype, request, session etc.)
There are two modes for creating proxies-
- ScopedProxyMode.TARGET_CLASS- Create a class-based proxy (uses CGLIB).
- ScopedProxyMode.INTERFACES- Create a JDK dynamic proxy implementing all interfaces exposed by the class of the target object.
@Component public class MsgManager{ @Autowired private MsgHandler msgHandler; public void handleRequest(){ msgHandler.handleMessage(); } }Output
In constructor of ClassB In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
4. Using ObjectFactory interface
There is also a functional interface ObjectFactory which defines a factory that can return an Object instance (shared or independent) when invoked. Using this interface you can encapsulate a generic factory which returns a new instance (prototype) of some target object on each invocation.
@Component public class MsgManager{ @Autowired private ObjectFactory<MsgHandler> msgHandlerObjectFactory; public void handleRequest(){ msgHandlerObjectFactory.getObject().handleMessage(); } }
Here msgHandlerObjectFactory.getObject() method call returns a new instance of MsgHandler bean (which has prototype scope) every time.
@Component @Scope(value = "prototype") public class MsgHandler { MsgHandler(){ System.out.println("In MsgHandler Constructor"); } public void handleMessage(){ System.out.println("Handling message"); } }Output
In MsgHandler Constructor Handling message In MsgHandler Constructor Handling message
That's all for the topic Injecting Prototype Bean into a Singleton Bean in Spring . If something is missing or you have something to share about the topic please write a comment.
You may also like
- Injecting List, Set or Map in Spring
- Spring Bean Scopes
- Spring + JPA (Hibernate) OneToMany Example
- Creating Password Protected Zip File in Java
- Java Collections Framework Tutorial
- Java Primitive Type Streams With Examples
- Remove Spaces From a String in Java - trim(), strip()
- Generating PDF in Java Using PDFBox Tutorial
No comments:
Post a Comment