Java Built-in Annotations

  • @Override – When we want to override a method of Superclass, we should use this annotation to inform compiler that we are overriding a method. So when superclass method is removed or changed, compiler will show error message.
  • @Deprecated – when we want the compiler to know that a method is deprecated, we should use this annotation. Java recommends that in javadoc, we should provide information for why this method is deprecated and what is the alternative to use.
  • @SuppressWarnings – This is just to tell compiler to ignore specific warnings they produce, for example using raw types in generics. It’s retention policy is SOURCE and it gets discarded by compiler.

Meta annotations used in custom annotations

Target

@Target – indicates the kinds of program element to which an annotation type is applicable. Some possible values are TYPE, METHOD, CONSTRUCTOR, FIELD etc. If Target meta-annotation is not present, then annotation can be used on any program element. The java.lang.annotation.ElementType enum declares many constants to specify the type of element where annotation is to be applied such as TYPE, METHOD, FIELD etc. Let's see the constants of ElementType enum:

Element Types Where the annotation can be applied
TYPE class, interface or enumeration
FIELD fields
METHOD methods
CONSTRUCTOR constructors
LOCAL_VARIABLE local variables
ANNOTATION_TYPE annotation type
PARAMETER parameter

Example to specify annoation for a class

@Target(ElementType.TYPE)  
@interface MyAnnotation{  
    int value1();  
    String value2();  
}  
1
2
3
4
5

Example to specify annotation for a class, methods or fields

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})  
@interface MyAnnotation{  
    int value1();  
    String value2();  
}  
1
2
3
4
5

Retention

@Retention – indicates how long annotations with the annotated type are to be retained. It takes RetentionPolicy argument whose Possible values are SOURCE, CLASS and RUNTIME

RetentionPolicy Availability
RetentionPolicy.SOURCE refers to the source code, discarded during compilation. It will not be available in the compiled class.
RetentionPolicy.CLASS refers to the .class file, available to java compiler but not to JVM . It is included in the class file.
RetentionPolicy.RUNTIME refers to the runtime, available to java compiler and JVM .

Example to specify the RetentionPolicy

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
@interface MyAnnotation{  
   int value1();  
   String value2();  
}  
1
2
3
4
5
6

Inherited

  • @Inherited – indicates that an annotation type is automatically inherited. If user queries the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class’s superclass will automatically be queried for the annotation type. This process will be repeated until an annotation for this type is found, or the top of the class hierarchy (Object) is reached. By default, annotations are not inherited to subclasses. The @Inherited annotation marks the annotation to be inherited to subclasses.
@Inherited  
@interface ForEveryone { }//Now it will be available to subclass also  
  
@interface ForEveryone { }  
class Superclass{}  
  
class Subclass extends Superclass{}  
1
2
3
4
5
6
7

@Documented

  • @Documented – indicates that elements using this annotation should be documented by javadoc and similar tools. This type should be used to annotate the declarations of types whose annotations affect the use of annotated elements by their clients. If a type declaration is annotated with Documented, its annotations become part of the public API of the annotated elements.

Type of annotations

Marker Annotation

An annotation that has no method, is called marker annotation. For example:

@interface MyAnnotation{}  
1

The @Override and @Deprecated are marker annotations.

Single-Value Annotation

An annotation that has one method, is called single-value annotation. For example:

@interface MyAnnotation{  
int value();  
}  
1
2
3

We can provide the default value also. For example:

@interface MyAnnotation{  
int value() default 0;  
}  
1
2
3

How to apply Single-Value Annotation

@MyAnnotation(value=10)
1

Multi-Value Annotation

An annotation that has more than one method, is called Multi-Value annotation. For example:

@interface MyAnnotation{  
   int value1();  
   String value2();  
   String value3();  
}  
1
2
3
4
5

We can provide the default value also. For example:

@interface MyAnnotation{  
    int value1() default 1;  
    String value2() default "";  
    String value3() default "xyz";  
}  
1
2
3
4
5

How to apply Multi-Value Annotation

@MyAnnotation(value1=10,value2="hello",value3="world")  

1
2

Custom Annotation Example1

import java.lang.annotation.*;  
import java.lang.reflect.*;  
  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
@interface MyAnnotation{  
int value();  
}  
  
//Applying annotation  
class Hello{  
@MyAnnotation(value=10)  
public void sayHello(){System.out.println("hello annotation");}  
}  
  
//Accessing annotation  
class TestCustomAnnotation1{  
public static void main(String args[])throws Exception{  
  
Hello h=new Hello();  
Method m=h.getClass().getMethod("sayHello");  
  
MyAnnotation manno=m.getAnnotation(MyAnnotation.class);  
System.out.println("value is: "+manno.value());  
}}  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Example2

annoation define

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodInfo {
    String author() default "Freshal";
    String date();
    int revision() default 1;
    String comments();
}
1
2
3
4
5
6
7
8
9
10

class using annotation

