Wednesday 11 December 2019

Annotations explained


Annotation is a kind of metadata, that do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries, which can in turn affect the semantics of the running program. Annotations can be read from source files, class files, or reflectively at run time.

How to define an annotation?
Defining an annotation is similar to the interface declaration.

Syntax
modifier @interface AnnotationName {
....
....
}

Modifier: public, protected, private, abstract, static, strictfp can be used as modifiers.

@interface: To distinguish an annotation type declaration from a normal interface declaration, the keyword ‘interface’ is preceded by an at-sign (@).

AnnotationName: specifies the name of the annotation.

AnnotationBody: contains method declarations. Each method declaration defines an element of the annotation type. Method declarations must not have any parameters or a throws clause. Return types are restricted to primitives, String, Class, enums, annotations, and arrays of the preceding types. Methods can have default values.

Example
public @interface AuthorDetails{
 String author();
 String date();
 int currentVersion() default 1;
 String lastModified() default "N/A";
 String[] Reviewers();
}

Annotation uses
a.   Annotations can be used by compiler to detect errors and suppress warnings
b.   Software tools can use annotations to generate code, xml files, documentation etc., For example, Javadoc use annotations while generating java documentation for your class.
c.    Runtime processing of the application can be possible via annotations.
d.   You can use annotations to describe the constraints (Ex: @Null, @NotNull, @Max, @Min, @Email).
e.   Annotations can be used to describe type of an element. Ex: @Entity, @Repository, @Service, @Controller, @RestController, @Resource etc.,
f.     Annotation can be used to specify the behaviour. Ex: @Transactional, @Stateful
g.   Annotation are used to specify how to process an element. Ex: @Column, @Embeddable, @EmbeddedId
h.   Test frameworks like junit and testing use annotations to define test cases (@Test), define test suites (@Suite) etc.,
i.     AOP (Aspect Oriented programming) use annotations (@Before, @After, @Around etc.,)
j.     ORM tools like Hibernate, Eclipselink use annotations

Restriction on Annotations
a.   Annotations are not inherited
b.   Method declarations in annotation must not have any parameters or a throws clause.
c.    Return type of methods are restricted to primitives, String, Class, enums, annotations, and arrays of the preceding types. Methods can have default values.

Core annotations in Java
Java define number of annotations. Following table summarizes the annotations that are used frequently.

a.   @Target
b.   @Retention
c.    @Inherited
d.   @Override
f.     @Deprecated
g.   @SafeVarargs
h.   @Repeatable

Annotation Types
There are 3 categories of Annotation types
a.   Marker Annotation type
b.   Single-Element Annotation type
c.    Multi-element Annotation type

Marker Annotation type
An Annotation type with no elements is called Marker Annotation type

Example
@interface Test{


}

Single-Element Annotation type
An Annotation type with single element is called Single-Element Annotation type. By convention, the name of the element in a single-element annotation type is value.

@interface BookPublisher{
     String value();
}

Multi-element Annotation type

An Annotation type with more than one element is called Multi-Element Annotation type.
public @interface AuthorDetails {
	String author();

	String date();

	int currentVersion() default 1;

	String lastModified() default "N/A";

	String[] Reviewers();
}

An annotation type declaration contains an element whose type is also an annotation type.

@interface BookPublisher {
         String value();
}

public @interface Sample {
         BookPublisher name();
}


Where can I apply annotations?
Annotation types may be applicable in two contexts.
a.   Declaration Context
b.   Type Context

Declaration Context : Where the annotations Apply to Declarations

There are eight declaration contexts, each corresponding to an enum constant of java.lang.annotation.ElementType.

1.   Package Declarations
2.   Type Declarations : include class, interface, enum and Annotation type declarations.
3.   Method Declarations
4.   Constructor Declarations
5.   Type Parameter declarations of generic classes, methods, interfaces and constructors.
6.   Field declarations including enum constants
7.   Formal and Exception parameter declarations (A catch clause declares exactly one parameter, which is called an exception parameter)
8.   Local variable declarations (including loop variables of for statements and resource variables of try-with-resources statements)

