Java 注解

Java 注解其实大家不陌生,使用 spring、mybatis 这样的框架时,会有许多的注解,那注解到底是什么?

java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用

注解 (annotation) 是在实际的源代码级别保存所有的信息,而不是某种注释性的文字 (comment)

常见注解

Java 注解是从 Java 5 引入的,也称作 元数据,即描述数据的数据

Java SE5 内置了三种标准注解:

  • @Override,表示当前的方法定义将覆盖超类中的方法。拼写错误或方法签名对不上覆盖的方法,编译器会错误提示
  • @Deprecated,表示过时的方法,表示被弃用的代码,使用了注解为它的元素编译器将发出警告
  • @SuppressWarnings,关闭不当编译器警告信息。

基本语法

要学习注解,我们就必须要能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法。

定义注解格式: public @interface 注解名 {定义体}

定义注解

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
}

注解的定义看起来很像接口的定义,事实上,注解也会被编译成 class 文件

定义注解时,会需要一些 元注解(meta-annotation),如 @Target 和 @Retention。在注解中,一般会有一些元素(参数)以表示某些值,分析处理注解时,可以去利用这些值。没有元素的注解为 标记注解(marker annotation)

使用注解

1
2
3
4
5
6
public class TestExample{
@Test
void test(){
...
}
}

被注解的方法和其他方法没有区别,如上,注解@Test 可以和修饰符共同作用于方法,从语法角度,注解的使用方式与修饰符一样

元注解

元注解是用来注解其他的注解,也就是我们自定义注解需要元注解

@Target
描述该注解的使用范围,可能的ElementType参数有:

  • CONSTRUCTOR:构造器的声明
  • FIELD:域声明(包括enum实例)
  • LOCAL_VARIABLE:局部变量声明
  • METHOD:方法声明
  • PACKAGE:包声明
  • PARAMETER:参数声明
  • TYPE:类、接口(包括注解类型)或enum声明 |

@Retention
表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:

  • SOURCE:注解将被编译器丢弃
  • CLASS:注解在class文件中可用,但会被VM丢弃
  • RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息 |

@Documented
将注解包含在Javadoc中

@Inherited
允许子类继承父类中的注解 |

注解元素

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

注解中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

注解类型里面的元素该怎么设定:

  1. 只能用 public 或默认(default)这两个访问权修饰

  2. 只能用基本类型(byte,short,char,int,long,float,double,boolean)八种基本数据类型和 String, Enum, Class, Annotation 等数据类型,以及这些类型的数组

  3. 如果只有一个参数成员,最好把参数名称设为 value(注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值)

默认值限制

注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null

1
2
3
4
5
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitName {
String value() default "";
}
1
2
3
4
5
6
7
8
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitColor {

public enum Color{ BULE,RED,GREEN};

Color fruitColor() default Color.GREEN;
}
1
2
3
4
5
6
7
8
9
10
public class Apple {

@FruitName("Apple")
private String appleName;

@FruitColor(fruitColor=Color.RED)
private String appleColor;

...
}

注解处理器

注解的本质其实是一个继承了 java.lang.annotation.Annotation 接口的接口

The common interface extended by all annotation types

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建和使用注解处理器。Java5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。

注解处理器类库(java.lang.reflect.AnnotatedElement):

Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  1. Class:类定义
  2. Constructor:构造器定义
  3. Field:累的成员变量定义
  4. Method:类的方法定义
  5. Package:类的包定义

当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,可以使用反射获取Annotation 信息。AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  • 方法1: T getAnnotation(Class annotationClass):返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

  • 方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。   

  • 方法3:boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.   

  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解。(如果没有注解直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){

Field[] fields = clazz.getDeclaredFields();

for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)) {
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
System.out.println(fruitName.value());
} else if(field.isAnnotationPresent(FruitColor.class)) {
FruitColor fruitColor= (FruitColor)field.getAnnotation(FruitColor.class);
System.out.println(fruitColor.fruitColor().toString());
}
}
}
}