Giorgi Moniava
Giorgi Moniava

Reputation: 28674

Issue receiving event on prototype bean when used through interface?

A little bit new with spring. When I instantiate a bean via interface, it doesn't seem to get events, if however, I use actual class implementing the interface, then the event is received. Why is this? Code below.

package javabeans.di;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;


public class HelloWorldImpl  implements HelloWorld,  ApplicationListener<ContextStartedEvent> {

    private String msg;

    public HelloWorldImpl(String s){
        msg = s;
    }

    @Override
    public void printHelloWorld() {

        System.out.println("Hello : " + msg);
    }


    public void onApplicationEvent(ContextStartedEvent event) {
          System.out.println("ContextStartedEvent Received");
    }
}

Here is the calling code:

public static void main(String[] args) {
     ConfigurableApplicationContext  ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);

     // Let us raise a start event.
    ctx.start();           

    HelloWorld obj = (HelloWorld) ctx.getBean("helloWorld");        
    obj.printHelloWorld();

    ctx.stop();
}

Config class:

@Configuration
public class HelloWorldConfig {

   @Bean 
   @Scope("prototype")
   public HelloWorld helloWorld(){
      return new HelloWorldImpl("Hello java beans");
   }



}

The interface:

package javabeans.di;

public interface HelloWorld {

    void printHelloWorld();

}

"ContextStartedEvent Received" never gets shown if the bean has a prototype scope.

NOTE: If I change return type of bean method to HelloWorldImpl in the config class, and also change HelloWorld to HelloWorldImpl inside main (two occurrences - basically on the line where I call getBean), then this works also with prototype beans. Why would that be? Additionally if I create two instances of HelloWorldImpl in main, in a manner described in this paragraph, still the event is received only once (but that might be separate issue).

Upvotes: 7

Views: 810

Answers (3)

M. Deinum
M. Deinum

Reputation: 124760

When using java based configuration what happens is very early in the process the @Configuration classes are read with ASM (they aren't loaded through a class loader yet). Based on that read bytecode Spring creates the bean definitions and proxy based classes.

A @Bean method (regardless where it is) is basically the same as a FactoryBean. It acts more or less in the same way. When the meta data is created it does so by inspecting the method signature and using the return type to create a factory. This return type is basically used for the getObjectType method of a FactoryBean. And this the result of that method is used to determine what the bean supports.

Now when return HelloWorld as a type you get a factory creating beans of that type. When using HelloWorldImpl you will get a factory creating beans of that type. The first doesn't contain the ApplicationListener interface and as such is ignored by spring, the second however does (it is detected at that point of generating the (auto) configuration meta data).

So when using @Configuration with @Bean it is important to be as specific as possible about the return type.

Upvotes: 3

Vale
Vale

Reputation: 1124

Isn't this because the interface itself doesn't have the listening method?
Shouldn't you

package javabeans.di;

public interface HelloWorld extends ApplicationListener<ContextStartedEvent>{

    void printHelloWorld();
    public void onApplicationEvent(ContextStartedEvent event);

}

And then @Override in the implementing class?

Upvotes: 3

T. Neidhart
T. Neidhart

Reputation: 6200

This is just a guess, but the ApplicationListener interface is only available for the concrete implementation class HelloWorldImpl. Thus when spring creates the helloWorld bean it creates a type HelloWorld which does not have the ApplicationListener interface and thus the event will not be propagated to this bean.

Let the HelloWorld interface extend ApplicationListener and you should receive an event.

Upvotes: 2

Related Questions