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:
- Annotations may be used by the compiler to detect errors or suppress warnings.
- Annotations may be used to map SQL entities to Java objects.
- Annotations may be used to generate code, manipulate XML files, and so forth.
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
:
- @interface MyAnnotation {
- String str();
- int val();
- }
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
ElementType.ANNOTATION_TYPE
means "annotation for annotation".
TheElementType.TYPE
means any type.
ElementType.TYPE_USE
andElementType.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.
- @Retention(RetentionPolicy.RUNTIME)
- @interface myAnnotation {
- String str();
- int val();
- }
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:
- import java.lang.annotation.Inherited;
- // declare annotation
- @Inherited
- public @interface myInheritedAnnotation {
- }
- // apply annotation
- @MyAnnotation
- public class mySuperClass { ... }
- 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:
- @Repeatable(myRepeatedAnnotations.class)
- @interface myAnnotation {
- String str();
- int val();
- }
- // container annotation.
- @Retention(RetentionPolicy.RUNTIME)
- @interface myRepeatedAnnotations {
- myAnnotation[] value();
- }
- class repeatAnnotation {
- @myAnnotation(str = "First", val = 1)
- @myAnnotation(str = "Second", val = 2)
- public static void method() {}
- }
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:
- @FunctionalInterface
- public interface myFunctionalInterface{
- void myFunction();
- }
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:
- At compile time, Annotation Processors, a specialized type of classes, handles the different annotations found in code being compiled.
- At runtime, annotations with runtime retention policy are accessible through reflection. The methods
getAnnotation()
andgetAnnotations()
in theClass
class will return annotations that developers need.
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.
- Create an instance of a Java annotation Processor with the no-arg constructor of the processor class.
- Call the init method with an appropriate
ProcessingEnvironment
. - Set up
SupportedAnnotationTypes
,SupportedOptions
, andSupportedSourceVersion
. - 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.
- @SupportedAnnotationTypes(
- {"com.ociweb.annotation.PrintAnnotation"}
- )
- @SupportedSourceVersion(SourceVersion.RELEASE_6)
- public class PrintAnnotationProcessor extends AbstractProcessor {
- public PrintAnnotationProcessor() {
- super();
- }
- @Override
- public boolean process(Set<? extends TypeElement> annotations,
- RoundEnvironment roundEnvironment) {
- for (TypeElement te : annotations) {
- for (Element e : roundEnvironment.getElementsAnnotatedWith(te)) {
- String message = "Annotation " + te.getSimpleName() + " for " + e.toString());
- processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
- }
- }
- return true;
- }
- }
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.
- ...
- ApplicationComponent appComponent = Dagger_ApplicationComponent.builder()
- .tweeterModule(new TweeterModule("rbrugier"))
- .build();
- Tweeter tweeter = appComponent.getTweeter();
- tweeter.tweet("Hello");
- 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.
- import dagger.Component;
- import javax.inject.Singleton;
-
- @Singleton
- @Component(modules = {TweeterModule.class, NetworkModule.class})
- public interface ApplicationComponent {
- Tweeter getTweeter();
- Timeline getTimeline();
- }
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.
- import javax.inject.Inject;
- public class Tweeter {
- private final TwitterApi twitterApi;
-
- @Inject
- public Tweeter(TwitterApi twitterApi) {
- this.twitterApi = twitterApi;
- }
-
- public void tweet(String tweetChars) {
- twitterApi.sendTweet(tweetChars);
- }
- }
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:
- Interfaces
- Configurable objects
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
.
- ...
- import dagger.Module;
- import dagger.Provides;
-
- @Module
- public class TweeterModule {
-
- @Provides
- @Singleton
- public Tweeter provideTweeter(TwitterApi twitterApi) {
- return new Tweeter(twitterApi);
- }
-
- @Provides
- @Singleton
- public Timeline provideTimeline(TwitterApi twitterApi) {
- return new Timeline(twitterApi);
- }
- ...
- }
- ...
- import dagger.Module;
- import dagger.Provides;
- @Module
- public class NetworkModule {
- @Provides
- @Singleton
- public OkHttpClient provideHttpClient() {
- return new OkHttpClient();
- }
- }
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.
- @AutoService(Processor.class)
- public final class ComponentProcessor extends BasicAnnotationProcessor {
- private InjectBindingRegistry injectBindingRegistry;
- private FactoryGenerator factoryGenerator;
- private MembersInjectorGenerator membersInjectorGenerator;
-
- @Override
- public SourceVersion getSupportedSourceVersion() {
- ...
- }
-
- @Override
- public Set<String> getSupportedOptions() {
- ...
- }
-
- @Override
- protected Iterable<ProcessingStep> initSteps() {
- Messager messager = processingEnv.getMessager();
- Types types = processingEnv.getTypeUtils();
- Elements elements = processingEnv.getElementUtils();
- Filer filer = processingEnv.getFiler();
- ...
- return ImmutableList.of(
- new MapKeyProcessingStep(
- messager,
- mapKeyValidator,
- mapKeyGenerator),
- ...
- }
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.
- import org.checkerframework.checker.nullness.qual.*;
- public class Example {
- void sample() {
- @NonNull Object ref = null;
- }
- }
If we run the Checker:
javac -processor org.checkerframework.checker.nullness.NullnessChecker Example.java
Then the Checker will emit the following errors:
- Example.java:4: incompatible types.
- found : @Nullable
- required: @NonNull Object
- @NonNull Object ref = null;
- ^
- 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:
- public class MyTestClass {
- OutputStream stream;
-
- @Before
- public void initialize() {
- stream = new FileOutputStream(...);
- }
-
- @Test
- public void myTestMethod() {
- ...
- }
-
- @After
- public void closeOutputStream() {
- try{
- if(stream != null) stream.close();
- } catch(Exception ex){
- ...
- }
- }
- }
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:
- create table EMPLOYEE (
- id INT NOT NULL auto_increment,
- first_name VARCHAR(20) default NULL,
- last_name VARCHAR(20) default NULL,
- salary INT default NULL,
- PRIMARY KEY (id)
- );
Using Hibernate annotations, we can map Employee class objects into the defined EMPLOYEE table:
- import javax.persistence.*;
-
- @Entity
- @Table(name = "EMPLOYEE")
- public class Employee {
- @Id @GeneratedValue
- @Column(name = "id")
- private int id;
-
- @Column(name = "first_name")
- private String firstName;
-
- @Column(name = "last_name")
- private String lastName;
-
- @Column(name = "salary")
- private int salary;
-
- public Employee() {}
- public int getId() {
- return id;
- }
- // setters and getters
- ...
- }
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.annotation
in 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:
- ...
- @XmlType( propOrder = { "brand", "model", "year", "km" } )
- @XmlRootElement( name = "Car" )
- class Car
- ...
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:
- Car car = new Car();
- car.setBrand( "Honda" );
- car.setModel( "CRV" );
- car.setYear( 1999 );
- car.setKm( 250000 );
- Marshaller jaxbMarshaller = JAXBContext.newInstance( Car.class ).createMarshaller();
- jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
- 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
- [1] Schildt, Herbert. Java: the Complete Reference. Comprehensive Coverage of the Java Language. 9th edition. McGraw-Hill OSbrone Media, 2014. Print.
- [2] Java Platform, Standard Edition 8, API Specification. Oracle, 2015. Web.
http://docs.oracle.com/javase/8/docs/api/ - [3] Lessons: Annotations. Oracle, 2015. Web.
https://docs.oracle.com/javase/tutorial/java/annotations/ - [4] Type Annotations in Java 8: Tools and Opportunities. Todd Shiller, InfoQueue, 2014. Web.
http://www.infoq.com/articles/Type-Annotations-in-Java-8
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.