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:
- a
FooBean gets created
- a
BarBean gets created
- an
Example gets created with the above two beans and name
- this
Example is returned to UseExample
- an different
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 Examples 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.