Spring DI - Singleton beans with prototype-bean dependencies

Upasana | April 27, 2019 | 3 min read | 1,544 views


Injecting Prototype Bean into Singleton Bean

Prototype bean

Every time we request a bean, Spring shall create and inject a new instance of bean as the dependency. This is different from Singleton Bean where only one instance is created application context wide and reused again and again. Most Beans in a typical Spring Application are declared Singleton.

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean, or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other.

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.

What is challenge?

So problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container only creates the singleton bean A once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed on method invocation.

Spring Boot - Lookup method injection

Lookup method injection shall be used in Spring Boot Applications to Inject a Prototype Bean into Singleton Bean.

Prototype Bean
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ThreadLocalRandom;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {

    private int dateTimeString = ThreadLocalRandom.current().nextInt();       (1)

    public int getSeed() {
        return dateTimeString;
    }
}
1 We are injecting a Random Seed value at Prototype Bean Construction Time, so if getSeed() returns same value, its the same instance of Bean otherwise a new instance will show a different value for each method call.
Singleton Bean with prototype bean dependency
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component;

@Component
public class SingletonBean {

    public void showMessage() {
        PrototypeBean bean = getPrototypeBean();    (1)
        System.out.println("The prototype bean version is " + bean.getSeed());
    }

    @Lookup       (2)
    public PrototypeBean getPrototypeBean() {
        //Do not provide method implementation, spring will override this method behind the scenes.
        return null;
    }
}
1 Everytime this method is called, a new instance of Prototype Bean should be created, so we shall see a different seed value on each method call.
2 Spring DI will lookup the actual bean at runtime, so we do not need to provide an implementation here.
Main Spring Boot Application
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(SingletonBean singletonBean, ApplicationContext ctx) {
        return args -> {

            singletonBean.showMessage();      (1)
            singletonBean.showMessage();
            singletonBean.showMessage();

            SpringApplication.exit(ctx, () -> 0);
        };
    }

}
1 Each method invocation shall create a new instance of Prototype Bean, so we shall see a different seed value each time.
Program Output
2017-12-27 10:11:48.982  INFO 44948 --- [           main] .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port(s) 8080 (http/1.1)
The prototype bean version is 183657962
The prototype bean version is 1467467319
The prototype bean version is -319235431

How to do it wrongly?

If you directly inject a Protoype Bean into Singleton Bean, without a @Lookup method, then a single instance of Protoype Bean will be created and that’s wrong approach.


Top articles in this category:
  1. What are different Bean Scopes in Spring?
  2. Unresolved circular dependency in spring dependency injection
  3. SendGrid Attachments with Spring Boot
  4. Dialoglfow fulfillment with Spring Boot
  5. Elasticsearch with Spring Boot + Spring Data
  6. Sendgrid Dynamic Templates with Spring Boot
  7. Spring Data ElasticSearch with Basic Auth

Recommended books for interview preparation:

Find more on this topic:
Buy interview books

Java & Microservices interview refresher for experienced developers.