Spring Java Config: how do you create a prototype-scoped @Bean with runtime arguments?
In a @Configuration
class, a @Bean
method like so
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
is used to register a bean definition and provide the factory for creating the bean. The bean that it defines is only instantiated upon request using arguments that are determined either directly or through scanning that ApplicationContext
.
In the case of a prototype
bean, a new object is created every time and therefore the corresponding @Bean
method is also executed.
You can retrieve a bean from the ApplicationContext
through its BeanFactory#getBean(String name, Object... args)
method which states
Allows for specifying explicit constructor arguments / factory method
arguments, overriding the specified default arguments (if any) in the
bean definition.Parameters:
args arguments to use if creating a prototype using explicit arguments
to a static factory method. It is invalid to use a non-null args value
in any other case.
In other words, for this prototype
scoped bean, you are providing the arguments that will be used, not in the constructor of the bean class, but in the @Bean
method invocation. (This method has very weak type guarantees since it uses a name lookup for the bean.)
Alternatively, you can use the typed BeanFactory#getBean(Class requiredType, Object... args)
method which looks up the bean by type.
This is at least true for Spring versions 4+.
Note that, if you don't want to start with the ApplicationContext
or BeanFactory
for your bean retrieval, you can inject an ObjectProvider
(since Spring 4.3).
A variant of
ObjectFactory
designed specifically for injection points,
allowing for programmatic optionality and lenient not-unique handling.
and use its getObject(Object... args)
method
Return an instance (possibly shared or independent) of the object
managed by this factory.Allows for specifying explicit construction arguments, along the lines
ofBeanFactory.getBean(String, Object)
.
For example,
@Autowired
private ObjectProvider<Thing> things;
[...]
Thing newThing = things.getObject(name);
[...]
Spring bean with runtime constructor arguments
You can use a prototype bean along with a BeanFactory
.
@Configuration
public class AppConfig {
@Autowired
Dao dao;
@Bean
@Scope(value = "prototype")
public FixedLengthReport fixedLengthReport(String sourceSystem) {
return new TdctFixedLengthReport(sourceSystem, dao);
}
}
@Scope(value = "prototype")
means that Spring will not instantiate the bean right on start, but will do it later on demand. Now, to customize an instance of the prototype bean, you have to do the following.
@Controller
public class ExampleController{
@Autowired
private BeanFactory beanFactory;
@RequestMapping("/")
public String exampleMethod(){
TdctFixedLengthReport report =
beanFactory.getBean(TdctFixedLengthReport.class, "sourceSystem");
}
}
Note, because your bean cannot be instantiated on start, you must not Autowire your bean directly; otherwise Spring will try to instantiate the bean itself. This usage will cause an error.
@Controller
public class ExampleController{
//next declaration will cause ERROR
@Autowired
private TdctFixedLengthReport report;
}
How to use Spring Prototype Beans with constructor arguments?
My comments:
Option 1:
It is clean and simple. Not every class is required to be declared as a Spring bean. If a class is simple enough that does not use any Spring features (such as @Cache
, @Tranascational
etc.) , it is okay to KISS and not declaring it as a Spring bean and create it manually. The Consumer
just like the Main class to drive the logic ,so I would say SomeDependency
is also required by Consumer
in some sense as it needs them to drive the logic of creating the Processor
.
Option 2:
Agree with you. Not so nice as we need separate call and the extra "initialized" property to make sure Processor are created properly when compared with the option 1 which we simply need to create it through constructor. But Processor
is a Spring bean so that we can apply Spring feature to it very easily.
Do we have alternatives?
My alternative is to use the factory pattern with @Configuration
and @Bean
to have the best of both worlds:
First define a factory:
@Configuration
public class ProcessorFactory {
@Autowired
private final SomeDependency dep1;
@Autowired
private final SomeDependency2 dep2;
@Bean(autowireCandidate = false)
@Scope("prototype")
public Processor createProcessor(String requestData) {
return new Processor(requestData, dep1, dep2);
}
}
Then in the consumer :
@Component
public class Consumer {
@Autowired
private final ProcessorFactory processorFactory;
public void method() {
Processor p = processorFactory.createProcessor("some random per request data");
p.blablbaba();
}
}
Note: @Bean(autowireCandidate = false)
on Processor
@Bean is necessary. Otherwise , Spring will try to find a bean with the String type to create a Processor during start up. Since there is no bean with the String type , it will throw exception. Setting autowireCandidate
to false can disable Spring create it. After all , we will create it by manually from ProcessorFactory
Spring prototype beans with parameters?
To get a prototype bean from another bean while passing arguments to constructor you can use <lookup-method>
(XML Configuration) or @Lookup
(annotation-based configuration).
If you want to get the prototype instance from "unmanaged" code (not from a bean) or you don't want to use the lookup methods, you can achieve the same using org.springframework.beans.factory.BeanFactory.getBean(String beanName, Object...)
.
Answering your second question, difference between a prototype-scope bean and using a simple Java constructor is that the prototype-scope bean still have access to Spring container's features. This includes, but it's not limited to the following: it can have collaborators provided in XML configuration (<property name="someCollaborator" ref="..."/>
) or with annotations (@Resource
, @Autowired
, ...), t can implement Spring-aware interfaces (like ApplicationContextAware
so that the prototype bean itself has access to the container).
Get New Instance Of Prototype Scoped Spring Component Using JavaConfig
Scope 'prototype' means that each time you call beanFactory.getBean()
, you get a fresh instance. And when you inject a dependency (for instance, via @Autowired), Spring (internally) calls that getBean()
only once per injection point.
To call it multiple times, you need a BeanFactory
, or just call that @Bean
-annotated method in your configuration.
An interesting use case of prototype scope with @Configuration is described here: Spring Java Config: how do you create a prototype-scoped @Bean with runtime arguments?
Update
You could implement what you described without the prototype scope.
@Component
public class MyTaskFactoryImpl implements MyTaskFactory {
@Autowired
Field myField;
@Override
public MyTask newTask() {
return new MyTask(myField);
}
}
And in MyTask
:
public class MyTask {
final Field myField;
public MyTask(Field myField) {
this.myField = myField;
}
public void runTask() {...}
}
Then inject MyTaskFactory taskFactory
and use it:
MyTask task = taskFactory.newTask();
task.runTask()
Related Topics
How to Print Formatted Bigdecimal Values
Persistencecontext Entitymanager Injection Nullpointerexception
How to Accept Date Params in a Get Request to Spring MVC Controller
How to Call the Overridden Method of a Superclass
Is There an Upper Bound to Biginteger
How to Remove a Cookie in a Java Servlet
Convert an Integer to an Array of Digits
How Can a Class Have a Member of Its Own Type, Isn't This Infinite Recursion
Embed a Jre in a Windows Executable
Parsing JSON in Java Without Knowing JSON Format
How to Hot-Reload Properties in Java Ee and Spring Boot