Java Annotation: Dependency Injection and Beyond

Java Annotation: Dependency Injection and Beyond

By Yong Fu, OCI Software Engineer, Ph.D

July 2015


Introduction

There are many explanations of annotations. The basic meaning of annotation from Webster's Dictionary is a note added to a text, book, drawing, etc., as a comment or explanation.

A more concise definition of annotations is the word metadata, i.e., data that contains information about itself.

In programming language, annotations are metadata for code, which contain information related to the code itself.

Actually, annotations in Java is a metadata facility that enables you to embed supplemental information in a source file. Java annotation defines APIs for annotating fields, methods, and classes as having particular attributes that indicate they should be processed in specific ways by development tools, deployment tools, or run-time libraries.

However, this information with an annotation does not change the semantics of a program. There are many uses of Java annotations during both development and deployment. For example:

Java annotations were introduced for the first time in Java 5 based on JSR 175, A Metadata Facility for the Java Programming Language. More enhancements and improvements of the annotation system were added in later versions of Java based on JSR 250, Common Annotations, which defines common Java SE and EE annotations, and JSR 269 Pluggable Annotation Processing API, which defines a pluggable interface for developing build-time annotation processors. The latest version, Java 8, also introduces some new features for the annotation system, such as type and repeatable annotations.

In this article, we first introduce some basic concepts of Java annotations; then we use a simple example showing how to process Java annotations and create custom annotations. Dagger, a dependency injection library, is used as a real-world example using Java annotation tools. Finally, we briefly describe several annotation tools used in different aspects in Java development.

Basics of Java Annotations

Basically, an annotation is created based on Java interface. The following example shows how to declare a simple annotation, named myAnnotation:

  1. @interface MyAnnotation {
  2. String str();
  3. int val();
  4. }

The symbol of @ preceding the keyword interface indicates this is an annotation type. The myAnnotation consists solely of method declarations. However, it is not necessary to provide bodies for these methods since Java implements these methods. All annotation types are automatically extended from the annotation interface, which is a member of java.lang.annotation.

After myAnnotation is defined, it may be used to annotate something. Any type of declaration may be annotated by it. For examples, classes, methods, fields, parameters, and enum constants may be annotated. Even an annotation may be annotated. In all cases the annotation precedes the rest of the declaration.

@myAnnotation(str = "Example", val = 1)
public static void myMethod() { ... }

To use the annotation, we need to put @ before the annotation name, myAnnotation. The member of the annotation is given value by assignment to the member name. For example, str is given value of "Example". Note that, because there are no parentheses, the assignment is more like a member field assignment.

Built-in Annotations

Java defines many built-in annotations, most of which are specialized, i.e. those used for persistence (JSR 317), but several of them are general purpose.

Annotations for Code

@Override
indicates that a method declaration is intended to override a method declaration in a superclass.
@Deprecated
marks the method as obsolete.
@SuppressWarnings
indicates that the named compiler warnings should be suppressed in the annotated element (and in all program elements contained in the annotated element). Note that, if several category warnings are suppressed, they should be added in curly braces, like @SuppressWarnings({"unchecked", "cast"}).
@SafeVarargs
suppresses warnings for all callers of a method or constructor with a generic varargs parameter, since Java 7.

Annotations for Annotations

@Target
indicates the kind of program element to which an annotation type is applicable. The program element contains several types (ElementType is an Enum for these types):
  • ElementType.ANNOTATION_TYPE
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • ElementType.LOCAL_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE
  • ElementType.TYPE_PARAMETER
  • ElementType.TYPE_USE
The ElementType.ANNOTATION_TYPE means "annotation for annotation".
The ElementType.TYPE means any type.
ElementType.TYPE_USE and ElementType.TYPE_PARAMETER are used for type annotation and will be explained later.
@Retention
A retention policy determines at what point an annotation is discarded. Java defines three such policies, which are encapsulated within the java.lang.annotation.RetentionPolicy enumeration.
SOURCE
an annotation with a retention policy of SOURCE is retained only in the source file and is discarded during compilation.
CLASS
an annotation with a retention policy of CLASS is stored in the .class file during compilation. However, it is not available through the JVM during run time.
RUNTIME
an annotation with a retention policy of RUNTIME is stored in the .class file during compilation and is available through the JVM during run time.
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @interface myAnnotation {
  3. String str();
  4. int val();
  5. }

In the above example, the annotation myAnnotation will be available during JVM running time.

