I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

1. Overview

In this quick article, we’re going to show different approaches of injecting prototype beans into a singleton instance. We’ll discuss the use cases and the advantages/disadvantages of each scenario.

By default, Spring beans are singletons. The problem arises when we try to wire beans of different scopes. For example, a prototype bean into a singleton. This is known as the scoped bean injection problem.

To learn more about bean scopes, this write-up is a good place to start.

2. Prototype Bean Injection Problem

In order to describe the problem, let’s configure the following beans:

@Configuration
public class AppConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PrototypeBean prototypeBean() {
        return new PrototypeBean();
    }

    @Bean
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }
}

Notice that the first bean has a prototype scope, the other one is a singleton.

Now, let’s inject the prototype-scoped bean into the singleton – and then expose if via the getPrototypeBean() method:

public class SingletonBean {

    // ..

    @Autowired
    private PrototypeBean prototypeBean;

    public SingletonBean() {
        logger.info("Singleton instance created");
    }

    public PrototypeBean getPrototypeBean() {
        logger.info(String.valueOf(LocalTime.now()));
        return prototypeBean;
    }
}

Then, let’s load up the ApplicationContext and get the singleton bean twice:

public static void main(String[] args) throws InterruptedException {
    AnnotationConfigApplicationContext context 
      = new AnnotationConfigApplicationContext(AppConfig.class);
    
    SingletonBean firstSingleton = context.getBean(SingletonBean.class);
    PrototypeBean firstPrototype = firstSingleton.getPrototypeBean();
    
    // get singleton bean instance one more time
    SingletonBean secondSingleton = context.getBean(SingletonBean.class);
    PrototypeBean secondPrototype = secondSingleton.getPrototypeBean();

    isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned");
}

Here’s the output from the console:

Singleton Bean created
Prototype Bean created
11:06:57.894
// should create another prototype bean instance here
11:06:58.895

Both beans were initialized only once, at the startup of the application context.

3. Injecting ApplicationContext

We can also inject the ApplicationContext directly into a bean.

To achieve this, either use the @Autowire annotation or implement the ApplicationContextAware interface:

public class SingletonAppContextBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public PrototypeBean getPrototypeBean() {
        return applicationContext.getBean(PrototypeBean.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
      throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Every time the getPrototypeBean() method is called, a new instance of PrototypeBean will be returned from the ApplicationContext.

However, this approach has serious disadvantages. It contradicts the principle of inversion of control, as we request the dependencies from the container directly.

Also, we fetch the prototype bean from the applicationContext within the SingletonAppcontextBean class. This means coupling the code to the Spring Framework.

4. Method Injection

Another way to solve the problem is method injection with the @Lookup annotation:

@Component
public class SingletonLookupBean {

    @Lookup
    public PrototypeBean getPrototypeBean() {
        return null;
    }
}

Spring will override the getPrototypeBean() method annotated with @Lookup. It then registers the bean into the application context. Whenever we request the getPrototypeBean() method, it returns a new PrototypeBean instance.

It will use CGLIB to generate the bytecode responsible for fetching the PrototypeBean from the application context.

5. javax.inject API

The setup along with required dependencies are described in this Spring wiring article.

Here’s the singleton bean:

public class SingletonProviderBean {

    @Autowired
    private Provider<PrototypeBean> myPrototypeBeanProvider;

    public PrototypeBean getPrototypeInstance() {
        return myPrototypeBeanProvider.get();
    }
}

We use Provider interface to inject the prototype bean. For each getPrototypeInstance() method call, the myPrototypeBeanProvider.get() method returns a new instance of PrototypeBean.

6. Scoped Proxy

By default, Spring holds a reference to the real object to perform the injection. Here, we create a proxy object to wire the real object with the dependent one.

Each time the method on the proxy object is called, the proxy decides itself whether to create a new instance of the real object or reuse the existing one.

To set up this, we modify the Appconfig class to add a new @Scope annotation:

@Scope(
  value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
  proxyMode = ScopedProxyMode.TARGET_CLASS)

By default, Spring uses CGLIB library to directly subclass the objects. To avoid CGLIB usage, we can configure the proxy mode with ScopedProxyMode.INTERFACES, to use the JDK dynamic proxy instead.

7. ObjectFactory Interface

Spring provides the ObjectFactory<T> interface to produce on demand objects of the given type:

public class SingletonObjectFactoryBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;

    public PrototypeBean getPrototypeInstance() {
        return prototypeBeanObjectFactory.getObject();
    }
}

Let’s have a look at getPrototypeInstance() method; getObject() returns a brand new instance of PrototypeBean for each request. Here, we have more control over initialization of the prototype.

Also, the ObjectFactory is a part of the framework; this means avoiding additional setup in order to use this option.

8. Testing

Let’s now write a simple JUnit test to exercise the case with ObjectFactory interface:

@Test
public void givenPrototypeInjection_WhenObjectFactory_ThenNewInstanceReturn() {

    AbstractApplicationContext context
     = new AnnotationConfigApplicationContext(AppConfig.class);

    SingletonObjectFactoryBean firstContext
     = context.getBean(SingletonObjectFactoryBean.class);
    SingletonObjectFactoryBean secondContext
     = context.getBean(SingletonObjectFactoryBean.class);

    PrototypeBean firstInstance = firstContext.getPrototypeInstance();
    PrototypeBean secondInstance = secondContext.getPrototypeInstance();

    assertTrue("New instance expected", firstInstance != secondInstance);
}

After successfully launching the test, we can see that each time getPrototypeInstance() method called, a new prototype bean instance created.

9. Conclusion

In this short tutorial, we learned several ways to inject the prototype bean into the singleton instance.

As always, the complete code for this tutorial can be found on GitHub project.

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS

Leave a Reply

5 Comments on "Injecting Prototype Beans into a Singleton Instance in Spring"

avatar
  Subscribe  
newest oldest most voted
Notify of
Tomas
Guest

Nice article! Maybe you could also cover usage of abstract method in singleton bean returning the prototype bean. Spring then inject the implementation for you.

Loredana Crusoveanu
Editor

Hey Tomas,

Thanks for the suggestion. To be clear, you’re referring to the @Lookup method being abstract instead of the implementation that returns null?

Thanks.

Les
Guest

Hi,

I’m having a problem trying to use ObjectFactory – at least as it’s written above. I’m using Spring Boot and have a singleton class which needs to make use of prototype beans from another component. But when I call the factory.getObject() method, all I get back is ‘null’.

I have a feeling that there is something fundamental that I’m missing, but I haven’t yet found another explanation. I’ve also tried the @Lookup approach and gotten “null” there as well.

All help appreciated!

Les

Loredana Crusoveanu
Editor

Hey Les,

It sounds as if the prototype bean doesn’t exist at all, but it’s difficult to say from the description.

If you can create a stackoverflow post with relevant code details, then paste the link to it here, we’ll have a look.

Cheers.

Les
Guest

Turns out it was a configuration error on my part – bad bean generation issue. Code example works as expected!