Reputation: 5836
I need to create multiple instances of a spring bean (let's call it MainPrototypeBean
), which I can do with the prototype
scope. It depends on some other beans, and I want to create new instances of them each time the main bean is created. However, there is a shared dependency between some of the beans, let's call it SharedPrototypeBean
. How do I inject the same instance of SharedPrototypeBean
in each of the dependent beans, while also creating a new instance for each MainPrototypeBean
?
I'm looking into implementing a custom scope, but I'm hoping to find a cleaner way. Making any of the beans singletons is not an option, as they need to be isolated between different instances of MainPrototypeBean
.
Here's an example of what I'm trying to do:
@SpringBootApplication
public class DIDemo {
public static void main(String[]args){
ConfigurableApplicationContext context = SpringApplication.run(DIDemo.class, args);
context.getBean(MainPrototypeBean.class);
}
@Component @Scope("prototype") static class SharedPrototypeBean {}
@Component @Scope("prototype") static class FirstPrototypeBean {
@Autowired SharedPrototypeBean shared;
@PostConstruct public void init() {
System.out.println("FirstPrototypeBean.init() with shared " + shared);
}
}
@Component @Scope("prototype") static class SecondPrototypeBean {
@Autowired SharedPrototypeBean shared;
@PostConstruct public void init() {
System.out.println("SecondPrototypeBean.init() with shared " + shared);
}
}
@Component @Scope("prototype") static class MainPrototypeBean {
@Autowired FirstPrototypeBean first;
@Autowired SecondPrototypeBean second;
}
}
And the output of executing it is:
FirstPrototypeBean.init() with shared DIDemo$SharedPrototypeBean@1b84f475
SecondPrototypeBean.init() with shared DIDemo$SharedPrototypeBean@539d019
Upvotes: 3
Views: 1717
Reputation: 5836
After reading the comments and the other answer, I realized that the design is indeed too complex. I made SharedPrototypeBean
, FirstPrototypeBean
and SecondPrototypeBean
regular POJOs, not managed by Spring. I then create all of the objects in a @Bean
annotated method.
@Bean
public MainPrototypeBean mainPrototypeBean() {
Shared shared = new Shared();
First first = new First(shared);
Second second = new Second(shared);
return new MainPrototypeBean(first, second);
}
Upvotes: 0
Reputation: 131
You can use the FactoryBean
for complex construction logic. Implement its abstract subclass AbstractFactoryBean
for creating a MainPrototypeBean
, and inject all three dependent beans into it. You can then wire them together in the createInstance
method.
The FactoryBean implementation:
public class MainFactoryBean extends AbstractFactoryBean<MainPrototypeBean> implements FactoryBean<MainPrototypeBean> {
private FirstPrototypeBean firstPrototype;
private SecondPrototypeBean secondPrototpye;
private SharedPrototypeBean sharedPrototype;
public MainFactoryBean(FirstPrototypeBean firstPrototype, SecondPrototypeBean secondPrototype, SharedPrototypeBean sharedPrototype) {
this.firstPrototype = firstPrototype;
this.secondPrototpye = secondPrototype;
this.sharedPrototype = sharedPrototype;
}
@Override
protected MainPrototypeBean createInstance() throws Exception {
MainPrototypeBean mainPrototype = new MainPrototypeBean();
firstPrototype.setSharedPrototypeBean(sharedPrototype);
secondPrototpye.setSharedPrototypeBean(sharedPrototype);
mainPrototype.first = firstPrototype;
mainPrototype.second = secondPrototpye;
//call post construct methods on first and second prototype beans manually
firstPrototype.init();
secondPrototpye.init();
return mainPrototype;
}
@Override
public Class<?> getObjectType() {
return MainPrototypeBean.class;
}
}
Note: sharedPrototype
is injected after the post-construct phase in the lifecycle of the first and second prototype. So, if you have post-construction logic in these beans that require the sharedPrototype
, you need to manually call the init-method when creating the MainPrototypeBean
.
Your annotation - configuration changes as as a consequence. The sharedPrototype
attributes are no longer autowired (they are set inside FactoryBean), and MainPrototypeBean
is not annotated anymore. Instead you need to create the MainFactoryBean
.
@Configuration
public class JavaConfig {
//method name is the name refers to MainPrototypeBean, not to the factory
@Bean
@Scope("prototype")
public MainFactoryBean mainPrototypeBean(FirstPrototypeBean firstPrototype, SecondPrototypeBean secondPrototype, SharedPrototypeBean sharedPrototype) {
return new MainFactoryBean(firstPrototype, secondPrototype, sharedPrototype);
}
//Annotations are not needed anymore
static class MainPrototypeBean {
FirstPrototypeBean first;
SecondPrototypeBean second;
}
@Component
@Scope("prototype")
static class SharedPrototypeBean {
}
@Component
@Scope("prototype")
static class FirstPrototypeBean {
private SharedPrototypeBean shared;
//no autowiring required
public void setSharedPrototypeBean(SharedPrototypeBean shared) {
this.shared = shared;
}
@PostConstruct
public void init() {//reference to shared will be null in post construction phase
System.out.println("FirstPrototypeBean.init() with shared " + shared);
}
}
@Component
@Scope("prototype")
static class SecondPrototypeBean {
private SharedPrototypeBean shared;
public void setSharedPrototypeBean(SharedPrototypeBean shared) {
this.shared = shared;
}
@PostConstruct
public void init() {
System.out.println("SecondPrototypeBean.init() with shared " + shared);
}
}
}
Upvotes: 1