views:

1348

answers:

6

While simple, interface-driven event notification frameworks in Java have been around since pre-Cambrian times (e.g. java.beans.PropertyChangeSupport), it is becoming increasingly popular for frameworks to use annotation-driven event notification instead.

For an example, see JBossCache 2.2. The listener class has its listener methods annotated, rather than conforming to a rigid interface. This is rather easier to program to, and easier to read, since you don't have to write empty implementations of listener callbacks that you're not interested in (and yes, I know about listener adapter superclasses).

Here's a sample from the JBossCache docs:

@CacheListener
public class MyListener {
   @CacheStarted
   @CacheStopped
   public void cacheStartStopEvent(Event e) {
         switch (e.getType()) {
            case Event.Type.CACHE_STARTED:
               System.out.println("Cache has started");
               break;    
            case Event.Type.CACHE_STOPPED:    
               System.out.println("Cache has stopped");
               break;    
         }
   }    

   @NodeCreated    
   @NodeRemoved
   @NodeVisited
   @NodeModified
   @NodeMoved
   public void logNodeEvent(NodeEvent ne) {
         log("An event on node " + ne.getFqn() + " has occured");
   }

}

The problem with this, is that it's very much more of an involved process writing the framework to support this sort of thing, due to the annotation-reflection nature of it.

So, before I charge off down the road of writing a generic framework, I was hoping someone had done it already. Has anyone come across such a thing?

+2  A: 

Don't mistake complicated for clever. It seems to me that this would be:

  1. A nightmare to debug
  2. Difficult to follow (from a maintenance perspective, or someone attempting to change something 6 months down the line)
  3. Full of if (event instanceof NodeCreatedEvent) like code. Why this is better than subclassing an adapter I have no idea!
oxbow_lakes
1. I don't see how it's any worse than decoupled interfaces, and certainly no worse than the AOP-heavy frameworks that we are (or should be) used to by now.
skaffman
2. I don't see how. Your IDE will show you references to the annotations just as easily as it will show you references to interfaces.
skaffman
3. Because you only get one shot at subclassing, and adapters are poor way of using it. What if you've already subclassed? You either need to take the hit and implement every method with empty stubs, or introduce a delegate object to handle the events.
skaffman
It's too easy to change some interception logic (or a method name) and "miss" the fact that all of a sudden your program just does not work as it is supposed to. Like I said, "complicated" and "clever" are not synonymous
oxbow_lakes
That way well be the case, and thanks for taking the time to answer, but I didn't ask if it was a good idea, I asked how to go about it.
skaffman
+2  A: 

The main problem I see here are the method parameters, which restrict which methods can actually be used for which events, and there's no compile-time help for that.

This is what makes interfaces attractive to me for observer pattern implementations like the Java event model. Tools like eclipse can autogen method stubs so you can't get the signatures wrong. In your example, it's very easy to use the wrong parameter type and never know it until an event occurs (which might be an error case several months down the line)

One thing you might try are my annotations & processor for implementing observers and null object implementations. Suppose you have

package a.b.c;

public interface SomeListener {
    void fee();
    void fie();
    void fo();
    void fum();
}

and wanted to create a listener instance. You could write

package x.y.z;

import a.b.c.SomeListener;
import com.javadude.annotation.Bean;
import com.javadude.annotation.NullObject;

@Bean(nullObjectImplementations = {@NullObject(type = SomeListener.class) })
public class Foo extends FooGen implements SomeListener {
    @Override
    public void fie() {
        // whatever code you need here
    }
}

To create a source for these events, you can write

package a.b.c;

import com.javadude.annotation.Bean;
import com.javadude.annotation.Observer;

@Bean(observers = {@Observer(type = SomeListener.class)})
public class Source extends SourceGen {
    // SourceGen will have add/remove listener and fire methods
    //   for each method in SomeListener
}

See http://code.google.com/p/javadude/wiki/Annotations if you're interested. Might give you some other ideas as well.

Scott Stanchfield
You're absolutely correct about the lack of static type checking, but in some cases it's a worthwhile trade-off. Having an untyped mechanism here allows you decouple the notification mechanics (e.g. threading) from the notification itself. Stongly-typed interfaces would limit this decoupling.
skaffman
+1  A: 

I've been thinking about a generic annotation-driven event framework as well. I like the benefits provided by static typing, but the current interface-driven event model is painful to use (ugly code). Would it be possible to use a custom annotation processor to do some compile-time checking? That might help add some of the missing "safety" that we've all grown used to.

A lot of the error checking can also be done at the time that the listeners are "registered" with the event producers. Thus, the application would fail early (when the listeners are registered), possibly even at at startup-time.

