Reputation: 1480
Say I have the following:
Config
Sets up the guava EventBus bean.
@Configuration
public class AppConfig {
@Bean
public EventBus eventBus() {
return new EventBus(); // guava event bus
}
}
Event listener
Listens and handles to some/all of the events that are put on the event bus. May have several different classes similar to this that subscribe to events in the application.
@Component
public class EventListener {
@Autowired
public EventListener(EventBus eventBus) {
eventBus.register(this); // register this instance with the event bus so it receives any events
}
@Subscribe
public void onTestEvent(TestEvent e) {
System.out.println("Caught event");
}
}
Event Dispatcher
Something that puts events on the event bus.
@Component
public class EventDispatcher {
@Autowired
public EventDispatcher(EventBus eventBus) {
eventBus.post(new TestEvent());
}
}
Main App Entry Point
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
How can I make sure that all of the classes that are listening to events are instantiated before the classes that post them? Ie at the moment it could be possible for the event dispatcher class to be instantiated and post the event before the listener is instantiated, meaning the listener would have missed the events.
I was thinking maybe creating another annotation like @EventListener, and then have spring prioritise all classes with this annotation somehow, but not sure how to implement this or if this is actually a good idea anyway.
Thanks.
Upvotes: 3
Views: 4492
Reputation: 1480
I decided to extend the Guava EventBus
class creating BufferedEventBus
.
This simply buffers any event objects that are posted to it until the application context has finished loading. When this happens it then posts any buffered events. Any future events are posted immediately and not buffered.
BufferedEventBus
package tjenkinson.events;
import java.util.ArrayList;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus;
/**
* A guava event bus that holds back any events that are posted to it until the application
* context has finished loading meaning all eager-loaded beans will have been constructed.
*
* This means any eager-loaded beans that want to listen to events on this event bus will be able
* to and not miss any events that were posted from other beans before they had a chance to listen.
*/
@Service
public class BufferedEventBus extends EventBus implements ApplicationListener<ContextRefreshedEvent> {
private boolean applicationContextLoaded = false;
private final ArrayList<Object> bufferedEvents = new ArrayList<>();
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
synchronized(bufferedEvents) {
if (applicationContextLoaded) {
// context already loaded. maybe it's been refreshed.
return;
}
postBufferedEvents();
applicationContextLoaded = true;
}
}
@Override
public void post(Object event) {
synchronized(bufferedEvents) {
if (applicationContextLoaded) {
super.post(event);
}
else {
bufferedEvents.add(event);
}
}
}
private void postBufferedEvents() {
synchronized(bufferedEvents) {
for (Object event : bufferedEvents) {
super.post(event);
}
bufferedEvents.clear();
}
}
}
I am now simply using this instead of the standard EventBus
throughout my application.
Upvotes: 3