30
votes

I am trying to inject a prototype bean in a singleton bean such that every new call to a singleton bean method has a new instance of the prototype bean.

Consider a singleton bean as below:

    @Component
    public class SingletonBean {
       @Autowired 
       private PrototypeBean prototypeBean;

       public void doSomething() {
         prototypeBean.setX(1);
         prototypeBean.display();
       }
    }

I expect that every time the doSomething() method is called, a new PrototypeBean instance is utilized.

Below is the prototype bean:

     @Component 
     @Scope(value="prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
     public class PrototypeBean {
        Integer x;

        void setX(Integer x) {
         this.x = x;
        }

        void display() {
          System.out.println(x);
        }
    }

What seems to be happening is that spring is being overeager in handing over a new instance of PrototypeBean in the doSomething() method. That is, the 2 lines of code in the doSomething() method are creating a new instance of prototypeBean in each line.

And so in the 2nd line - prototypeBean.display() prints NULL.

What is missing in configuration for this injection?

5
what is the use-case to use a proxied prototype bean?Francisco Spaeth
@FranciscoSpaeth: I have multiple methods in the singleton bean that utilize the prototype bean methods. The prototype bean is stateful and so I need to grab a fresh copy every time a singleton bean method is called.Angad

5 Answers

39
votes

From Spring documentation:

You do not need to use the <aop:scoped-proxy/> in conjunction with beans that are scoped as singletons or prototypes. If you try to create a scoped proxy for a singleton bean, the BeanCreationException is raised.

It seems the documentation has changed a bit for version 3.2 documentation where you can find this sentence:

You do not need to use the <aop:scoped-proxy/> in conjunction with beans that are scoped as singletons or prototypes.

It seems that its not expected you use a proxied prototype bean, as each time it is requested to the BeanFactory it will create a new instance of it.


In order to have a kind of factory for your prototype bean you could use an ObjectFactory as follows:

@Component
public class SingletonBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeFactory;

    public void doSomething() {
        PrototypeBean prototypeBean = prototypeFactory.getObject();
        prototypeBean.setX(1);
        prototypeBean.display();
    }
}

and your prototype bean would be declared as follows:

@Component 
@Scope(value="prototype")
public class PrototypeBean {
    // ...
}
17
votes

Singleton bean is created only once so the prototype bean which is injected also will be created once at the instantiation of singleton bean.The same instance of prototype bean will be used for every request.

If new instance of prototype bean will be created for each request at runtime ,the below method Injection can be used

Example

public class Singleton {
    private Prototype prototype;

    public Singleton(Prototype prototype) {
        this.prototype = prototype;
    }

    public void doSomething() {
         prototype.foo();
    }

    public void doSomethingElse() {
        prototype.bar();
    }
}

public abstract class Singleton {
    protected abstract Prototype createPrototype();

    public void doSomething() {
        createPrototype().foo();
    }

    public void doSomethingElse() {
        createPrototype().bar();
    }
}


<bean id="prototype" class="ch.frankel.blog.Prototype" scope="prototype" />
<bean id="singleton" class="sample.MySingleton">
   <lookup-method name="createPrototype" bean="prototype" />
</bean>
1
votes

Right way to achieve it - use lookup method injection and everywhere where you used beans use lookup method invocation (detailed answer)

0
votes

Spring wires up your beans in a pretty straight forward way. I'm working in a large commercial application, and I inserted the following code snippets to verify the load order.

1) All of your singleton bean class structures are initially loaded by Spring (as long as Spring is aware of them via annotations and/or xml). This only ever happens once. You can test this by logging or printing in a static block:

    static {
        log.info("#### classNameHere loaded"); //or println if no log setup
    }

2) Spring creates all singleton instances that it is aware of (but not prototypes! Prototype instances WILL be created IF they are referenced inside a singleton bean - there class structures are of course loaded first). You can test this by adding this method to each class:

    @PostConstruct
    public void methodHitAfterClassInstantiation() {
        LOGGER.info("#### instance of classNameHere");
    }

So in your example, the class structures of SingletonBean is loaded when Spring starts up. A new instance of SingletonBean is created. And because PrototypeBean is Autowired inside of SingletonBean, its class structure is loaded and an instance of it is created. Now, if there was another bean, say AnotherSingletonBean, with an Autowired PrototypeBean inside of it, then a DIFFERENT instance of PrototypeBean would be created (no need to load the class structure again). So there is only ever 1 SingletonBean, and inside of it is a PrototypeBean, which will always point to the same bean. Because of this, singletons should always be stateless, as all of your other beans that use a singleton will be pointing at the same object. But you CAN maintain state in a prototype bean, because wherever you create a new reference, you will be pointing at another bean object. http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-scopes-prototype

0
votes

Since Spring 4.1 you can use annotation @Lookup

@Lookup
    public PrototypeBean getPrototypeBean() {
        return null;
    }

Every time you will call method getPrototypeBean() - you will receive new prototype bean instance. Don't worry about empty method realization: Spring will override it for you.

Read more in official documentation.