Using Spring's Java Config, I need to acquire/instantiate a prototype-scoped bean with constructor arguments that are only obtainable at runtime. Consider the following code example (simplified for brevity):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
where the Thing class is defined as follows:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Notice name
is final
: it can only be supplied via a constructor, and guarantees immutability. The other dependencies are implementation-specific dependencies of the Thing
class, and shouldn't be known to (tightly coupled to) the request handler implementation.
This code works perfectly well with Spring XML config, for example:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
How do I achieve the same thing with Java config? The following does not work using Spring 3.x:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Now, I could create a Factory, e.g.:
public interface ThingFactory {
public Thing createThing(String name);
}
But that defeats the entire point of using Spring to replace the ServiceLocator and Factory design pattern, which would be ideal for this use case.
If Spring Java Config could do this, I would be able to avoid:
- defining a Factory interface
- defining a Factory implementation
- writing tests for the Factory implementation
That's a ton of work (relatively speaking) for something so trivial that Spring already supports via XML config.
You can achieve a similar effect just by using an inner class:
With Spring > 4.0 and Java 8 you can do this more type-safely:
Usage:
So now you can get your bean at runtime. This is a factory pattern of course, but you can save some time on writing specific class like
ThingFactory
(however you will have to write custom@FunctionalInterface
to pass more than two parameters).In a
@Configuration
class, a@Bean
method like sois 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 itsBeanFactory#getBean(String name, Object... args)
method which statesIn 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 is at least true for Spring versions 4+.
Since Spring 4.3, there is new way to do it, which was sewed for that issue.
ObjectProvider - It enables you just to add it as a dependency to your "argumented" Prototype scoped bean and to instantiate it using the argument
Here is a simple example of how to use it:
This will of course print hello string when calling usePrototype.
UPDATED per comment
First, I'm not sure why you say "this does not work" for something that works just fine in Spring 3.x. I suspect something must be wrong in your configuration somewhere.
This works:
-- Config File:
-- Test File to execute:
Using Spring 3.2.8 and Java 7, gives this output:
So the 'Singleton' Bean is requested twice. However as we would expect, Spring only creates it once. The second time it sees that it has that bean and just returns the existing object. The constructor (@Bean method) is not invoked a second time. In deference to this, when the 'Prototype' Bean is requested from the same context object twice we see that the reference changes in the output AND that the constructor (@Bean method) IS invoked twice.
So then the question is how to inject a singleton into a prototype. The configuration class above shows how to do that too! You should pass all such references into the constructor. This will allow the created class to be a pure POJO as well as making the contained reference objects immutable as they should be. So the transfer service might look something like:
If you write Unit Tests you will be ever so happy you created the classes this without all the @Autowired. If you do need autowired components keep those local to the java config files.
This will call the method below in the BeanFactory. Note in the description how this is intended for your exact use case.