Reputation: 28674
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
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
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
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