public class MyObject {
    @Override
    @MethodInfo(author = "freshal", comments = "Main method", date = "Nov 17 2012", revision = 1)
    public String toString() {
        return "Overriden toString method";
    }

    @Deprecated
    @MethodInfo(comments = "deprecated method", date = "Nov 17 2012")
    public static void oldMethod() {
        System.out.println("old method, don't use it.");
    }

    @SuppressWarnings({"unchecked", "deprecation"})
    @MethodInfo(author = "freshal", comments = "Main method", date = "Nov 17 2012", revision = 10)
    public static void genericsTest() throws FileNotFoundException {
        List l = new ArrayList();
        l.add("abc");
        oldMethod();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class Main {

    public static void main(String[] args) {

        Class<MyObject> obj = MyObject.class;
        try {
            for (Method method : obj.getDeclaredMethods()) {
                // checks if MethodInfo annotation is present for the method
                if (method
                        .isAnnotationPresent(com.freshal.example.annotation.MethodInfo.class)) {
                    try {
                        // iterates all the annotations available in the method
                        for (Annotation anno : method.getDeclaredAnnotations()) {
                            System.out.println("Annotation in Method "
                                    + method + ":" + anno);
                        }
                        MethodInfo methodAnno = method
                                .getAnnotation(MethodInfo.class);
                        if (methodAnno.revision() == 1) {
                            System.out.println("Method with revision no 1 = "
                                    + method);
                        }

                    } catch (Throwable ex) {
                        ex.printStackTrace();
                    }
                }
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Example3:Spring Custom Annotation

Annotation Class


@Retention(RetentionPolicy.RUNTIME) //Annotation will work at RUNTIME
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) //Annotation can be applied either on METHOD or on FIELD
@Constraint(validatedBy = CustomValidator.class) //CustomValidator class will validate the values
public @interface SupportedValues 
{
	String message() default "Values are not supported";
	
	String[] values();
	
	Class<?>[] groups() default {}; //Required by Constraint
	
	Class<? extends Payload>[] payload() default {}; //Required by Constraint
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Validator Class

public class CustomValidator implements ConstraintValidator<SupportedValues, String>{

	public String message;
	public String[] values;
	
	@Override
	public void initialize(SupportedValues supportedValues) 
	{
		this.message = supportedValues.message();
		this.values = supportedValues.values();
	}

	@Override
	public boolean isValid(String value, ConstraintValidatorContext arg1) 
	{
		List<String> lstValues = Arrays.asList(values);
		
		return value != null && !value.isEmpty() && lstValues.contains(value);
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Class using Annotation

@Component
public class ConfigProp {

	@SupportedValues(message = "Invalid values found for dbType", values = {"ORACLE", "MYSQL", "SQL"})
	private String dbType;

	public String getDbType() {
		return dbType;
	}

	public void setDbType(String dbType) {
		this.dbType = dbType;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Spring Boot class for testing

@SpringBootApplication
public class SpringBootMain implements CommandLineRunner
{
	public static void main(String[] args) {
		SpringApplication.run(SpringBootMain.class, args);
	}

	@Override
	public void run(String... args) throws Exception 
	{

		String dbType = "POSTGRES";
		
		ConfigProp property = new ConfigProp();
		property.setDbType(dbType);//setting unsupported values here
		
		//Inbuilt class that is used to validate data
		ValidatorFactory validator = Validation.buildDefaultValidatorFactory();
		Set<ConstraintViolation<ConfigProp>> validationErrors = validator.getValidator().validate(property);
		if(!validationErrors.isEmpty()) //If there are some errors then print those
		{
			for(ConstraintViolation<ConfigProp> invalidObj : validationErrors)
			{
				System.out.println(invalidObj.getMessage());
			}
		}
		
	}
	
	
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

Further read:Spring Annotation with AOP

https://moelholm.com/2016/10/15/spring-4-3-custom-annotations/

元注解

注解作用:每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。 Java提供了四种元注解,专门负责新注解的创建工作。

比如Junit3和Junit4 ,比如Servlet2与Servlet3 比如Hibernate3与Hibernate4 比如Spring2之后的Spring版本,都引用注解这一机制,作用就是利用注解将一些本来重复性的工作,变成程序自动完成,简化和自动化该过程(PostScript:上述各个组件我也不是很熟悉,具体加入注解的版本是几不一定正确)。

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:

  • 1.@Target
  • 2.@Retention
  • 3.@Documented
  • 4.@Inherited

这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Target @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。 作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:

  • 1.CONSTRUCTOR:用于描述构造器
  • 2.FIELD:用于描述域
  • 3.LOCAL_VARIABLE:用于描述局部变量
  • 4.METHOD:用于描述方法
  • 5.PACKAGE:用于描述包
  • 6.PARAMETER:用于描述参数
  • 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

例子: Entity.java

/***
 *
 * 实体注解接口
 */
@Target(value = {ElementType.TYPE}) //仅应用于类、接口、enum声明、注解类型
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface Entity {
    /***
     * 实体默认firstLevelCache属性为false
     * @return boolean
     */
    boolean firstLevelCache() default false;
    /***
     * 实体默认secondLevelCache属性为false
     * @return boolean
     */
    boolean secondLevelCache() default true;
    /***
     * 表名默认为空
     * @return String
     */
    String tableName() default "";
    /***
     * 默认以""分割注解
     */
    String split() default "";
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

@Retention  @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。 作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)  取值(RetentionPoicy)有:

  • 1.SOURCE:在源文件中有效(即源文件保留)
  • 2.CLASS:在class文件中有效(即class保留)
  • 3.RUNTIME:在运行时有效(即运行时保留)
/***
 * 字段注解接口
 */
@Target(value = {ElementType.FIELD})//注解可以被添加在属性上
@Retention(value = RetentionPolicy.RUNTIME)//注解保存在JVM运行时刻,能够在运行时刻通过反射API来获取到注解的信息
public @interface Column {
    String name();//注解的name属性
}

1
2
3
4
5
6
7
8
9

-------------------------------------分割线--------------------------------------------------------- 一个完整的例子

注解:DBTable.java

package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.TYPE) // 应用于类、接口、enum、注解类型
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
  public String name() default "";
} 
1
2
3
4
5
6
7
8

注解:Constraints.java

package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.FIELD) //用于变量名
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
  boolean primaryKey() default false;
  boolean allowNull() default true;
  boolean unique() default false;
} 
1
2
3
4
5
6
7
8
9
10

注解:SQLString.java

package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
  int value() default 0;
  String name() default "";
  Constraints constraints() default @Constraints;
} 
1
2
3
4
5
6
7
8
9
10

注解:SQLInteger.java

package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.FIELD)//FIELD  用于变量名
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
  String name() default "";
  Constraints constraints() default @Constraints;
} 
1
2
3
4
5
6
7
8
9

类Member.java

package annotations.database;

@DBTable(name = "MEMBER")
public class Member {
  @SQLString(30) String firstName;
  @SQLString(50) String lastName;
  @SQLInteger Integer age;
  @SQLString(value = 30,constraints = @Constraints(primaryKey = true))
  String handle;
  
  static int memberCount;
  public String getHandle() { return handle; }
  public String getFirstName() { return firstName; }
  public String getLastName() { return lastName; }
  public String toString() { return handle; }
  public Integer getAge() { return age; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

------------------------------分割线-----------------------------------

注解处理器TableCreator.java 下面是一个注解处理器的例子,它将读取一个类文件,并检查其上的数据库注解,并生成用来创建数据库的SQL命令

package com.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.swing.SpringLayout.Constraints;

public class TableCreator
{
    public static void main(String[] args) throws Exception
    {
        if (args.length < 1)
        {
            System.out.println("arguments: annotated classes");
            System.exit(0);
        }
        for (String className : args)
        {
            Class<?> cl = Class.forName(className);
            DBTable dbTable = cl.getAnnotation(DBTable.class);
            if (dbTable == null)
            {
                System.out.println("No DBTable annotations in class " + className);
                continue;
            }
            String tableName = dbTable.name();
            // If the name is empty, use the Class name:
            if (tableName.length() < 1)
                tableName = cl.getName().toUpperCase();
            List<String> columnDefs = new ArrayList<String>();
            for (Field field : cl.getDeclaredFields())
            {
                String columnName = null;
                Annotation[] anns = field.getDeclaredAnnotations();
                if (anns.length < 1)
                    continue; // Not a db table column
                if (anns[0] instanceof SQLInteger)
                {
                    SQLInteger sInt = (SQLInteger) anns[0];
                    // Use field name if name not specified
                    if (sInt.name().length() < 1)
                        columnName = field.getName().toUpperCase();
                    else
                        columnName = sInt.name();
                    columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
                }
                if (anns[0] instanceof SQLString)
                {
                    SQLString sString = (SQLString) anns[0];
                    // Use field name if name not specified.
                    if (sString.name().length() < 1)
                        columnName = field.getName().toUpperCase();
                    else
                        columnName = sString.name();
                    columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
                }
                StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
                for (String columnDef : columnDefs)
                    createCommand.append("\n    " + columnDef + ",");
                // Remove trailing comma
                String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
                System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate);
            }
        }
    }

    private static String getConstraints(Constraints con)
    {
        String constraints = "";
        if (!con.allowNull())
            constraints += " NOT NULL";
        if (con.primaryKey())
            constraints += " PRIMARY KEY";
        if (con.unique())
            constraints += " UNIQUE";
        return constraints;
    }
} /*
   * Output: Table Creation SQL for annotations.database.Member is : CREATE
   * TABLE MEMBER( FIRSTNAME VARCHAR(30)); Table Creation SQL for
   * annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
   * VARCHAR(30), LASTNAME VARCHAR(50)); Table Creation SQL for
   * annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
   * VARCHAR(30), LASTNAME VARCHAR(50), AGE INT); Table Creation SQL for
   * annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
   * VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30) PRIMARY
   * KEY);
   */// :~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90