A logo showing the text blog.marcnuri.com
Español
Home»Java»Spring Bean Scopes: Singleton with Prototypes

Recent Posts

  • MCP Tool Annotations: Adding Metadata and Context to Your AI Tools
  • Fabric8 Kubernetes Client 7.2 is now available!
  • Connecting to an MCP Server from JavaScript using AI SDK
  • Connecting to an MCP Server from JavaScript using LangChain.js
  • The Future of Developer Tools: Adapting to Machine-Based Developers

Categories

  • Artificial Intelligence
  • Front-end
  • Go
  • Industry and business
  • Java
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Tools

Archives

  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • February 2020
  • January 2020
  • December 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

Spring Bean Scopes: Singleton with Prototypes

2018-05-11 in Java tagged Bean / Java / Scopes / SOLID / Spring Framework / Spring Boot / Testing by Marc Nuri | Last updated: 2021-03-27
Versión en Español

Introduction

This is the second post on the series about Spring Bean Scopes. In the previous tutorial we saw that there were issues rising when a Prototype scoped Bean was injected in a Singleton scoped Bean. The main problem is that autowired Prototypes will be injected when the Singleton Bean is instantiated (which happens only once) thus even though they are prototypes in reality they’ll behave as singletons.

The next code highlights this behavior:

1@Test
2public void getAutowiredSample_na_shouldBeSameInstance() {
3  // Given
4  ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
5  ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
6
7  // When
8  Sample autowiredSample1 = singleton1.getAutowiredSample();
9  Sample autowiredSample2 = singleton2.getAutowiredSample();
10
11  // Then
12  assertEquals(singleton1, singleton2);
13  assertEquals(autowiredSample1, autowiredSample2);
14  assertEquals(autowiredSample1.getUuid(), autowiredSample2.getUuid());
15}

In the previous example, although the object requested with getAutowiredSample is defined with a Prototype scoped Bean, the instance of the object returned in both requests is the same.

To overcome this behavior there are several options available.

@Lookup annotation

The @Lookup annotation allows us to annotate getter methods for a specific Bean. The Spring container will override these methods at runtime and redirect them to the BeanFactory performing a regular getBean call, returning whatever bean the getBean method would be returning (Singleton, Prototype, Request, Session…). In this case, as the defined Bean has a Prototype scope it will return a new instance for each Bean request.

The next code snippet shows how to use the @Lookup annotation:

1@Lookup
2public Sample getSampleUsingLookup() {
3  return null;
4}

The problem with this approach is that it will only work with @Component annotated beans instantiated using component scan or autodetection. It won’t work with @Bean annotated beans.

The next test shows the behavior of the @Lookup annotation in a @Component singleton Bean:

1@Test
2public void getSampleUsingLookupInComponent_na_shouldBeDifferent() {
3  // Given
4  SingletonWithLookupPrototype singleton1 = applicationContext.getBean(SingletonWithLookupPrototype.class);
5  SingletonWithLookupPrototype singleton2 = applicationContext.getBean(SingletonWithLookupPrototype.class);
6
7  // When
8  Sample lookedupSample1 = singleton1.getSampleUsingLookup();
9  Sample lookedupSample2 = singleton2.getSampleUsingLookup();
10
11  // Then
12  assertEquals(singleton1, singleton2);
13  assertNotNull(singleton1);
14  assertNotNull(singleton2);
15  assertNotEquals(lookedupSample1, lookedupSample2);
16  assertNotEquals(lookedupSample1.getUuid(), lookedupSample2.getUuid());
17}

As shown in the previous snippet, this is the behavior we are expecting. Two Singleton Beans which are the same instance even if requested twice, return different instances of the Sample class which is defined in a Bean with Prototype scope.

There is a problem with this approach, as already stated, it will only work with Singleton Beans declared using the @Component annotation. If the Bean is declared using an approach similar to the following:

1@Bean
2@Scope(SCOPE_SINGLETON)
3public ClassWithSamplePrototypes singletonWithSamplePrototypes(Sample autowiredSample, ObjectFactory<Sample> sampleObjectFactory) {
4  return new ClassWithSamplePrototypes(autowiredSample, sampleObjectFactory);
5}

The previous test will fail, as the getSampleUsingLoopup() won’t be overridden, thus returning null. We can check this behavior with this modified test:

1@Test
2public void getSampleUsingLookupInRegularBean_na_shouldBeNull() {
3  // Given
4  ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
5  ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
6
7  // When
8  Sample lookedupSample1 = singleton1.getSampleUsingLookup();
9  Sample lookedupSample2 = singleton2.getSampleUsingLookup();
10
11  // Then
12  assertEquals(singleton1, singleton2);
13  assertNull(lookedupSample1);
14  assertNull(lookedupSample2);
15}

