Brian Slesinsky's Weblog
 
   

Sunday, 10 Jun 2007

Annotated Callback Methods

There's a Java design pattern that shows up in Guice, TestNG, and JUnit 4 that seems common enough to need a name. I'm calling it an annotated callback method, and I'll describe it here, in the format popularized by the Design Patterns book.

Pattern Name

Annotated Callback Method

Intent

Conveniently register callbacks with a library.

Motivation

Java doesn't have direct support for functions, so the typical way to write a callback is to implement a one-method interface. This can be done inline using anonymous inner classes. However, anonymous inner classes are a clumsy language construct that confuses many beginners, and it makes callback-registering code difficult to read, especially when you need to write a large number of callbacks. If callbacks can be written using syntax more similar to regular methods, the library can be significantly easier to use.

Applicability

You are writing a library that requires many callbacks to be registered, and are willing to do extra work to increase the library's ease of use.

Participants

There is a callback provider that exports some callback methods and a library that calls them.

Collaborations

The library declares a Java annotation:

  @Retention(RetentionPolicy.RunTime)
  @Target(ElementType.METHOD)
  public @interface Foo {}

The callback provider puts this annotation on each method that it wishes to export to the library:

public class FooCallbacks {
 
  @Foo
  public void methodToCall() { ... }
 
  @Foo
  public void anotherMethodToCall() { ... }
}

The callback provider somehow registers this class with the library:

  something.register(FooCallbacks.class);

or

  something.register(new FooCallbacks());

The library uses reflection to find the callback methods and verifies that they have the correct parameters and return values.

Consequences
  • The names of the callback methods are irrelevant to the library, giving the person writing the callbacks the freedom to use more meaningful names and to rename methods at any time.
  • The contract between a callback method and its caller is implicit. As a result, the Java compiler can't verify that a callback method's parameters and return types are correct, and generic refactoring tools can't be used to automatically change the method signature everywhere it's used. The library can provide type-checking using an annotation processor or at runtime, and refactoring tools could be written explicitly supporting the library, but this increases the burden on the library's authors.
  • When you use an IDE's to find all the usages of a method, it will not list the library as a caller. If the annotation is well-known, this isn't a problem, but it's one more thing for people unfamiliar with the code to learn.
  • The class that provides the callbacks will have a dependency on the annotation interface.

As a result, annotated callbacks are suitable for stable and commonly used libraries. For internal interfaces that may change frequently, anonymous inner classes are more appropriate.

Variations

In versions of Java before JDK 1.5, annotations were not available, so alternate ways of identifying callback methods had to be used. For example, JUnit 3.x has the well-known convention of starting all callback methods that implement a test case with test. Some other frameworks used Javadoc tags.

If annotating each method is too bothersome, a class annotation might be used instead. In that case, the library's way of finding callback methods needs to be documented, and it's more likely that some readers won't understand that the method is used as a callback.