1. Introduction
In this article, I will go through a few different ways to solve an interesting dependency injection problem that can arise while using Spring. The specific use case that we will discuss is injecting a prototype scoped bean into a singleton scoped bean.
I have written a few other articles on Spring Framework that you might be interested in.
- Writing Unit tests using Spring Framework (No Mockito)
- Implementing Bridge Pattern using Spring Framework
- Implementing Validators in Spring Framework using @Inject
2. Content
The default scope of a bean in Spring is singleton. It means that Spring will create one instance in its lifetime. Any request to bean with a matching id or ids results in the same instance.
The prototype scope means any request to bean with a matching id or ids results in creating a new bean.
Why would we really run into the problem when injecting a prototype bean into a singleton bean class? This is because the Singleton bean is initialized only once, at the start of Application context. Once the singleton bean is created, including its dependencies, we cannot to get any other beans into the class using injection. Problem arises when the singleton bean depends on the prototype bean.
Let us now discuss a few different ways to inject prototype beans in a singleton bean.
For simplicity’s sake, we will define our class as SingletonBean with singleton scope and PrototypeBean as prototype scope.
We will just use constructor injection. We will not use field or setter injection.
3. Class Structure for SingletonBean and PrototypeBean.
I defined the SingletonBean class :
import javax.inject.Inject; import javax.inject.Named; @Named public class SingletonBean { private final PrototypeBean prototypeBean; @Inject public SingletonBean(PrototypeBean prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.prototypeBeanMethod(); } }
I defined PrototypeBean class :
import javax.inject.Named; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; @Named @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean { public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
Testing classes.
BeansTest will make sure that prototype injection works in the correct manner.
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.justamonad.tutorials.injection.beans.SingletonBean; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { TestConfig.class }) public class BeansTest { @Inject private SingletonBean singletonBean; @Test public void beanTest() { singletonBean.singletonBeanMethod(); singletonBean.singletonBeanMethod(); singletonBean.singletonBeanMethod(); } }
In the below output, see how PrototypeBean returns the same hashCode for two different invocations. It should be a different hashCode, as it must create different objects. Certainly, in our code, that doesn’t happen. We will fix that.
Running the beanTest test yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 2145420209 Called prototypeBeanMethod. PrototypeBean hashCode is 600017090 Called singletonBeanMethod. SingletonBean hashCode is 2145420209 Called prototypeBeanMethod. PrototypeBean hashCode is 600017090
TestConfig class is used to scan all the classes marked for injection using ComponentScan.
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan({ "com.justamonad" }) public class TestConfig { }
4. Using javax.inject.Provider<T> and @Scope Bean
This is the simplest way to inject prototype beans in a singleton bean class. Provider<T> interface is part of the JSR-330 Spec. The definition of this interface is for any call to the get() method of this interface, it will return an instance of T.
In Spring, for each invocation of get() method, Spring IOC container will return a new instance.
We need no changes in PrototypeBean class. SingletonBean class needs to change a little.
import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; @Named public class SingletonBean { private final Provider<PrototypeBean> prototypeBean; @Inject public SingletonBean(Provider<PrototypeBean> prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.get().prototypeBeanMethod(); } }
Now we have two different instances of PrototypeBean because we called the get() method of Provider<PrototypeBean> twice using tests. Running the beanTest test yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 939254952 Called prototypeBeanMethod. PrototypeBean hashCode is 268599241 Called singletonBeanMethod. SingletonBean hashCode is 939254952 Called prototypeBeanMethod. PrototypeBean hashCode is 1736293769
This is the recommended approach if you are using javax annotations. If you are using Spring annotations all over the place, then you should use ObjectFactory. We discuss that in a minute.
5. Using javax.inject.Provider<T> and @Configuration
There are chances you may not be annotating the PrototypeBean class as @Named or @Component. But defining it in Configuration class.
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class ApplicationConfiguration { @Bean @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public PrototypeBean prototypeBean() { return new PrototypeBean(); } }
The PrototypeBean class would look like this.
public class PrototypeBean { public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
In this class, the SingletonBean class wouldn’t change at all. You can just inject Provider<PrototypeBean> and you are ready.
import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; @Named public class SingletonBean { private final Provider<PrototypeBean> prototypeBean; @Inject public SingletonBean(Provider<PrototypeBean> prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.get().prototypeBeanMethod(); } }
That’s all for using Provider<T> interface.
6. Using Spring ObjectFactory<T> and @Scope Bean
If you are using Spring’s annotations in your project like @Autowired, @Component then it would be prudent to use ObjectFactory<T> interface.
org.springframework.beans.factory.ObjectFactory<T> interface is used to return a new instance of type T on each invocation.
The only difference between the previous approach of using Provider<T> and ObjectFactory<T> is you need to inject ObjectFactory<T>. And that’s it.
PrototypeBean class needs to be annotated with @Named and @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE). Or we can define it as @Bean and @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) in ApplicationConfiguration class as described earlier.
PrototypeBean class :
@Named @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean { public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
SingletonBean class :
import javax.inject.Inject; import javax.inject.Named; import org.springframework.beans.factory.ObjectFactory; @Named public class SingletonBean { private final ObjectFactory<PrototypeBean> prototypeBean; @Inject public SingletonBean( ObjectFactory<PrototypeBean> prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.getObject().prototypeBeanMethod(); } }
Running the beansTest() yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 1215029765 Called prototypeBeanMethod. PrototypeBean hashCode is 5255258 Called singletonBeanMethod. SingletonBean hashCode is 1215029765 Called prototypeBeanMethod. PrototypeBean hashCode is 1834031967
7. Using proxyMode = ScopedProxyMode.TARGET_CLASS
One way to inject prototype bean into a singleton bean is to create a proxy of the prototype bean class. The proxy will be created just once and will be returned whenever the getBean() method of BeanFactory interface is called. When you invoke a method on this proxy, based on the scope of this bean it will either create a new instance or reuse the existing one. As the scope is set to prototype it will create a new instance every time.
PrototypeBean class needs to have a new parameter in @Scope annotation.
import javax.inject.Named; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; @Named @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS) public class PrototypeBean { public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
Singleton class can now directly inject PrototypeBean.
import javax.inject.Inject; import javax.inject.Named; @Named public class SingletonBean { private final PrototypeBean prototypeBean; @Inject public SingletonBean(PrototypeBean prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.prototypeBeanMethod(); } }
Running the beansTest() yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 1017792343 Called prototypeBeanMethod. PrototypeBean hashCode is 280223635 Called singletonBeanMethod. SingletonBean hashCode is 1017792343 Called prototypeBeanMethod. PrototypeBean hashCode is 245530164
I would discourage you from using this method. Nothing wrong with it but you need to understand what does class-based proxy and JDK dynamic proxy mean and how it works.
8. Using ApplicationContext
This is the worst option of all. This method describes how you should not inject prototype beans into a singleton bean. I will still post how to use this but try to refrain from this solution.
Why not use this? Because we are asking for dependencies from IOC container, i.e. ApplicationContext directly. We shouldn’t be directly asking the container to give us dependency. We should instead let the container figure out the details and inject proper dependencies.
You can implement the ApplicationContextAware interface of Spring, which sets the ApplicationContext. Now you can access the Prototype scoped bean using Application by getting Bean directly out of ApplicationContext.
import javax.inject.Named; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @Named public class SingletonBeanApplicationContextAware implements ApplicationContextAware { private ApplicationContext applicationContext; public PrototypeBean getPrototypeBean() { return applicationContext.getBean(PrototypeBean.class); } @Override public void setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
Now SingletonBean class can work with SingletonBeanApplicationContextAware and get the PrototypeBean.
import javax.inject.Inject; import javax.inject.Named; @Named public class SingletonBean { private final SingletonBeanApplicationContextAware appContextAware; @Inject public SingletonBean( SingletonBeanApplicationContextAware appContextAware) { this.appContextAware = appContextAware; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); appContextAware.getPrototypeBean().prototypeBeanMethod(); } }
PrototypeBean class needs to be annotated with @Named and @Scope.
import javax.inject.Named; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; @Named @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean { public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
Running the beansTest() yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 795273218 Called prototypeBeanMethod. PrototypeBean hashCode is 846718105 Called singletonBeanMethod. SingletonBean hashCode is 795273218 Called prototypeBeanMethod. PrototypeBean hashCode is 1482166692
9. Using @Configuration and Supplier interface
This approach is interesting as it exploits the nature of laziness of Functional interfaces.
First, let us create a PrototypeBean class.
public class PrototypeBean { public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
Now let us create a Supplier of PrototypeBean in Configuration class.
import java.util.function.Supplier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ApplicationConfiguration { @Bean public Supplier<PrototypeBean> prototypeBean() { return PrototypeBean::new; } }
Inject Supplier<PrototypeBean> in SingletonBean class.
import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Named; @Named public class SingletonBean { private final Supplier<PrototypeBean> prototypeBean; @Inject public SingletonBean(Supplier<PrototypeBean> prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.get().prototypeBeanMethod(); prototypeBean.get().prototypeBeanMethod(); } }
Running the beanTest test yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 1418315639 Called prototypeBeanMethod. PrototypeBean hashCode is 913746983 Called prototypeBeanMethod. PrototypeBean hashCode is 1660451908 Called singletonBeanMethod. SingletonBean hashCode is 1418315639 Called prototypeBeanMethod. PrototypeBean hashCode is 1224064486 Called prototypeBeanMethod. PrototypeBean hashCode is 1381857776
10. Using @Configuration and Function interface
If the PrototypeBean class has any constructor arguments, then we can use the Function interface. We cannot use the Supplier interface because it doesn’t allow any parameters.
Let us first write the PrototypeBean class.
public class PrototypeBean { private String str; public PrototypeBean(String str) { this.str = str; } public void prototypeBeanMethod() { System.out.println("Called prototypeBeanMethod."); System.out.println("PrototypeBean hashCode is " + this.hashCode()); } }
Let us write a Function<String, PrototypeBean> which accepts a String as input and returns a new PrototypeBean object.
Using Lambda expression :
Function<String, PrototypeBean> proto = (String str) -> new PrototypeBean(str);
Using Method reference :
Function<String, PrototypeBean> proto = PrototypeBean::new;
Writing Configuration class using Function and method reference.
import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ApplicationConfiguration { @Bean public Function<String, PrototypeBean> prototypeBean() { return PrototypeBean::new; } }
Let us now Inject Function in our SingletonBean class.
import java.util.function.Function; import javax.inject.Inject; import javax.inject.Named; @Named public class SingletonBean { private final Function<String, PrototypeBean> prototypeBean; @Inject public SingletonBean( Function<String, PrototypeBean> prototypeBean) { this.prototypeBean = prototypeBean; } public void singletonBeanMethod() { System.out.println("Called singletonBeanMethod."); System.out.println("SingletonBean hashCode is " + this.hashCode()); prototypeBean.apply("args1").prototypeBeanMethod(); prototypeBean.apply("args2").prototypeBeanMethod(); } }
Running the beanTest test yields below output :
Called singletonBeanMethod. SingletonBean hashCode is 1381857776 Called prototypeBeanMethod. PrototypeBean hashCode is 1363800072 Called prototypeBeanMethod. PrototypeBean hashCode is 1886567481 Called singletonBeanMethod. SingletonBean hashCode is 1381857776 Called prototypeBeanMethod. PrototypeBean hashCode is 361571676 Called prototypeBeanMethod. PrototypeBean hashCode is 749100260
11. Conclusion
In this article, we discussed several ways to inject prototype scoped bean in a singleton bean. I would highly recommend you to follow the approach using Provider<T> or ObjectFactory<T>.