Reputation: 1010
I was looking for a way to create a non-singleton spring bean which can be "partially" autowired.
@Component
class Example {
private SpringBean1 bean1;
private SpringBean2 bean2;
private String dynamicDependancy;
@Autowired
public Example(SpringBean1 bean1, SpringBean2 bean2, String dynamicDepedency) {
this.bean1 = bean1;
this.bean2 = bean2;
this.dynamicDepedency = dynamicDepedency;
}
}
I want such a thing because sometimes the dependency is known only in runtime. There is a way I have thought of which is creating a kind of a factory that create a static member class, in that way I can test the static member class:
@Component
class ExampleFactory {
private SpringBean1 bean1;
private SpringBean2 bean2;
@Autowired
public ExampleFactory(SpringBean1 bean1, SpringBean2 bean2) {
this.bean1 = bean1;
this.bean2 = bean2;
}
public Example from(String dynamicDependency) {
return new Example(bean1, bean2, dynamicDependency);
}
static class Example {
private SpringBean1 bean1;
private SpringBean2 bean2;
private String dynamicDependancy;
public Example(SpringBean1 bean1, SpringBean2 bean2, String
dynamicDependancy) {
this.bean1 = bean1;
this.bean2 = bean2;
this.dynamicDependancy = dynamicDependancy;
}
}
}
I was looking a bit about the Prototype scope and using getBean(java.lang.String, java.lang.Object) make it harder to use dependency injection. I'd like to know if there's any "Spring way" to do such things.
Thank you.
Update: I have found another solution and post an answer at another post: https://stackoverflow.com/a/52021965/2580829
Upvotes: 1
Views: 6487
Reputation: 39950
Your basic approach using a factory that's injected by Spring that then exposes a method to create Example
instances is how I'd do this, so it's essentially correct. If you want to have Spring take care of doing this transparently, using its modern features, you can use a @Configuration
class in combination with lookup method injection to create instances of Example
on-demand from singleton-scoped beans.
First, the configuration class:
@Configuration
public class DemoConfiguration {
@Autowired IFooBean fooBean;
@Autowired IBarBean barBean;
@Bean()
@Scope("prototype")
Example newExample(String name) {
return new Example(fooBean, barBean, name);
}
}
There should be nothing too surprising here, except maybe the name
parameter to newExample
. You can autowire the dependencies the container can satisfy (fooBean
and barBean
) as I did above, but since instances of configuration classes are created like any other bean by Spring, you can also use any other mechanism: inject an ObjectFactory
or ObjectProvider
into the configuration, have it implement ApplicationContextAware
, or even use lookup method injection for them. This would be useful if you need to avoid fooBean
and barBean
being initialized early as they would if they're autowired into a configuration bean.
Don't forget setting the scope of the factory method to "prototype"
, otherwise Spring will just return the first bean you create using even if you pass in a different value for name
.
The implementation of Example
itself is analogous to the one in your question:
public class Example {
IFooBean fooBean;
IBarBean barBean;
String name;
public Example(IFooBean fooBean, IBarBean barBean, String name) {
System.out.printf("%s(fooBean=%s, barBean=%s, name=%s)\n", this, fooBean, barBean, name);
this.fooBean = fooBean;
this.barBean = barBean;
this.name = name;
}
}
Then, at the point where you actually need the instances of Example
, you use @Lookup
to inject the factory method:
public interface IUsesExample {
void doThing();
}
@Component
public class UsesExample implements IUsesExample {
@Lookup
protected Example getExample(String name) {return null;};
public void doThing() {
System.out.printf("%s.doThing(getExample() = %s)\n", this, getExample("aaa"));
System.out.printf("%s.doThing(getExample() = %s)\n", this, getExample("bbb"));
}
}
To use @Component
and scanning, this has to be a concrete class, which means we need a dummy implementation of getExample()
; Spring will use CGLIB to replace it with a call to the factory method defined in DemoConfiguration
above. Spring will correctly pass parameters from the lookup method to the factory method.
For test purposes I just call getExample()
twice with different values for name
to demonstrate we're getting a different instance with the right things injected into it each time.
Testing this with the following small Spring Boot app:
@SpringBootApplication
public class DemoApplication {
@Autowired IUsesExample usesExample;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@PostConstruct
void run() {
usesExample.doThing();
}
}
gives the following output:
com.example.demo.FooBean@fd46303
com.example.demo.BarBean@6a62689d
com.example.demo.Example@66629f63(fooBean=com.example.demo.FooBean@fd46303, barBean=com.example.demo.BarBean@6a62689d, name=aaa)
com.example.demo.UsesExample$$EnhancerBySpringCGLIB$$68b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@66629f63)
com.example.demo.Example@6b5966e1(fooBean=com.example.demo.FooBean@fd46303, barBean=com.example.demo.BarBean@6a62689d, name=bbb)
com.example.demo.UsesExample$$EnhancerBySpringCGLIB$$68b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@6b5966e1)
That is:
FooBean
gets createdBarBean
gets createdExample
gets created with the above two beans and name
Example
is returned to UseExample
Example
gets created, with the same FooBean
and BarBean
, and name
set to "bbb"
this time.I'm assuming you're familiar with how to set up java-based configuration and component scanning and all the other plumbing the above examples rely on. I used Spring Boot to get the whole shebang in a simple way.
If you're creating Example
s from other prototype-scoped beans there might be a way to pass in the value for the runtime-only dependency through the scope but I have no idea where to even begin answering how to do that, especially without knowing the actual scopes of the beans and how they relate to one another. Either way the above solution seems more straightforward and easy to understand.
Upvotes: 4