Accessing prototype bean in singleton

It’s not sheer coincidence or a random choice that the default scope in Spring is the singleton. The vast majority of business logic in our applications is placed in stateless objects, which thanks to the lack of state can be safely reused across the whole code base. However, sometimes some data needs to be stored between sequential calls to object’s methods. In that case we need to have more control over the life cycle of such object. Spring provides us with several scopes which live shorter than singletons and perfectly fit for short term data storage. The problem appears when you try to inject such scoped bean into a singleton. This post describes the possible options to access shorter living object inside these that reside in the container for longer periods. Although all examples will focus on a prototype inside a singleton, presented information applies to other scopes as well.

Advertisement

1. Is prototype the right choice?

Before falling into the pitfalls of connection between prototype and singleton beans, you should decide whether you actually need such relation. The fact that you use dependency injection in your project does not mean you should avoid the new keyword like the plague. There is nothing wrong in instantiating an object on your own as long as testability isn’t compromised.

The most common need for the aforementioned relation is when a prototype bean has a dependency on another bean already managed by the dependency injection container and you do not want to set these dependencies manually. The following diagram represents this situation.

singleton-prototype relation diagram

No matter how many MessageBuilders are created we expect they will always get the reference to the same ContentProcessor, so this is rather a pretty straightforward connection. The relation between MessageBuilder and MessageService is more interesting. Since only one MessageService is created by the container, it will have access to only one instance of MessageBuilder that is injected when that singleton bean is created. If another bean depends on the prototype, it will get a different instance. But what if we want a new instance of the prototype on every call to a particular method of the MessageService bean? Let’s examine the options that we have.

2. Playground code

Our goal is to use a new instance of the prototype bean on every call to singleton’s method. We are going to verify how many prototypes have been created by the container. Therefore, we add a static instance counter that is incremented by the constructor of the class.

@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class MessageBuilder {

    private static final AtomicInteger instanceCounter = new AtomicInteger(0);

    private final ContentProcessor contentProcessor;

    MessageBuilder(ContentProcessor contentProcessor) {
        this.contentProcessor = contentProcessor;
        instanceCounter.incrementAndGet();
    }

    static int getInstanceCounter() {
        return instanceCounter.get();
    }

}

The main task of the class is to build some immutable Message object. The build process relies on another bean managed by the container, which is injected into the builder class. In the real application you would probably put such builder as a static member of the Message class (Spring handles them without any problem), but let’s do not get distracted by details.

class MessageBuilder {

    //...
    private String content;
    private String receiver;

    MessageBuilder withContent(String content) {
        this.content = contentProcessor.process(content);
        return this;
    }

    MessageBuilder withReceiver(String receiver) {
        this.receiver = receiver;
        return this;
    }

    Message build() {
        return new Message(content, receiver);
    }

}

The MesssageService class is a simple singleton with only one method, which utilizes the prototype by calling all three of its methods.

@Service
class MessageService {

    private final MessageBuilder messageBuilder;

    MessageService(MessageBuilder messageBuilder) {
        this.messageBuilder = messageBuilder;
    }

    Message createMessage(String content, String receiver) {
        return messageBuilder
                .withContent(content)
                .withReceiver(receiver)
                .build();
    }

}

As a reminder, since Spring 4.3 you do not have to put the @Autowired annotation on a constructor if there is only one in a class. The framework will use it automatically.

Finally, we create a test that will confirm that on every call to the singleton, a new prototype instance is created. We call the singleton twice so we expect two objects.

@RunWith(SpringRunner.class)
@SpringBootTest
public class MessageServiceTest {

    @Autowired
    private MessageService messageService;

    @Test
    public void shouldCreateTwoBuilders() throws Exception {
        //when
        messageService.createMessage("text", "alice");
        messageService.createMessage("msg", "bob");
        //then
        int prototypeCounter = MessageBuilder.getInstanceCounter();
        assertEquals("Wrong number of instances", 2, prototypeCounter);
    }

}

Let’s run the test to confirm our assumption from the previous paragraph that only one prototype will be created.

plain test result

3. Proxy mode to the rescue?

