June 29, 2022

Injecting Prototype Bean into a Singleton Bean in Spring

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.

MsgManager

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

Output
In 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.
MsgManager
@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

No comments:

Post a Comment