Annotations are first introduced in java1.5. Annotation mean “a crucial explanatory note” added to anything. These are more powerful than java comments, javadoc comments.
Annotations have a number of uses, among them:
Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
Compiler-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
Runtime processing — Some annotations are available to be examined at runtime.
Using annotations, we are able to add metadata information to our source code – build or deployment information, configuration properties, compilation behavior or quality checks.
Annotations can be retained at source code level, class level and runtime level. If the retention policy is specified as “source code” then the annotations will only be used by the compiler and they will not present in the class files. If the retention policy is “class” then the annotations will be present in class files but will not be used by VM. If the retention policy is “runtime” then they are also available at runtime which means the VM loads them.
Annotation is a kind of interface.
Annotation Types have some differences compared to regular interfaces:
Only primitives, strings, enums and arrays of them are allowed. Note that as Objects in general are not allowed, arrays of arrays are not allowed in Annotation Types (every array is an object).
The annotation elements are defined with a syntax very similar to that of methods, but keep in mind that modifiers and parameters are not allowed.
Default values are defined using the default keyword followed by the value that will be a literal, an array initializer or an enum value.
All the annotations by default extends “java.lang.annotation.Annotation”. we are not manually allowed to extend an interface in annotations. This happens automatically like all classes extends “java.lang.Object” class by default.
Here while declaring annotation the Interface keyword is preceded by “@”. The declaration of Annotation is as follows.
public @interface Complexity {
Level value(); // The return type Level is an Enum.
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}
Single Value Annotations:-
There is a chance that an annotation can have only one element. In such a case that element should be named value.
To make the information in @Complexity appear in Javadoc-generated documentation, you must annotate the @Complexity definition itself with the@Documented annotation:
@Documented
@interface Complexity {
// Annotation element definitions
}
Annotations can be applied to a program's declarations of classes, fields, methods, and other program elements.
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() { }
or
@SuppressWarnings(value = "unchecked")
void myMethod() { }
If there is just one element named "value," then the name may be omitted, as in:
@SuppressWarnings("unchecked")
void myMethod() { }
Also, if an annotation has no elements, the parentheses may be omitted, as in:
@Override
void mySuperMethod() { }
Annotations Used by the Compiler
There are three annotation types that are predefined by the language specification itself:
@Deprecated, @Override, and @SuppressWarnings.
@Deprecated—the @Deprecated annotation indicates that the marked element is deprecated and should no longer be used. The compiler generates a warning whenever a program uses a method, class, or field with the @Deprecated annotation. When an element is deprecated, it should also be documented using the Javadoc @deprecated tag, as shown in the following example. The use of the "@" symbol in both Javadoc comments and in annotations is not coincidental — they are related conceptually. Also, note that the Javadoc tag starts with a lowercase "d" and the annotation starts with an uppercase "D".
// Javadoc comment follows
/**
* @deprecated
* explanation of why it
* was deprecated
*/
@Deprecated
static void deprecatedMethod() { }
}
@Override—the @Override annotation informs the compiler that the element is meant to override an element declared in a superclass (overriding methods will be discussed in the the lesson titled "Interfaces and Inheritance").
// mark method as a superclass method
// that has been overridden
@Override
int overriddenMethod() { }
While it's not required to use this annotation when overriding a method, it helps to prevent errors. If a method marked with @Override fails to correctly override a method in one of its superclasses, the compiler generates an error.
@SuppressWarnings—the @SuppressWarnings annotation tells the compiler to suppress specific warnings that it would otherwise generate. In the example below, a deprecated method is used and the compiler would normally generate a warning. In this case, however, the annotation causes the warning to be suppressed.
// use a deprecated method and tell
// compiler not to generate a warning
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
// deprecation warning
// - suppressed
objectOne.deprecatedMethod();
}
Every compiler warning belongs to a category. The Java Language Specification lists two categories:
"deprecation" and "unchecked." The "unchecked" warning can occur when interfacing with legacy code written before the advent of generics (discussed in the lesson titled "Generics"). To suppress more than one category of warnings, use the following syntax:
@SuppressWarnings({"unchecked", "deprecation"})
The JDK comes with some annotations that are used to modify the behavior of the Annotation Types that we are defining:
@Documented: Indicates that the marked Annotation Type should be documented by Javadoc each time it is found in an annotated element.
@Inherited: Indicates that the marked Annotation Type is inherited by subclasses. This way, if the marked annotation is not present in a subclass it inherits the annotation in the superclass, if present. Only applies to class inheritance and not to interface implementations.
@Retention: Indicates how long the marked Annotation Type will be retained. Possible values are those of enum RetentionPolicy: CLASS (default – included in class files but not accessible at run-time), SOURCE (discarded by the compiler when the class file is created) and RUNTIME (available at run-time).
@Target: Indicates the element types to which the marked Annotation Type is applicable. Possible values are those of enum ElementType: ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER and TYPE.
Processing Annotations:-
The Annotation Processor API
When Annotations were first introduced in Java 5, the Annotation Processor API was not mature or standardized. A standalone tool named apt, the Annotation Processor Tool, was needed to process annotations, and the Mirror API, used by apt to write custom processors, was distributed in com.sun.mirror packages.
Starting with Java 6, Annotation Processors were standardized through JSR 269 (2), incorporated into the standard libraries and the tool apt seamlessly integrated with the Java Compiler Tool, javac.
We can process the annotations using customs processors and can perform desired actions based on the annotations.
Anybody who writes custom annotation processor has to implement the interface “javax.annotation.processing.Processor” but java provides an abstract class which implements this interface making the developer job easy by providing implementation to most common methods. The abstract class name is “javax.annotation.processing.AbstractProcessor”. This class has an abstract method
“public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)”
Parameters:
annotations - the annotation types requested to be processed
roundEnv - environment for information about the current and prior round
The annotation processing may happen in several rounds. RoundEnvironment provides the current round information.
All the classes, methods,variables that are annotated are treated as elements. The “RoundEnvironment.getElementsAnnotatedWith” method returns the annotated things as Elements. The Elements could be of kind annotation, class, constructor,enum, enum constant, field and so on.
The following is the example of a processor which process “Complexity” annotation to write a new source code file from the existing.
Enum class used in this example:-
public enum Level {
SIMPLE, MEDIUM, COMPLEX;
}
Annotation used in this example:-
public @interface Complexity {
Level value();
}
Class that will be processed in this example:-
@Complexity(Level.SIMPLE)
public class Person {
@Complexity(Level.MEDIUM)
public void move(){
}
@Complexity(Level.COMPLEX)
public void drive(){
}
}
The Processor which process the annotation:-
import java.io.BufferedWriter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
/**
* @author Muralidhar Yaragalla
*
*/
@SupportedAnnotationTypes("annotate.Complexity")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ComplexityProcessor extends AbstractProcessor{
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv){
for(Element elm:roundEnv.getElementsAnnotatedWith(Complexity.class)){
//processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,elm.getSimpleName());
if (elm.getKind() == ElementKind.CLASS) {
TypeElement classElement = (TypeElement) elm;
PackageElement packageElement = (PackageElement) classElement.getEnclosingElement();
try{
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(classElement.getQualifiedName() + "info");
BufferedWriter bw = new BufferedWriter(jfo.openWriter());
bw.append("package ");
bw.append(packageElement.getQualifiedName());
bw.append(";");
bw.newLine();
bw.newLine();
bw.flush();
bw.close();
}catch(Exception e){
}
}//end of if
}//end of for
return true;
}
}
How this entire process works:-
The class files of the custom processor and its dependencies has to be packed in a jar file and this jar file should be in the classpath before compiling the source files(declared with annotations). Then when the compiler(javac) is invoked on the source files. It starts executing the Processor whenever it finds the source files with the registered annotation to that Processor. The Processor process the source files when compilation itself.
How to package the Processor:-
The jar file should contain a “META-INF” folder. Inside “META-INF” folder there should be a folder with name “services”. Inside “services” folder a file with name “javax.annotation.processing.Processor” has to be created. Inside this file the processors(Fully qualified name of the processor) have to be specified. One Processor in one line.