Spring Java Config: How to Create a Prototype-Scoped @Bean with Runtime Arguments

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
of BeanFactory.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



Leave a reply



Submit