Using ApplicationContext

Another approach to overcome the scoped bean injection problem is to inject ApplicationContext into the Singleton scoped bean. Whenever an instance of a Prototype scoped bean is needed, the singleton should request it to the ApplicationContext instance.

We can declare the Singleton scoped class as follows:

1/** ... **/
2public class ClassWithSamplePrototypes implements ApplicationContextAware {
3/** ... **/
4  private ApplicationContext applicationContext;
5
6  @Override
7  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
8    this.applicationContext = applicationContext;
9  }
10/** ... **/
11  public Sample getSampleUsingApplicationContext() {
12    return applicationContext.getBean(Sample.class);
13  }
14/** ... **/
15}

We can test the expected behavior using the following code snippet:

1@Test
2public void getSampleUsingApplicationContext_na_shouldBeDifferent() {
3  // Given
4  ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
5  ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
6
7  // When
8  Sample applicationContextSample1 = singleton1.getSampleUsingApplicationContext();
9  Sample applicationContextSample2 = singleton2.getSampleUsingApplicationContext();
10
11  // Then
12  assertEquals(singleton1, singleton2);
13  assertNotNull(singleton1);
14  assertNotNull(singleton2);
15  assertNotEquals(applicationContextSample1, applicationContextSample2);
16  assertNotEquals(applicationContextSample1.getUuid(), applicationContextSample2.getUuid());
17}

Although this approach works, it clearly violates SOLID by contradicting the inversion of control design principle. Dependencies are requested directly to the Spring container instead of being injected to the class, so the code is tightly coupled to Spring. e.g. there’s no other way to run the previous unit test without using SpringJUnit4ClassRunner.

Spring’s ObjectFactory interface

Finally but, in my opinion, the cleanest way to solve the scoped bean injection problem is to use the Spring’s ObjectFactory interface. With this approach, instead of injecting the Bean itself we inject a factory for the Bean using this interface.

1/** ... **/
2public class ClassWithSamplePrototypes implements ApplicationContextAware {
3/** ... **/
4  private final ObjectFactory<Sample> sampleObjectFactory;
5  @Autowired
6  public ClassWithSamplePrototypes(Sample autowiredSample, ObjectFactory<Sample> sampleObjectFactory) {
7    this.autowiredSample = autowiredSample;
8    this.sampleObjectFactory = sampleObjectFactory;
9  }
10/** ... **/
11  public Sample getSampleUsingObjectFactory() {
12    return sampleObjectFactory.getObject();
13  }
14/** ... **/
15}

Instead of injecting the bean during the Singleton scope class instantiation, we inject an interface for a Bean ObjectFactory for a given type of Bean. Depending on the type and scope configured for the ObjectFactory, the getObject method will return an object instance accordingly.

The next test shows the described expected behavior:

1@Test
2public void getSampleUsingObjectFactory_na_shouldBeDifferent() {
3  // Given
4  ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
5  ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
6
7  // When
8  Sample objectFactorySample1 = singleton1.getSampleUsingObjectFactory();
9  Sample objectFactorySample2 = singleton2.getSampleUsingObjectFactory();
10
11  // Then
12  assertEquals(singleton1, singleton2);
13  assertNotNull(singleton1);
14  assertNotNull(singleton2);
15  assertNotEquals(objectFactorySample1, objectFactorySample2);
16  assertNotEquals(objectFactorySample1.getUuid(), objectFactorySample2.getUuid());
17}

Using ObjectFactory won’t violate any OOP principle as you can always inject it using a custom ObjectFactory implementation:

1new ClassWithSamplePrototypes(new Sample(),
2    () -> new Sample());

Conclusion

This post shows how to solve the scoped bean injection problems that arises when injecting a Prototype scoped Bean into a Singleton scoped Bean. We’ve seen different approaches to solve the problem and the advantages and disadvantages for each of them.

The full source code for this post can be found at GitHub.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Comments in "Spring Bean Scopes: Singleton with Prototypes"

  • Avatar for Sainath
    Sainath
    2020-03-31 10:32
    Hi Marc,

    That's an excellent article and I have followed it and it worked for me.

    Now I have a scenario where I want to access the particular prototype bean through singleton bean or let's say I want to track all prototype beans through singleton bean.
    Is it possible?

    Thanks for your time

Post navigation
Spring Boot: How to change the application port?Spring Bean Scopes: Guide to understand the different Bean scopes
© 2007 - 2025 Marc Nuri