The problem of referencing short living beans from these which live longer can be handled using dynamically generated proxy objects. The idea is very simple. Instead of the reference to the real object the dependent bean gets a reference to the proxy object, which has exactly the same interface as the object it mimics. When a method on the proxy object is called, the proxy decides where that call should be propagated and if needed creates a new instance of proxied bean or reuse the existing one.

proxy diagram

By default, Spring doesn’t create a proxy object for a bean and uses the reference to the real object when injection occurs. We can mark a bean for proxy creation using the proxyMode property of the @Scope annotation. Available values can be found in the ScopedProxyMode enumeration. Let’s leave the difference between those values for another time and believe the following change of MesssageBuilder class enables the proxy for the bean.

@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
       proxyMode = ScopedProxyMode.TARGET_CLASS)
class MessageBuilder {
  // ...
}

Let’s run our test again and see if it fixes our use case.

proxy test result

Ups… We did not get one instance, as without the proxy, but as many as six. Why that happened? As we already know, the proxy decides when a new instance of the proxied object should be created. Since our bean is marked with the prototype scope, the new instance is created on a call to its every method. In our test we call the createMessage() method on the MessageService object twice and this in turn executes three methods of the proxied MessageBuilder. Two times three gives us six in total and that is what our test proved.

However, that is not what we wanted and it is even worse. Not only got we more instance than we expected, but also the final result is different than wanted because each instance has a separate state, but the createMessage() method relies on the state that is stored between sequential calls on the builder. With the given setup, the result of the method is the output of the build() method on a newly created MessageBuilder object. Therefore, setting a proxy mode is a dead end for our solution.

4. Creating prototype on demand

Although the proxy mode might be useful in some cases, it will not help us to achieve our goal. We need more control over initialization of the prototype. Fortunately, Spring doesn’t leave us without help thanks to the ObjectFactory class, which is a part of the framework almost from the beginning.

As you may guess from the name, ObjectFactory produces on demand objects of the given type managed by the Spring context. You can inject the factory to your bean just like any other managed class. The type produced by the factory is defined using a generic type parameter. ObjectFactory declares only one method called getObject(), which depending on the scope of produced bean can either return a new instance or an already created shared one. Let’s update the MessageService singleton to utilize the factory.

@Service
class MessageService {

    private final ObjectFactory<MessageBuilder> messageBuilder;

    MessageService(ObjectFactory<MessageBuilder> messageBuilder) {
        this.messageBuilder = messageBuilder;
    }

    Message createMessage(String content, String receiver) {
        return messageBuilder.getObject()
                .withContent(content)
                .withReceiver(receiver)
                .build();
    }

}

From now on the exact moment when the prototype is created can be controlled.  We can verify the expected result with our integration test and check if this is the solution that we’ve been looking for.

factory test result

5. What about ApplicationContext?

As an alternative, you can inject the whole ApplicationContext into the singleton bean and also get the prototype bean on demand using the getBean() method. From the technical perspective there will be no difference in the result, but injecting ObjectFactory is more expressive and vividly describes the real dependencies of our bean. Any reader of that class (including future you) will most likely benefit from the clarity and transparency.

6. ObjectFactory in unit test

Once you start using ObjectFactory in your beans, sooner or later you will encounter a unit test where the tested class depends on a factory that you might want to stub. The ObjectFactory interface has been annotated with @FunctionalInterface in Spring 5, but even in previous versions the contract of the functional interface is fulfilled, therefore we can use lambdas to write a concise implementation of the interface, which is very convenient in unit tests. The following example presents how easily the factory can be stubbed in the dependent class so that it returns a fixed object.

@Test
public void shouldStubFactory() throws Exception {
    MessageService sut = new MessageService(() -> 
            new MessageBuilder(mock(ContentProcessor.class))
    );
    //...
}

7. A few words of conclusion

The Spring framework has been growing since 2002 and keeping up with everything it includes is almost impossible. This post did not touch upon any recently added goodies and it is rather a journey into the past, but hopefully it will be useful for the readers who are interested in broadening the knowledge about the framework just like you. If something bothers you and there is any question in your head regarding the described topic, please share it in a comment so that we can analyze it together.

Facebooktwittergoogle_plusredditlinkedinmail
Advertisement