-
Notifications
You must be signed in to change notification settings - Fork 2
Home
Index
Blackboard is a centralized event handling system, as per the Observer pattern. If the observer pattern is unfamiliar to you, you probably don’t need this (but it would still be good to read up on this useful pattern.)
The need for this library came from a Vaadin project that was (and still is) highly modularized, with very loose coupling. The modules could even be contained within each other, having a deep hierarchy. The modules still needed to communicate with each other, and some modules needed to communicate with a large amount of other modules.
The way events are handled in Vaadin (and many other environments) is with direct communication. Some component tells directly another component that one wants to listen to the other. This makes a hassle if something deep in the component hierarchy needs to inform listeners much higher in the hierarchy about something. Consider the following sketch:
Assume that the select on the left would affect the text on the right. With Vaadin, you’d probably have a Label on the right. Its parent would then be a SplitPanel. The left side would probably be a VerticalLayout, containing a TabSheet, whose first tab contains some kind of Select. To get the ValueChangeEvent go all the way from that Select to the context where the Label is in, you have at least three, probably more, intermediate steps. While passing the event all that way through those layers is doable, it breaks encapsulation and is generally just ugly to look at.
Blackboard doesn’t care about any component hierarchies, since it doesn’t care (or even know) about them. It just knows about Listeners and Events. If you will, the component hierarchy can be seen as a two-dimensional tree (breadth and depth). Blackboard doesn’t exist in those dimensions. It looks at that component hierarchy from the third dimension, and therefore can interact with all components instantly.
While Blackboard got its conception from the needs of a Vaadin project, Blackboard can be used equally well in any project that can utilize Java libraries in JAR form.
Although the observer pattern doesn’t leave room for much interpretation, I think it’s good to make sure we’re on the same page with the terminology.
Often used synonyms for what here is called a listener are observer and subscriber. A listener is an object interested of a certain happening in the system. I.e. it is listening to certain events. In Blackboard, a listener can mean the interface, or any subinterface of, com.github.wolfie.blackboard.Listener
. It can also mean a class that implements (one or many of) the aforementioned interfaces, or it can mean an instance of the aforementioned class(es).
The notifier also has other names, such as publisher and observable. The notifier is an object that makes certain actions, that might be interesting to others. I.e. it is triggering certain events to happen. Blackboard doesn’t have a class or interface for a notifier, since any class that has access to a Blackboard instance can trigger events (“notify”).
The closest synonym to an event is a signal, but it’s not a close match, since a signal has connotations of lower-level languages, or hardware. An event is technically a high-level object, that wraps data into a common interface. Conceptually, an event is a message from a notifier to a listener, that contains all the needed information for the listener to know what the notifier did. The com.github.wolfie.blackboard.Event
interface.
So, how does Blackboard function, then?
Blackboard does not provide any static access methods, by design, and it is up to the client code to handle this. This is to enable the client application to define the scope and lifetime of the Blackboard. You still probably want to access Blackboard in a static way within your application. You could define it as a static field in a central application class, but that is highly discouraged, and suitable only for small, one-off hacky applications.
In general, the scope of a Blackboard should be as small as possible, since it’s very easy to leak memory with Blackboard (more on that in a while). Placing Blackboard as low in the object hierarchy as possible will allow Blackboard to be garbage collected more effectively.
If you happen to use Vaadin and need Blackboard to pass events from one Component to another, the ideal situation would be to use the ThreadLocal pattern:
public class BlackboardThreadLocal extends Application implements TransactionListener {
private static ThreadLocal<Blackboard> BLACKBOARD = new ThreadLocal<Blackboard>(); private final Blackboard blackboardInstance = new Blackboard();
@Override public void init() { // Needs to be set manually for the first transaction BLACKBOARD.set(blackboardInstance);
// ...other initialization logic...
getContext().addTransactionListener(this); }
public void transactionStart(final Application application, final Object transactionData) { if (application == this) { BLACKBOARD.set(blackboardInstance); } }
public void transactionEnd(final Application application, final Object transactionData) { if (application == this) { // to avoid keeping an Application hanging, and mitigate the // possibility of user session crosstalk BLACKBOARD.set(null); } }
public static Blackboard getBlackboard() { return BLACKBOARD.get(); } }
This enables you to call BlackboardThreadLocal.getBlackboard()
anywhere in your Application to access the Blackboard. You may need to adapt the code above to be suitable for your particular environment.
As already mentioned in the glossary, Blackboard comes with the interfaces Listener and Event. These are your base ingredients. Their relationship should be one-to-one. Assuming you want to have an event announcing your application that new year has come. The relationship would then mean that you would probably have one NewYearListener
interface and one non-abstract NewYearEvent
class. You could then have several classes that implement NewYearListener
.
Creating the Listener interface correctly is the most important step. Let’s look at a simple one:
public interface FooListener extends Listener {
@ListenerMethod
void listenerMethod(FooEvent event);
}
The Listener
this interface extends is the Blackboard’s own listener interface, com.github.wolfie.blackboard.Listener
. All Listeners to be used with Blackboard must be of a sub type of this. The annotation is com.github.wolfie.blackboard.annotation.ListenerMethod
, and this tells Blackboard that this is the method that will be called whenever the event is fired. Each Listener must have one, and only one, method with this annotation. Also, the parameter of the method must be the one that is registered with the Blackboard.register()
-method (we’ll come back to that in a moment) and there may not be any other parameters. What the method returns doesn’t matter, but Blackboard will ignore whatever is returned.
Blackboard doesn’t care how you construct your Events. It’s worth to bear in mind that an Event is meant to transport information from one part of the application to another one, so include enough information in the event. But be cautious of including too much information, too. Events are about events happening in an application, not data transfer per se.
To continue with the new year example, here’s how one might implement the triplet for those.
public class NewYearEvent extends Event { private final int theNewYear;
public NewYearEvent(final int theNewYear) { this.theNewYear = theNewYear; }
public int getNewYear() { return theNewYear; } }
public interface NewYearListener extends Listener { @ListenerMethod void newYearArrived(NewYearEvent event); }
For Blackboard to know which Listeners are interested in which Events, you need to register the pair:
blackboard.register(NewYearListener.class, NewYearEvent.class);
This needs to be done only once, when you initialize your application. After this line, whenever a NewYearEvent
is fired, all implementations of NewYearListener
that have been added to Blackboard as listeners will be fired.
Each listener needs to be added to Blackboard, for it to be aware of all that are interested in events:
MyNewYearListener listener = new MyNewYearListener();
blackboard.addListener(listener);
From now on, the listener
instance will receive any and all events that Blackboard knows it is interested of. This is determined by which interfaces it implements. Assuming that MyNewYearListener
implements the NewYearListener
interface (as above), it will receive all events of type NewYearEvent
. If MyNewYearListener
implements several Listener-interfaces, the object will immediately start listening also to all other events, suitable for each of the Listener interfaces.
Actually firing the events, i.e. telling the Listeners that something interesting has happened, is very easy. Just call:
blackboard.fire(newYearEvent);
The newYearEvent
argument is an event that you have constructed from a suitable Event
class. For example, it could be a new NewYearEvent(2011);
.
Blackboard is not a perfect system, and shouldn’t be used for absolutely everything. Read the following with thought.
A thing to keep in mind is the scope of Blackboard events: there is none.
It is important to keep your events as granular as possible, since all listeners matching a certain event will be called, regardless of where they logically are in your application, as long as they are registered to the same Blackboard instance. So, if you have only one static and global instance of Blackboard, the scope of that Blackboard is the whole application.
So, for example a SaveEvent
wouldn’t be a good idea, unless you have exactly one thing that you save in your application, since you can’t reuse that event for many things, without ugly code. If you have several things that you want to save, you probably want to use different events and listeners for each thing you save, e.g. SaveUserEvent
and SavePrivilegesEvent
. On the other hand, these things often are handled within a small scope, and aren’t cross-cutting for your whole application, so those shouldn’t even be handled by a global Blackboard, but either manually, or possibly a lower-level Blackboard instance.
I want to emphasize that Blackboard is intended only for events whose listeners are scattered around, without proper and direct connections. If they are contained within a unit small enough, you might handle the events locally with your own, directly connected, addListener()
methods.
Java makes it hard to manage memory. In Blackboard’s situation, this is not a good thing. You are forced to manage the memory yourself.
If you keep your Blackboard instances small enough, and make sure that in a long-lived application the instance gets cleaned up properly (via the removeListener()
-method, or simply creating a whole new Blackboard instance), memory leaking will be kept at a minimum.
To take Vaadin as an example, one Blackboard instance per user session (i.e. Application instance) should be okay, since they decay eventually. This, of course, is with the assumption that your listener objects aren’t huge.
In essence, if memory leaking becomes a problem, profile your application to see where it’s happening. It’s in your hands. Blackboard’s memory overhead is quite small.
- Git repository – includes a quick example application
- Directory entry
- Issues