Type contexts: where annotations apply to types used in declarations and expressions.

There are 16 type contexts, all represented by the enum constant TYPE_USE of java.lang.annotation.ElementType.

16 type contexts are

1.   A type in the extends or implements clause of a class declaration
2.   A type in the extends clause of an interface declaration
3.   The return type of a method (including the type of an element of an annotation type)
4.   A type in the throws clause of a method or constructor
5.   A type in the extends clause of a type parameter declaration of a generic class, interface, method, or constructor
6.   The type in a field declaration of a class or interface (including an enum constant)
7.   The type in a formal parameter declaration of a method, constructor, or lambda expression
8.   The type of the receiver parameter of a method
9.   The type in a local variable declaration
10.                 The type in an exception parameter declaration

In expressions

1.   A type in the explicit type argument list to an explicit constructor invocation statement or class instance creation expression or method invocation expression
2.   In an unqualified class instance creation expression, as the class type to be instantiated or as the direct superclass or direct super interface of an anonymous class to be instantiated.
3.   The element type in an array creation expression
4.   The type in the cast operator of a cast expression
5.   The type that follows the instanceof relational operator
6.   In a method reference expression , as the reference type to search for a member method or as the class type or array type to construct.

Retention Policies
Retention policy defines how long an Annotation is retained. There are three different retention policies supported in java.

RetentionPolicy.SOURCE: Annotation with this retention policy is discarded during the compilation. These annotations don't make any sense after the compilation, so they aren't written to the bytecode generated by java compiler.

Example: @Override, @SuppressWarnings

RetentionPolicy.CLASS: Annotation with this retention policy are stored in .class file during compilation but not available at run time (discarded during class loading). These are useful when doing bytecode-level post-processing. If you do not specify any retention policy it is default.

RetentionPolicy.RUNTIME: Annotation with this retention policy is not discarded  and be part of .class file, available at runtime.

Testing tool using annotations
Let’s create a simple testing tool using annotations.


Step 1: Create Test annotation with retention policy RUNTIME.


Test.java
package com.sample.app.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {

}

Step 2: Create TestClass like below.


TestClass.java
package com.smaple.app;

import com.sample.app.annotations.Test;

public class TestClass {
	@Test
	public static void m1() {
	}

	public static void m2() {
	}

	@Test
	public static void m3() {
		throw new RuntimeException("Boom");
	}

	public static void m4() {
	}

	@Test
	public static void m5() {
	}

	public static void m6() {
	}

	@Test
	public static void m7() {
		throw new RuntimeException("Crash");
	}

	public static void m8() {
	}
}

Any method defined with @Test annotation is treated as test case.

Step 3: Lets run all the tesy cases by parsing the annotation.


App.java
package com.smaple.app;

import java.lang.reflect.Method;

import com.sample.app.annotations.Test;

public class App {
	public static void main(String[] args) throws Exception {
		String classToTest = TestClass.class.getCanonicalName();
		
		int passed = 0, failed = 0, total = 0;
		for (Method m : Class.forName(classToTest).getMethods()) {
			if (m.isAnnotationPresent(Test.class)) {
				try {
					total++;
					m.invoke(null);
					passed++;
				} catch (Throwable ex) {
					System.out.printf("Test %s failed: %s %n", m, ex.getCause());
					failed++;
				}
			}
		}
		System.out.printf("Total %d, Passed: %d, Failed %d%n", total, passed, failed);
	}
}


Run App.java, you will see below messages in console.
Test public static void com.smaple.app.TestClass.m3() failed: java.lang.RuntimeException: Boom 
Test public static void com.smaple.app.TestClass.m7() failed: java.lang.RuntimeException: Crash 
Total 4, Passed: 2, Failed 2

You may like




1 comment: