Spring Prototype Beans with Runtime Arguments
Spring offers various bean scopes, with the default being singleton, which creates a single instance throughout the application. Prototype scope, on the other hand, creates a new instance every time the bean is requested. But what if we need a prototype bean that can be customized with runtime arguments? This article explores several methods to achieve this in Spring.
1. Define the Prototype Bean
Spring provides several bean scopes, among which the two most commonly used are Singleton (A single instance per Spring container) and Prototype (A new instance every time the bean is requested). In this article, we will use the prototype scope.
To demonstrate this, we will create a simple Spring Boot application with the following dependencies spring-boot-starter-web and spring-boot-starter-test.
First, define a simple prototype-scoped bean that accepts runtime arguments. Below is a simple MessageService bean.
public class MessageService {
private String message;
public MessageService() {
}
public MessageService(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void printMessage() {
System.out.println("Message: " + message);
}
}
1.1 Create Configuration Class for Prototype Scoped Bean
@Configuration
@ComponentScan(basePackages = { "com.jcg.prototypescopebean" })
public class AppConfig {
@Bean(name="MessageService")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public MessageService createMessageService(String message) {
return new MessageService(message);
}
}
In the code above:
- The
createMessageServicemethod is annotated with@Bean, indicating that it will produce a bean managed by the Spring container. Thenameattribute specifies that this bean will be named MessageService. - The
@Scope(BeanDefinition.SCOPE_PROTOTYPE)annotation specifies that a new instance ofMessageServiceshould be created each time it is requested, instead of using a single shared instance.
2. Using Application Context
We can retrieve the bean from the application context multiple times within our main class to verify that it returns different instances. We access and print messages from our prototype bean from two different instances.
@SpringBootApplication
public class PrototypescopebeanApplication implements CommandLineRunner {
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(PrototypescopebeanApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
String beanName = "MessageService";
// Retrieve and use the prototype bean
MessageService messageService1 = (MessageService) applicationContext.getBean(beanName, "Hello, World!");
messageService1.printMessage();
MessageService messageService2 = (MessageService) applicationContext.getBean(beanName, "Goodbye, World!");
messageService2.printMessage();
}
}
From the code above, each call to applicationContext.getBean with the same bean name but different parameters creates a new instance of the MessageService bean.
@Autowiredis used to inject an instance ofApplicationContext.applicationContext.getBean(beanName, "Hello, World!")retrieves a new instance of theMessageServicebean with the message – Hello, World!applicationContext.getBean(beanName, "Goodbye, World!")retrieves another new instance of theMessageServicebean with the message – Goodbye, World!.
The output is:
3. Using the @Lookup Annotation
The @Lookup annotation allows injecting a method that retrieves a new prototype bean instance whenever called. This approach is ideal for situations where the bean is used within another bean.
To demonstrate the use of the @Lookup annotation for retrieving prototype-scoped beans, let’s create a separate component class that contains the @Lookup method. We’ll then inject this component into the main class and use it to retrieve and print messages from our prototype beans.
3.1 Creating the Lookup Component
Next, Create a separate component class with the @Lookup method:
@Component
public class MessageServiceFactory {
@Lookup
public MessageService createMessageService(String message) {
return new MessageService(message);
}
}
3.2 Main Application Class
Now, inject the MessageServiceFactory into the main application class and use it to retrieve and print messages from the prototype beans:
@SpringBootApplication
public class PrototypescopebeanApplication implements CommandLineRunner {
@Autowired
private MessageServiceFactory messageServiceFactory;
public static void main(String[] args) {
SpringApplication.run(PrototypescopebeanApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
MessageService messageService1 = messageServiceFactory.createMessageService("Hello, World!");
messageService1.printMessage();
MessageService messageService2 = messageServiceFactory.createMessageService("Goodbye, World!");
messageService2.printMessage();
}
}
4. Using ObjectProvider for Managing Prototype-Scoped Beans
To enhance the code by using ObjectProvider, we can leverage its ability to retrieve beans lazily and manage prototype-scoped beans more elegantly. Here’s how to modify the PrototypescopebeanApplication class to use ObjectProvider for the MessageService bean.
4.1 Create the ObjectProvider Component
Create a separate component class that uses ObjectProvider to retrieve the MessageService bean:
@Component
public class MessageServiceProvider {
@Autowired
private ObjectProvider<MessageService> messageServiceProvider;
public MessageService getMessageService(String message) {
return messageServiceProvider.getObject(message);
}
}
The ObjectProvider<MessageService> is injected into the class using the @Autowired annotation. ObjectProvider is a Spring utility that allows for lazy retrieval of beans and is particularly useful for prototype-scoped beans.
getMessageService method takes a String parameter message. It calls messageServiceProvider.getObject(message) to retrieve a new instance of MessageService with the provided message. Since MessageService is defined with prototype scope, each call to getObject returns a new instance of MessageService.
4.2 Update Main Application Class
Inject the MessageServiceProvider into the main application class and use it to retrieve and print messages from the prototype beans:
@SpringBootApplication
public class PrototypescopebeanApplication implements CommandLineRunner {
@Autowired
private MessageServiceProvider messageServiceProvider;
public static void main(String[] args) {
SpringApplication.run(PrototypescopebeanApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Retrieve and use the prototype bean
MessageService messageService1 = messageServiceProvider.getMessageService("Hello, World!");
messageService1.printMessage();
MessageService messageService2 = messageServiceProvider.getMessageService("Goodbye, World!");
messageService2.printMessage();
}
}
5. Using the Functional Interface
We can also use a Function interface to define the creation logic of our prototype-scoped beans. Here is the updated AppConfig class that includes a Function<String, MessageService> bean definition:
5.1 Create a MessageServiceFunction Class
Create a separate class that uses the Function interface to create instances of the MessageService bean:
@Component
public class MessageServiceFunction {
@Autowired
private Function messageServiceFunction;
public MessageService createMessageService(String message) {
return messageServiceFunction.apply(message);
}
}
In the above class,
private Function<String, MessageService> messageServiceFunctionfield represents a function that will take aStringparameter (the message) and return an instance ofMessageService.createMessageServicemethod is responsible for creating instances of theMessageServicebean using the injectedFunction. It accepts aStringparametermessage, which represents the message to be passed to theMessageServicebean.
5.2 Update Configuration Class
@Configuration
@ComponentScan(basePackages = { "com.jcg.prototypescopebean" })
public class AppConfig {
@Bean(name="MessageService")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public MessageService createMessageService(String message) {
return new MessageService(message);
}
@Bean
public Function<String, MessageService> serviceFactory() {
return message -> new MessageService(message);
}
}
The above class defines a Function<String, MessageService> bean named serviceFactory, which uses a lambda expression to create instances of the MessageService bean.
5.3 Update the Main Application Class
Inject the MessageServiceFunction into the main application class and use it to create instances of the MessageService bean:
@SpringBootApplication
public class PrototypescopebeanApplication implements CommandLineRunner {
@Autowired
private MessageServiceFunction messageServiceFunction;
public static void main(String[] args) {
SpringApplication.run(PrototypescopebeanApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Retrieve and use the prototype bean
MessageService messageService1 = messageServiceFunction.createMessageService("Hello, World!");
messageService1.printMessage();
MessageService messageService2 = messageServiceFunction.createMessageService("Goodbye, World!");
messageService2.printMessage();
}
}
6. Conclusion
In this article, we explored different strategies for managing prototype-scoped beans in a Spring Boot application. We examined the use of ObjectProvider to lazily retrieve prototype beans, ensuring that each request results in a new instance. Subsequently, we introduced the Function interface, demonstrating how it can be utilized to encapsulate bean creation logic within a separate class. Additionally, we explored the @Lookup annotation, which offers a concise way to retrieve prototype beans directly from the Spring container.
7. Download the Source Code
This was an article on how to create a Spring Prototype Bean with Runtime Arguments.
You can download the full source code of this example here: Spring Prototype Bean with Runtime Arguments





