jmruc
jmruc

Reputation: 5836

Shared prototype beans in Spring

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

Answers (2)

jmruc
jmruc

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

Tanja
Tanja

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

Related Questions