Here's an example of what the generic framework I've been toying with might look like:

public class ExampleProducer {

    private EventSupport<ActionEvent> eventSupport;

    public ExampleProducer() {
     eventSupport = new EventSupport<ActionEvent>(this);
    }

    @AddListenersFor(ActionEvent.class)
    public void addActionListener(Object listener)
    {
     eventSupport.addListener(listener);
    }

    @RemoveListenersFor(ActionEvent.class)
    public void removeActionListener(Object listener)
    {
     eventSupport.removeListener(listener);
    }

    public void buttonClicked() {
     eventSupport.fire(new ActionEvent(this, 
                              ActionEvent.ACTION_PERFORMED, "Click"));
    }
   }

The producer uses EventSupport, which uses reflection to invoke the events. As mentioned before, EventSupport could preform some initial checks when the events listeners are registered.

public class ExampleListener
{   
  private ExampleProducer submitButton;

  public ExampleListener()
  {
    submitButton = new ExampleProducer();
    EventSupport.autoRegisterEvents(this);
  }

  @HandlesEventFor("submitButton")
  public void handleSubmitButtonClick(ActionEvent event)
  {
    //...some code to handle the event here
  }
}

Here, EventSupport has a static method that uses reflection to auto-register the listener with the event producer. This eliminates the need to manually register with the event source. A custom annotation processor could be used to validate that the @HandlesEventFor annotation refers to an actual field of the ExampleListener. The annotation processor could do other checks as well, such as ensuring that the event handler method signature matches up with one of the registration methods on the ExampleProducer (basically, the same check that could be performed at registration-time).

What do you think? Is this worth putting some time into fully developing?

Jim Hurne
+4  A: 

You can already do this today with EventBus.

Following example is from EventBus Getting Started guide. Statusbar that updates based on published events, and no need to register statusbar control/widget as listener of publisher(s). Without EventBus, statusbar will need to be added as listener to many classes. Statusbar can also be created and destroyed at any time.

public StatusBar extends JLabel {
    public StatusBar() {
        AnnotationProcessor.process(this);
    }
    @EventSubscriber(eventClass=StatusEvent.class)
    public void updateStatus(StatusEvent statusEvent) {
        this.setText(statusEvent.getStatusText();
    }
}

A similar project is ELF (Event Listener Framework) but it seems to be less mature.

I'm currently researching about event notification frameworks on Publish-Subscribe Event Driven Programming | Kev's Spring vs Java EE Dev and the followup articles.

Hendy Irawan
I knew such a thing would exist somewhere :)
skaffman
Thank you @skaffman for the rep-up :-)
Hendy Irawan
+3  A: 

I've made http://neoevents.googlecode.com to handle this kind of annotation based event handler.

        @actionPerformed
        private void onClick() {
                //do something
        }

        protected void initComponents() {
                JButton button = new JButton("Click me!!!");
                button.addActionListener(new ActionListener(this) );
        }

It looks as simple as I was expecting it to be. Annotations are available for every single listener in J2SE.

eric
+1  A: 

Here's a similar project called SJES.

public class SomeController {

private Calculator c1 = new Calculator();
private Calculator c2 = new Calculator();

public SomeController() {
    c1.registerReceiver(this);
    c2.registerReceiver(this);
    c1.add(10, 10);
    c2.add(20, 20);
}

@EventReceiver(handleFor="c1")
public void onResultC1(Calculator.Event e) {
    System.out.println("Calculator 1 got: " + e.result);
}

@EventReceiver(handleFor="c2")
public void onResultC2(Calculator.Event e) {
    System.out.println("Calculator 2 got: " + e.result);
}

@EventReceiver
public void onResultAll(Calculator.Event e) {
    System.out.println("Calculator got: " + e.result);
}
}

public class Calculator {

private EventHelper eventHelper = new EventHelper(this);

public class Event {

    long result;

    public Event(long result) {
        this.result = result;
    }
}

public class AddEvent extends Event {

    public AddEvent(long result) {
        super(result);
    }
}

public class SubEvent extends Event {

    public SubEvent(long result) {
        super(result);
    }
}

public void unregisterReceiver(Object o) {
    eventHelper.unregisterReceiver(o);
}

public void registerReceiver(Object o) {
    eventHelper.registerReceiver(o);
}

public void add(long a, long b) {
    eventHelper.fireEvent(new AddEvent(a + b));
}

public void sub(long a, long b) {
    eventHelper.fireEvent(new SubEvent(a - b));
}

public void pass(long a) {
    eventHelper.fireEvent(new Event(a));
}
}

I think this is very easy to use.

Nitramz
Looks handy, thanks
skaffman