0

Disclaimer: I've read follownig heelpful staff about JDK dynamic proxy and CGLIB: https://stackoverflow.com/a/21762454/2674303

I''ve read following interesting article:Injecting Spring Prototype bean into Singleton bean

First case:

Prototype:

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

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

    MessageBuilder() {
        instanceCounter.incrementAndGet();
    }

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

Singleton:

@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();
    }     
}

Test:

@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);
    }

}

Obviously test fails because injection happens only once and actual result will be 1 but we expected 2.

Second case:

Singleton and Test are the same but prototype now looks like this(proxyMode was changed):

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

When we start our test we see that actual result is 6 because of inside createMessage method messageBuilder is accessed 3 times. and createMessage method is invoked twice so 3*2=6.

To explain behaviour author provided following picture:

enter image description here

I can't understand which bean is dependant and why each access to proxy messageBuilder genetares new bean instantiation. Why for first case the situation different ? Could you explain it ? As far I understand it - proxy are crated anyway - using CGLIB or using Dynamic proxy so anyway proxy is injected

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710

2 Answers2

1

If you define a bean with a prototype scope, a new instance is returned by the ApplicationContext when the bean is referenced from the Spring context. In your first example, a new instance of MessageBuilder prototype is created when the MessageService singleton bean is created. However, as the MessageService is constructed only once during the Spring lifecycle (as it is a singleton), it requests only one reference of the MessageBuilder prototype bean to be injected.

In other words, in your first example, the MessageBuilder bean is only instantiated once as it is injected (autowired) into the MessageService once. Method calls performed on the injected prototype bean will not be proxied to a new prototype bean instance afterwards.

By setting the proxyMode to TARGET_CLASS, the ApplicationContext does not directly inject a new prototype bean instance in another bean, but instead injects a proxy of the prototype bean. Thereby, when the singleton bean calls a method from the injected singleton bean, the intermediate proxy references a new prototype bean and calls the method.

More information can be found in the Spring documentation:

If you want to inject (for example) an HTTP request-scoped bean into another bean of a longer-lived scope, you may choose to inject an AOP proxy in place of the scoped bean. That is, you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object.

Michiel
  • 2,914
  • 1
  • 18
  • 27
0

https://stackoverflow.com/a/21762454/2674303 was the discussion about JDK dynamic proxy and CGLIB

article http://dolszewski.com/spring/accessing-prototype-bean-in-singleton/ was about how to inject prototype bean into a singleton bean

By definition of prototype scope, when you set prototype scope for MessageBuilder, you get a different object every time. You can verify ctx.getBean(MessageBuilder.class);

You want the same behavior when it is Autowired to a singleton bean. MessageService is singleton and only initialize once. If you inject a real MessageBuilder object to it, there is no way you get a different MessageBuilder object inside MessageService. So you will need to inject an aop proxy for MessageBuilder object. Spring can handle it correctly internally and gives you different object even though there is only one MessageService.

Abe
  • 310
  • 3
  • 15