@Inherited
indicates that an annotation type used in a class should be inherited by subclasses inheriting from that class. An example of @Inherited is:
  1. import java.lang.annotation.Inherited;
  2. // declare annotation
  3. @Inherited
  4. public @interface myInheritedAnnotation {
  5. }
  6. // apply annotation
  7. @MyAnnotation
  8. public class mySuperClass { ... }
  9. public class mySubClass extends mySuperClass { ... }

In this example mySubClass inherits the annotation from mySuperClass.

Java 8 Enhancements

Type Annotations

The place for an annnotation before Java 8 is only before a declaration. In Java 8, the place for an annotation could be where a type is used. This kind of annotation is often called type annotation. For example, you can annotate the return type of a method, generic types, including generic type parameter bounds and generic type arguments. Type annotations are important because they enhance Java type system and enable tools to perform additional checks on type systems to help prevent errors during compilation.

A type annotation must include ElementType.TYPE_USE or ElementType.TYEP_PARAM as a target. An example to declare type annotation is:

@Target(ElementType.TYPE_USE)
@interface typeAnnotation { ... }

When applying typeAnnotation, it must be placed before the type annotated.

void method() throws @typeAnnotation NullPointerException {...}

An example for annotation of type parameter is

@Target(ElementType.TYPE_PARAMETER)
@interface typeParameterAnnotation { ... }

Now typeParameterAnnotation can apply to a type parameter:

class typeAnnotationClass<@typeParameterAnnotation T> {...}

Repeatable Annotations

Java 8 also provides a new annotation feature, which enables an annotation to be repeated on the same element. This is called repeatable annotations and must be annotated with the @Repeatable annotation defined in java.lang.annotation. Its value field specifies the container type for the repeatable annotation. The container is specified as an annotation for which the value field is an array of the repeatable annotation type. An example of a repeatable annotation is:

  1. @Repeatable(myRepeatedAnnotations.class)
  2. @interface myAnnotation {
  3. String str();
  4. int val();
  5. }
  6. // container annotation.
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @interface myRepeatedAnnotations {
  9. myAnnotation[] value();
  10. }
  11. class repeatAnnotation {
  12. @myAnnotation(str = "First", val = 1)
  13. @myAnnotation(str = "Second", val = 2)
  14. public static void method() {}
  15. }

To retrieve the value of repeated myAnnotation, we must first retrieve the container annotation myRepeatedAnnotations, and then extract each repeated annotation from its value array.

Functional Interface Annotation

Java 8 addsa new general purpose built-in annotation @FunctionalInterface, which is an informative annotation type used to indicate that an interface type declaration is intended to be a functional interface, that is, an interface only has single abstract method. An example using @FunctionalInterface is:

  1. @FunctionalInterface
  2. public interface myFunctionalInterface{
  3. void myFunction();
  4. }

Native Annotation

This annotation indicates that an annotated field defining a constant value may be referenced from native code. In some cases, a developer may work on a project with mixed languages, for example, Java and C++. C++ may define constants by including a header file of constants. In Java, we maintain a separate folder for constants. Because this file is not necessary - and prone to inconsistency with its C++ counterpart - @Native can allow the annotation processing tools to generate the C++ header file of constants accordingly.

Java Annotation Processing

Although Java provides numerous built-in annotations, developers may need to create their own annotations and tools to perform specialized tasks. Java defined a set APIs to help developers process custom annotations in different phases in development:

In this article, we focus on compile time annotation processing, which is the core of most annotation processing tools. Commonly, a Java annotation processing framework includes several steps.

  1. Create an instance of a Java annotation Processor with the no-arg constructor of the processor class.
  2. Call the init method with an appropriate ProcessingEnvironment.
  3. Set up SupportedAnnotationTypes, SupportedOptions, and SupportedSourceVersion.
  4. Call the process method on the Processor object; this may be done in multiple rounds.

We use a simple example to describe details of each step.

A Simple Example

To implement a simple annotation whose only purpose is to print out the annotated element.

@Retention(RetentionPolicy.SOURCE)
public @interface PrintAnnotation {}

Since we only need a compiler to process the annotation, we choose RetentionPolicy.SOURCE, and then we implement the annotation processor.

  1. @SupportedAnnotationTypes(
  2. {"com.ociweb.annotation.PrintAnnotation"}
  3. )
  4. @SupportedSourceVersion(SourceVersion.RELEASE_6)
  5. public class PrintAnnotationProcessor extends AbstractProcessor {
  6. public PrintAnnotationProcessor() {
  7. super();
  8. }
  9. @Override
  10. public boolean process(Set<? extends TypeElement> annotations,
  11. RoundEnvironment roundEnvironment) {
  12. for (TypeElement te : annotations) {
  13. for (Element e : roundEnvironment.getElementsAnnotatedWith(te)) {
  14. String message = "Annotation " + te.getSimpleName() + " for " + e.toString());
  15. processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
  16. }
  17. }
  18. return true;
  19. }
  20. }

