Annotations introduced to Java 5.0 became a very well known and widely used language feature. There are more or less "standard" solutions where annotation can be successfully used. This article tries to classify different usages and to define some annotation based design patterns.
Introduction
Annotations define metadata that can be discovered using reflection API. They do not affect neither class hierarchy nor objects relationship. However, they can be used to label classes, methods and fields, to lookup services, to define model transformation rules, to configure proxies etc. These use cases can be classified as re-usable patterns exactly like design patterns in object oriented programming.
Scope
This article is not a tutorial that explains what annotations can do. It assumes that a reader is familiar with annotations. The article shows the most popular patterns that can be used for solving typical tasks. Annotations can be classified by their retention policy. This article discusses annotations that can be retained by VM at runtime, e.g. marked as
@Retention(RUNTIME)
Classification of annotations
Annotations may be classified by:
- target (type, method, field, annotation etc)
- role (stereotype, transformation, validation etc)
- module that uses this annotation. Usually annotation is used by other class but sometimes annotated class itself uses its own annotation.
- instance lifecycle phases
- before instance creation
- before executing business logic (runner, injector)
- during executing business logic (discovering stack trace)
Annotation based design patterns
Stereotype (Tag)
Tag interfaces (interfaces that do not declare any method and are used as a kind of label of class that implements them) were used for a long time before inventing of annotations. The classic example of such interface is java.io.Serializable. Since tag interface does not declare methods there are two ways to use it:
if (obj instanceof Serializable) {...}
or
if (Serializable.isAssignableFrom(clazz)) {...}
If we use annotations instead of tag interface we can replace definition:
class MyClass implements MyTag {...}
where MyTag is defined as
interface MyTag {}
by the following one:
@MyTag
class MyClass {...}
where MyTag is defined as:
Class marked with this annotation can be detected using code:
if (clazz.getAnnotation(MyTag.class)) {...}
Using annotation instead of tag interface does not require additional efforts during implementation and has advantage: annotation can hold one or several parameters.
Tags or Stereotypes allow defining what the class does in different contexts.
Examples:
- org.springframework.transaction.annotation.Transactional
- javax.persistence.Transient
- java.lang.annotation.Documented
- java.lang.annotation.Retention
- java.lang.annotation.Target
Service locator
Typical software product consists of modules or services. Components should be able to find other services that provide required functionality. Service as well as tag pattern may be defined as a class that implements specific interface. But contrary to tag interface service interface declares methods.
Before annotations were invented frameworks could locate service if it implemented required interface. This method does not allow distinguishing between two different implementations of the same interface. Annotations allow this. For example Service annotation of Spring Framework can hold service name:
@Service("manager1")
public class FirstManager implements Manager {}
@Service("manager2")
public class SecondManager implements Manager {}
This example shows two services that implement the same interface but can be identified by the name written as an annotation argument.
Not only class but also separate method can be a service. For example, each test in the test case is "service" that validates specific testing scenario. Particular test is labeled with annotation @Test and framework can find it.
More complicated example of service pattern is @RequestMapping of Spring MVC. This annotation supports several attributes that help the framework to choose suitable services according to HTTP request parameters. Both classes and methods can be annotated with @RequestMapping.
Injector
Injector is an annotation that helps framework to inject (or initialize) method arguments when calling business method. @PathVariable of Spring MVC that is used in conjunction with @RequestMapping instructs the framework how to initialize method arguments:
@RequestMapping(value = "/user/{username}", method = RequestMethod.GET)
@ResponseBody
public User getUser(@PathVariable("username") String userName) {...}
Now when URL looks like http://thehost/app/user/johnsmith the framework extracts johnsmith from the URL and passes it as a method argument userName because this argument is marked with annotation @PathVariable("username").
Master (Runner)
Contrary to Service Locator that defines metadata of the class itself the runner refers to other class that will be used to run the current class. The typical example is @RunWith annotation of JUnit. This annotation marks test case and defines the runner that will execute current test case:
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {...}
Configurer
Annotation may configure class at runtime. An example is @Parameters annotation of JUnit that must be used in conjunction with Parametrized test runner.
We can run the same test case with different set of arguments and expected results using @Parameters. The following simple example shows how to use these annotations:
@RunWith(value = Parameterized.class)
public class MatchTest {
private Pattern pattern = Pattern.compile("^\\d+$");
private String number;
private boolean expected;
public MatchTest(String number, boolean expected) {
this.number = number;
this.expected = expected;
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{"100 kg", false}, {"123", true}, {"220 lb", false}
});
}
@Test
public void match() {
Assert.assertEquals(expected, pattern.matcher(number).find());
}
}
In the above example JUnit runs this test case 3 times passing "100 kg", "123", "220 lb" as the test arguments and false, true, false as the expected results.
Proxy/Wrapper/AOP
Annotations may be used to create and configure class wrapper or dynamic proxy or even to modify the byte code of the class itself utilizing byte code engineering. Spring security framework implements this pattern as follows:
@PreAuthorize("hasRole('ROLE_USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();
The example above shows how to annotate bean method so that its invocation is permitted only if current user has role ROLE_USER. Moreover, the returned collection is filtered automatically and the result contains only elements for which the condition is defined by @PostFilter is true. Spring wraps the application level bean with dynamic proxy to perform security check and filter results according to annotation based configuration.
Validator
Annotations can be used for configuration of data validation. Two types of validation can be distinguished: bean validation (
JSR 303) and method validation. Hibernate validation framework supports both types using the similar way.
Bean validation
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
................
}
Method validation
public @NotNull @Size(min-1) Collection<Role> findRolesOfUser(@NotNull User user) {...}
Both bean and method validations use similar annotation syntax. However they differ in implementation.
- Method validator typically uses dynamic proxy or byte code engineering. Bean validation may be implemented utilizing validation mechanisms of other systems. For example, @NotNull annotated field may be validated using appropriate constraint of target relational database.
- Method validator is always synchronous while validation of bean field may be asynchronous. For example, in Hibernate persisted entity the constraint violation can be found when transaction is being committed. It is not necessarily happens directly after bean field modification.
Transformer
Transformer defines how to convert value from one form to another. Typical usage is marking bean properties or appropriate getters. The most popular examples are:
- JaxB annotations @XmlElement, @XmlAttribute, @XmlTransient that define how to transform bean properties to XML elements and vice versa.
- Hibernate annotations: @Table, @Id, @Column etc. that define how to transform objects to records stored in relational database and vice versa.
The above mentioned @PostFilter annotation that configures dynamic proxy can be classified also as a transformer. It defines additional criteria for filtering of collection retrieved from database.
Annotation container
Two or more annotations of the same type are not allowed on specific element. This is forbidden by the java compiler. One solution is to use array instead of simple value for the specific attribute. For example, already mentioned above
@RequestMapping can map specific controller or its method to several URLs:
@RequestMapping(value = {
"/user/{username}",
"/userlist?username={username}"},
method = RequestMethod.GET)
@ResponseBody
public User getUser(@PathVariable("username") String userName) {...}
This is a good solution if the value of the attribute is not used in conjunction with the value of the other attribute: mappings of both URLs are valid for HTTP GET.
Let's see another example.
@XmlElement(name = "user-name", required=true)
public String getUsername() {return username;}
In this example the field username is mapped to mandatory XML element user-name. How can we support mapping of the same bean field to additional XML element UserName? We cannot add annotation:
@XmlElement(name = "UserName", required=false)
to the same getter getUsername(). Fortunately JAXB provides another annotation @XmlElements that plays role of the container for other annotations:
@XmlElements ({
@XmlElement(name = "user-name", required=true),
@XmlElement(name = "UserName", required=false)
)}
public String getUserName() {return username;}
The getter is annotated once using @XmlElements. However, both object-to-XML mappings are provided.
Definition of @XmlElements is very simple. The value type is an array of XmlElement:
public @interface XmlElements {
XmlElement[] value();
}
Custom annotation
Regular classes can be customized using inheritance. Annotations do not support inheritance. How can we customize generic annotations?
Possible solution is to use custom annotation which is annotated with another annotation provided by framework. @Profile annotation of Spring framework is an example of this pattern. As described here Spring supports profiles. We can define several profiles and run different set of beans for each one. Bean can be associated with profile using @Profile annotation. Both FirstDevService and SecondDevService will run when profile is dev.
@Profile("dev") @Service
public class FirstDevService { ... }
@Profile("dev") @Service
public class SecondDevService { ... }
Using @Profile("dev")annotation is relatively verbose and error prone: a bean which is by mistake marked as @Profile("deu")will not start in mode "dev" and no error message will be generated. Fortunately Spring allows creating custom annotation @Dev and mark it with @Profile:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("dev")
pubilc @interface Dev {}
Now we can use this new annotation as following:
@Dev @Service
public class FirstDevService { ... }
@Dev @Service
public class SecondDevService { ... }
Syntax that uses custom annotations is shorter, more readable and less error prone: simple mistake in annotation name will produce compilation error.
Caller identifier
Sometimes code has to discover its caller. Let's examine the following examples. The trick is to iterate over stack trace and to look for specific annotation.
- Factory will create an instance of a special mockup implementation instead of real implementation when running in test environment. Test context may be identified if one of stack trace elements is annotated with @Test.
- Special logic can be required when running in web context. Web context can be detected if @WebServlet or @Controller annotations are found.
Here is a simple implementation of utility that checks whether the code was called by class annotated with specified annotation. For example CallerUtil.isCallerClassAnnotatedBy(Controller.class) determines whether the caller is Spring MVC controller.
public class CallerUtil {
private static Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
public static boolean isCallerClassAnnotatedBy(
Class<? extends Annotation> annotationClass) {
for (StackTraceElement e : new Throwable().getStackTrace()) {
Class<?> clazz = getClazz(e.getClassName());
if (clazz.getAnnotation(annotationClass) != null) {
return true;
}
}
return false;
}
private static Class<?> getClazz(String className) {
Class<?> clazz = classes.get(className);
if (clazz == null) {
try {
clazz = Class.forName(className);
classes.put(className, clazz);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
return clazz;
}
}
Annotated interface
Annotation targeted to type and annotated as @Inherited can be retrieved even if it is used to annotate not the class itself but its super class:
@Retention(RUNTIME)
@Target(TYPE)
@Inherited
public @interface
MyAnnotation {}
@MyAnnotation
public class Base {}
public class Child extends Base {}
The call Child.class.getAnnotation(MyAnnotation.class) will return instance of MyAnnotation although class Child is not annotated with MyAnnotation because base class is annotated and MyAnnotation is annotated itself as @Inherited. Unfortunately annotation are inherited only from super classes. Method getAnnotation() does not return annotations used for interface implemented by current class.
@MyAnnotation
public interface Foo {}
public class FooImpl implements Foo {}
The call FooImpl.class.getAnnotation(MyAnnotation.class) will return null because Foo is an interface.
Although I have not seen this pattern utilized by popular libraries I personally think that annotated interfaces may be very useful. To retrieve annotation from the interface we have to iterate over all interfaces implemented by class and try to retrieve the annotation from each interface separately. The following utility method retrieves annotation applied to class itself, its base class or any of interfaces implemented by this class directly or indirectly.
public static <A extends Annotation> A getAnnotation(Class<?> clazz, Class<A> annotationType) {
A classAnnotation = clazz.getAnnotation(annotationType);
if (classAnnotation != null) {
return classAnnotation;
}
for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
for (Class<?> implementedInterface : c.getInterfaces()) {
A interfaceAnnotation = implementedInterface.getAnnotation(annotationType);
if (interfaceAnnotation != null) {
return interfaceAnnotation;
}
}
}
return null;
}
Shared constructor parameter
Let's review the example. There is an abstract class Base with constructor that accepts parameters of type Class.
public abstract Base {
protected Base(Class<?> type) {
// uses argument type here
}
}
There is an abstract subclass Intermediate that has nothing to do with the parameter.
typical tasks
public abstract Intermediate extends Base {
protected Intermediate(Class<?> type) {
super(type);
}
}
There is the concrete class Concrete that sends the same value of the parameter for all its instances.
public Concrete extends Intermediate {
protected Intermediate(Class<?> type) {
super(String.class);
}
}
Although Intermediate has nothing to do with type it must declare constructor that just passes the parameter to its base class. The inheritance chain may be longer. But each class in the chain must have such trivial constructor, i.e. must be aware of the base class parameter existence.
Alternatively we can pass this data using annotations.
Let's define annotation CocreteType:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@interface ConcreteType {
Class<?> value();
}
The constructor of the base class does not have to accept the parameter. It extracts this information from annotation that exists in the concrete class:
public abstract Base {
protected Base(Class<?> type) {
Class<?> type =
getClass().getAnnotation(ConcreteType.class).value();
// deal with type
}
}
Both Intermediate and Concrete classes do not have explicit constructor at all:
public abstract Intermediate extends Base {
}
The value of type is defined using annotation.
@ConcreteType(String.class)
public Concrete extends Intermediate {
}
This implementation is shorter and easier for modification. For example, if parameter type is changed Intermediate class should not be modified at all.
By the way, Concrete class can be subclassed too:
public MoreConcrete extends Concrete {
}
Class MoreConcrete is not annotated with @ConcreteType but since @ConcreteType is annotated as @Inherited, MoreConcrete inherits it from Concrete.
Conclusions
Design patters are well known technique for creating robust and reusable software components. Various ways of using annotations in Java programming language can be classified as annotation based design patterns. This article suggests classification of typical tasks that can be implemented by using annotations. Author hopes that this classification may be helpful when designing and choosing instruments for implementation of other similar tasks.