1. Understanding the Problem
When working with Spring's dependency injection, a common question arises: " How can I inject a Prototype bean into a Singleton without losing the prototype behavior?" By default, Spring's Singleton scope is often used, meaning that the container will instantiate only one instance of the bean, which is reused across the application. The Prototype scope, on the other hand, creates a new instance each time it is requested.
Injecting a Prototype bean into a Singleton bean seems straightforward at first glance. However, if done using traditional dependency injection, Spring will inject a single instance of the Prototype bean into the Singleton , and that instance will be reused every time the Singleton bean requests it. This defeats the purpose of the Prototype scope, which is to create a new instance on each request.
1.1 The Pitfall of Direct Injection
Consider the following scenario: You have a Prototype bean named TaskProcessor and a Singleton bean named TaskManager that manages these processors. If you inject the TaskProcessor directly into the TaskManager , the result might not be what you expect.
@Component
@Scope("prototype")
public class TaskProcessor {
public void process() {
System.out.println("Processing task with: " + this);
}
}
@Component
@Scope("singleton")
public class TaskManager {
private final TaskProcessor taskProcessor;
@Autowired
public TaskManager(TaskProcessor taskProcessor) {
this.taskProcessor = taskProcessor;
}
public void manage() {
taskProcessor.process();
}
}
In this case, even though TaskProcessor is marked as a Prototype , a single instance will be injected into the TaskManager during startup, and the same instance will be used throughout the application. This defeats the purpose of using the Prototype scope.
2. How to Properly Inject a Prototype Bean into a Singleton
The solution to this issue is to use a method that ensures a new Prototype bean is created every time it is needed. There are several approaches in Spring to achieve this, including using ObjectFactory , Provider , or Lookup Method Injection ,...
2.1 Using ObjectFactory
Spring’s ObjectFactory is a functional interface that can be used to request a new instance of a Prototype bean when required. This ensures that every time the bean is needed, a fresh instance is provided.
Here’s how you can modify the TaskManager to inject the Prototype bean using ObjectFactory :
@Component
@Scope("singleton")
public class TaskManager {
private final ObjectFactory<TaskProcessor> taskProcessorFactory;
@Autowired
public TaskManager(ObjectFactory<TaskProcessor> taskProcessorFactory) {
this.taskProcessorFactory = taskProcessorFactory;
}
public void manage() {
TaskProcessor taskProcessor = taskProcessorFactory.getObject();
taskProcessor.process();
}
}
With this setup, every time TaskManager.manage() is called, a new TaskProcessor instance is created and used.
2.2 Using Provider
The Provider interface from the javax.inject package is another powerful way to achieve prototype injection. Like ObjectFactory , it allows for the creation of a new Prototype bean instance whenever it is requested.
Here’s an example of how to implement this with Provider :
@Component
@Scope("singleton")
public class TaskManager {
private final Provider<TaskProcessor> taskProcessorProvider;
@Autowired
public TaskManager(Provider<TaskProcessor> taskProcessorProvider) {
this.taskProcessorProvider = taskProcessorProvider;
}
public void manage() {
TaskProcessor taskProcessor = taskProcessorProvider.get();
taskProcessor.process();
}
}
This method is very similar to ObjectFactory , but many developers prefer Provider because of its clean, well-documented API.
2.3 Using Lookup Method Injection
Another option provided by Spring is Lookup Method Injection, which uses the @Lookup annotation. Spring will override the method at runtime to return a new instance of the Prototype bean each time it is called.
Here’s how you can use @Lookup :
@Component
@Scope("singleton")
public class TaskManager {
public void manage() {
TaskProcessor taskProcessor = getTaskProcessor();
taskProcessor.process();
}
@Lookup
protected TaskProcessor getTaskProcessor() {
return null; // This method will be overridden by Spring
}
}
The @Lookup annotation tells Spring to replace this method with code that retrieves a new Prototype bean at runtime. Although this approach is easy to use, it is generally less favored than ObjectFactory or Provider due to its reliance on Spring’s dynamic proxy mechanism, which may slightly impact performance.
2.4 Using ApplicationContext
When using ApplicationContext.getBean(), you request a bean from the Spring context at runtime, ensuring that each time the method is called, a new Prototype bean is created.
Here’s how you can use it:
The TaskProcessor bean will still be defined with a Prototype scope, ensuring that every time it’s requested, a new instance will be created.
@Component
@Scope("prototype")
public class TaskProcessor {
public void process() {
System.out.println("Processing task with: " + this);
}
}
In the TaskManager, we’ll inject the ApplicationContext. Then, whenever the manage() method is called, we’ll use getBean() to retrieve a new TaskProcessor instance.
@Component
@Scope("singleton")
public class TaskManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void manage() {
TaskProcessor taskProcessor = applicationContext.getBean(TaskProcessor.class);
taskProcessor.process();
}
}
3. Demo Results
Let’s test the behavior of injecting a Prototype bean into a Singleton using the Provider approach. When the TaskManager.manage() method is called multiple times, a new TaskProcessor instance should be created each time, as expected.
@SpringBootApplication
public class PrototypeInjectionApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(PrototypeInjectionApplication.class, args);
TaskManager taskManager = context.getBean(TaskManager.class);
taskManager.manage(); // Each call should print a new TaskProcessor instance
taskManager.manage();
taskManager.manage();
}
}
Expected Output:
Processing task with: TaskProcessor@12a3a380
Processing task with: TaskProcessor@45bc5d61
Processing task with: TaskProcessor@25cd1c48
As you can see, a new instance of TaskProcessor is created every time the manage() method is invoked, thus preserving the prototype behavior.
4. Conclusion
Injecting a Prototype bean into a Singleton in Spring is a common requirement in real-world applications. However, directly injecting a Prototype bean can cause it to behave like a Singleton, which defeats its purpose. By using mechanisms like ObjectFactory , Provider , or @Lookup method injection, you can ensure that a new instance of the Prototype bean is created each time it is needed.
If you have any questions or need further clarification on how to inject Prototype beans into Singleton beans in Spring, feel free to leave a comment below! I’d be happy to help.
Read posts more at : Injecting a Prototype Bean into a Singleton in Spring
Top comments (0)