From the above example we learn that an annotation processor is no more than a class that implements javax.annotation.processing.Processor interface. An abstract implementation with common functionality for custom processors is provided in the class javax.annotation.processing.AbstractProcessor.

For each custom processor, there are three annotations used to configure itself:

@SupportedAnnotationTypes
this annotation is used to register the annotations that the processor supports. Accepted values are fully qualified names of annotation types – wildcards are allowed.
@SupportedSourceVersion
this annotation is used to register the source version that the processor supports.
@SupportedOptions
this annotation is used to register allowed custom options that may be passed through the command line.

To interact with the annotated class, the process() method receives two parameters:

A set of java.lang.model.TypeElement objects
in each round the processors are called and they receive in this set the types of the annotations being processed in the current round.
A javax.annotation.processing.RoundEnvironment objects
annotation processing is done in one or several rounds. This object gives access to the annotated source elements being processed in both the current and the previous rounds.

A ProcessingEnvironment object processingEnv includes some utilities for the annotation processor, for example, writing log and reporting errors. This simple example only shows the skeleton of a typical application to use annotations. In most real world applications the simple framework itself is not too useful. It is necessary to combine with other utilities, especially those tools to manipulate source code, to fulfill the goal of the applications.

In the next section we will show how Dagger, a Java tool for dependency injection, depends on annotation processing and code generation to automatically manipulate code.

Dagger : Dependency Injection by Java Annotation

Dependency Injection (DI) is a software design pattern that implements Inversion of Control for software libraries. Simply put, DI separates a component of those components that it depends on. For example, for a class that reprensents Car, which include Engine and Wheels classes, DI allows Car to receive provided objects of Engine and Wheels rather than creating them by itself. DI can improve modularity of a software system by breaking coupling between a services’ clients and service implementations

Dagger is a Java tool that implements the dependency injection design pattern. It simplifies the process to write "infrastructure" code, like "BarCodeFactory", and allows users to focus on key components. Dagger is the first DI framework to implement standard javax.inject annotations (JSR 330).

Simple Example of Dagger

We use a simple example to show how Dagger works. In this example, the goal is an application that can simulate Twitter.

  1. ...
  2. ApplicationComponent appComponent = Dagger_ApplicationComponent.builder()
  3. .tweeterModule(new TweeterModule("rbrugier"))
  4. .build();
  5. Tweeter tweeter = appComponent.getTweeter();
  6. tweeter.tweet("Hello");
  7. appComponent.getTimeline().getTimeline(10).stream().forEach(System.out::println);

What Dagger does here is build an ApplicationComponent object which can produce the main object Tweeter for tweeting and printing. Next, we build ApplicationComponent.

  1. import dagger.Component;
  2. import javax.inject.Singleton;
  3.  
  4. @Singleton
  5. @Component(modules = {TweeterModule.class, NetworkModule.class})
  6. public interface ApplicationComponent {
  7. Tweeter getTweeter();
  8. Timeline getTimeline();
  9. }

In this snippet of code, we see the first annotation, @Component, used by Dagger. By applying the @Component annotation to the interface ApplicationComponent and passing the module types to the module parameter, Dagger 2 then fully generates code to get the Tweeter object. Dagger needs the users to explicitly annotate where they need objects to be injected, as the following example shows.

  1. import javax.inject.Inject;
  2. public class Tweeter {
  3. private final TwitterApi twitterApi;
  4.  
  5. @Inject
  6. public Tweeter(TwitterApi twitterApi) {
  7. this.twitterApi = twitterApi;
  8. }
  9.  
  10. public void tweet(String tweetChars) {
  11. twitterApi.sendTweet(tweetChars);
  12. }
  13. }

The annotation @Inject allows Dagger to call the constructor of the injected class to create an instance of a class, twitterApi. However, there are some cases that we may not call a constructor directly. For example:

For third-party classes we cannot use @Inject. In these cases, Dagger provides another mechanism for injection. Let us see the two modules TweeterModule and NetworkModule.

  1. ...
  2. import dagger.Module;
  3. import dagger.Provides;
  4.  
  5. @Module
  6. public class TweeterModule {
  7.  
  8. @Provides
  9. @Singleton
  10. public Tweeter provideTweeter(TwitterApi twitterApi) {
  11. return new Tweeter(twitterApi);
  12. }
  13.  
  14. @Provides
  15. @Singleton
  16. public Timeline provideTimeline(TwitterApi twitterApi) {
  17. return new Timeline(twitterApi);
  18. }
  19. ...
  20. }
  1. ...
  2. import dagger.Module;
  3. import dagger.Provides;
  4. @Module
  5. public class NetworkModule {
  6. @Provides
  7. @Singleton
  8. public OkHttpClient provideHttpClient() {
  9. return new OkHttpClient();
  10. }
  11. }

The annotation @Module applies to the classes that contain behavior on how to construct the objects that users want to inject. The methods annotated by the @Provides annotation is the key to create the instances for injection. It returns the object you want to inject into other components.

Annotation Processing in Dagger

Dagger uses a similar framework of annotation processing shown in the aforementioned simple example. However, due to complexity, Dagger breaks the whole annotation processing into several parts.

  1. @AutoService(Processor.class)
  2. public final class ComponentProcessor extends BasicAnnotationProcessor {
  3. private InjectBindingRegistry injectBindingRegistry;
  4. private FactoryGenerator factoryGenerator;
  5. private MembersInjectorGenerator membersInjectorGenerator;
  6.  
  7. @Override
  8. public SourceVersion getSupportedSourceVersion() {
  9. ...
  10. }
  11.  
  12. @Override
  13. public Set<String> getSupportedOptions() {
  14. ...
  15. }
  16.  
  17. @Override
  18. protected Iterable<ProcessingStep> initSteps() {
  19. Messager messager = processingEnv.getMessager();
  20. Types types = processingEnv.getTypeUtils();
  21. Elements elements = processingEnv.getElementUtils();
  22. Filer filer = processingEnv.getFiler();
  23. ...
  24. return ImmutableList.of(
  25. new MapKeyProcessingStep(
  26. messager,
  27. mapKeyValidator,
  28. mapKeyGenerator),
  29. ...
  30. }

This code snippet was extracted from ComponentProcessor.java, which was the core of annotation processing in Dagger. [The above link is no longer active; Dagger2 Annotation Processor can be accessed here.]

@AutoService(Processor.class) is another annotation from Google Auto and can register this annotation processor as a service for the Java compiler.

BasicAnnotationProcessor is an abstract annotation processing framework from Google Auto.

Next step, the annotation processor will query SupportedSourceVersion and SupportedOptions. The processingEnv provides some utilities: Messager for logging annotation processing, Filer for opening new files for generated code from the annotation processor, and Types and Elements representing annotated types and elements.

Finally, the processor will return a set of ProcessingStep, which are responsible for concrete processing of kinds of annotation.

Beyond Dependency Injection: Other applications

There are several Java libraries that make use of annotations. Some of the more well known libraries such as Checker, JUnit, Hibernate and JAXB use annotations. e.g. code quality analysis, unit testing, object-relational mapping, and XML parsing injection.

Checker

Checker is a framework to enhance Java's type system by adding pluggable type systems to the Java language. After defining type qualifiers and their semantics, and a compiler plug-in (a “checker” or annotation processor), developers may write the type qualifiers in their programs and use the plug-in to detect or prevent errors such as null pointer exceptions, unintended side effects, SQL injections, concurrency errors, and mistaken equality tests. In the following simple example, the local variable ref’s type is annotated as @NonNull to indicate that ref must be a reference to a non-null object.

  1. import org.checkerframework.checker.nullness.qual.*;
  2. public class Example {
  3. void sample() {
  4. @NonNull Object ref = null;
  5. }
  6. }

If we run the Checker:

javac -processor org.checkerframework.checker.nullness.NullnessChecker Example.java

Then the Checker will emit the following errors:

  1. Example.java:4: incompatible types.
  2. found : @Nullable
  3. required: @NonNull Object
  4. @NonNull Object ref = null;
  5. ^
  6. 1 error

More details of Checker annotation may be found in Checker Framework Manual.

JUnit

This framework is used for unit testing in Java. Basically, JUnit reads the classes and suites with unit tests and executes depending on the annotations. There are JUnit annotations that setup and tear down unit tests environment, modify the way a test is executed, prevent execution, change order of execution, etc.

A framework to use JUnit is shown as the example below:

  1. public class MyTestClass {
  2. OutputStream stream;
  3.  
  4. @Before
  5. public void initialize() {
  6. stream = new FileOutputStream(...);
  7. }
  8.  
  9. @Test
  10. public void myTestMethod() {
  11. ...
  12. }
  13.  
  14. @After
  15. public void closeOutputStream() {
  16. try{
  17. if(stream != null) stream.close();
  18. } catch(Exception ex){
  19. ...
  20. }
  21. }
  22. }

There are several frequently used annotations in JUnit shown in this example:

@Test
this annotation indicates that the annotated method has to be executed as a unit test.
@Before
this annotation indicates that the annotated methods should be executed before every unit test.
@After
this annotation indicates that the annotated methods should be executed after every unit test.

For a complete list of available annotations in JUnit, see: https://github.com/junit-team/junit/wiki/Getting-started

Hibernate

Hibernate is a popular library for object-relational mapping in Java. It provides a framework for mapping object models and relational databases. Annotations play an important role in its design. Assuming we want to use the following EMPLOYEE table to store our objects:

  1. create table EMPLOYEE (
  2. id INT NOT NULL auto_increment,
  3. first_name VARCHAR(20) default NULL,
  4. last_name VARCHAR(20) default NULL,
  5. salary INT default NULL,
  6. PRIMARY KEY (id)
  7. );

Using Hibernate annotations, we can map Employee class objects into the defined EMPLOYEE table:

  1. import javax.persistence.*;
  2.  
  3. @Entity
  4. @Table(name = "EMPLOYEE")
  5. public class Employee {
  6. @Id @GeneratedValue
  7. @Column(name = "id")
  8. private int id;
  9.  
  10. @Column(name = "first_name")
  11. private String firstName;
  12.  
  13. @Column(name = "last_name")
  14. private String lastName;
  15.  
  16. @Column(name = "salary")
  17. private int salary;
  18.  
  19. public Employee() {}
  20. public int getId() {
  21. return id;
  22. }
  23. // setters and getters
  24. ...
  25. }

The annotations used here are :

@Entity
this annotation indicates that the annnotated class is as an entity bean.
@Table
this annotation specifies the table to persist the entity in the database.
@Id
this annotation indicates the annotated element is a primary key.
@GeneratedValue
this annotation indicates the primary key will be auto-generated.
@Column
this annotation specifies the details of the column to which a field or property will be mapped.

These annotations belong to the Java Persistence APIs package from the Java Enterprise Edition, which covers all the most common annotations that Hibernate uses.

JAXB

JAXB is a library to translate XML files into Java objects and vice versa. JAXB allows Java developers to access and process XML data without having to know XML or XML processing. It may be used directly by importing the classes into the package javax.xml.bind.annotationin your applications.

JAXB uses some annotations to enable conversion between the XML and code. For example, there are annotations used to indicate XML nodes, XML attributes and values, etc. in the code. Here is a simple example:

  1. ...
  2. @XmlType( propOrder = { "brand", "model", "year", "km" } )
  3. @XmlRootElement( name = "Car" )
  4. class Car
  5. ...

The annotations used here are @XmlType and @XmlRootElement. The @XmlType indicates the order of the properties in the resultant XML. The @XmlRootElement indicates that the annotated class is used as a XML node. For conversion we still need to call JAXB marshaller:

  1. Car car = new Car();
  2. car.setBrand( "Honda" );
  3. car.setModel( "CRV" );
  4. car.setYear( 1999 );
  5. car.setKm( 250000 );
  6. Marshaller jaxbMarshaller = JAXBContext.newInstance( Car.class ).createMarshaller();
  7. jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
  8. jaxbMarshaller.marshal( car, System.out );

The output of this program will be something like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Car>
    <brand>Honda</brand>
    <model>CRV</model>
    <year>1999</year>
    <km>250000</km>
</Car>

More information may be found at https://jaxb.java.net/.

Summary

In this tutorial, we introduced Java annotation, an important feature and infrastructure of the Java language.

Java annotation can provide metadata of the code and be processed by various tools to help development and deployment of Java applications. We first described the history and basics of Java annotations and processing framework. Then we used Dagger, a dependency injection library, as an example to show how real-world tools using annotations accelerate development. We also briefly presented some libraries powered by annotations to show how annotations can used in other scopes such as code validation, protocol translation, and unit testing.

Acknowledgements

This article is inspired by dynamic APIs design for Pronghorn project in OCI. I want to thank my colleagues from OCI, Charles Sharp and Nathan Tippy, for very useful and professional comments that greatly improved the quality of this article.

Further Reading



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret