Java 注解

words: 1.5k    views:    time: 7min

注解为在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的获取和使用这些信息,注解其实可以理解为代码的元数据(元数据是关于数据的数据)。

在注解之前,xml已经被广泛用于元数据,其好处是元数据与代码可以完全解耦。但是随着项目的不断膨胀,xml维护将变得越来越麻烦,因此对于那些并不会发生变化的元数据,使用xml配置只会带来累赘。而注解的方式相当于在代码上添加一些标记,并且可以参与编译以便在运行时能够获取。其好处主要是省去了xml文件的维护,另外使代码更加干净易读,以及提供编译期检查,但是耦合度增加了,即牺牲了xml配置的灵活性。

1. 注解定义

定义注解需要使用元注解来声明(定义注解的注解),包括:

  1. @Retention:标明注解在什么阶段可以保留,即源码阶段,class阶段以及运行时阶段,如下
java.lang.annotation.RetentionPolicy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum RetentionPolicy {
/** Annotations are to be discarded by the compiler. */
SOURCE,

/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default behavior.
*/
CLASS,

/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
  1. @Target:标明注解可以用于什么地方,具体如下
java.lang.annotation.RetentionPolicy.ElementType
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
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,

/** Field declaration (includes enum constants) */
FIELD,

/** Method declaration */
METHOD,

/** Formal parameter declaration */
PARAMETER,

/** Constructor declaration */
CONSTRUCTOR,

/** Local variable declaration */
LOCAL_VARIABLE,

/** Annotation type declaration */
ANNOTATION_TYPE,

/** Package declaration */
PACKAGE,

/** @since 1.8 Type parameter declaration */
TYPE_PARAMETER,

/** @since 1.8 Use of a type */
TYPE_USE
}
  • TYPE_PARAMETER用来注解类型参数,例如
1
2
3
4
5
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParameterAnnotation {

}
1
2
3
4
5
public class TypeParameterClass<@TypeParameterAnnotation T> {
public <@TypeParameterAnnotation U> T foo(T t) {
return null;
}
}
  • TYPE_USE能标注任何类型名称,例如
1
2
3
4
5
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeUseAnnotation {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {

}
}

public static void main(String[] args) throws Exception {
TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
typeUseClass.foo("");
List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();
@TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
}
  1. @Inherited:允许子类继承父类的注解
  2. @Documented:包含到在javadoc中

另外,对于注解中元素的数据类型,支持所有基本类型,即int,float,boolean,byte,double,char,long,short,以及String,Class,enum,Annotation和它们的数组。

2. 注解解析

如果在运行时注解不进行解析,那么注解也没有什么用。解析注解其实很容易,只要拿到被注解标记的代码元数据,再通过java api提供的类似getAnnotation(xx.class)便可以获取。至于如何获取哪些类是被标识了注解的,可以通过一些第三方工具进行扫描,比如

1
2
3
4
5
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
1
2
Reflections reflections = new Reflections("com.test");
Set<Class<?>> set = reflections.getTypesAnnotatedWith(TestAnnotation.class);

但是实际工作中,有时我们还希望能够在注解中指定路径,那这时就矛盾了,路径在注解中指定,而获取注解首先要指定路径,对于这个问题,spring给出了解决方案,具体可以参考笔记:spring. 应用上下文(ApplicationContext)

3. 注解实现

注解在编译后,编译器会自动使其继承Annotation接口,因此所有注解的实际上都是一个Annotation,但是Annotation本身以及手动继承它的接口都不是注解,如其注释所述:

The common interface extended by all annotation types. Note that an interface that manually extends this one does not define
an annotation type. Also note that this interface does not itself define an annotation type.

可以定义个注解,然后看下它编译后的class文件

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

int value() default 0;
}
javap -c -v TestAnnotation.class
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
public interface example.fom.TestAnnotation extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #2 // example/fom/TestAnnotation
#2 = Utf8 example/fom/TestAnnotation
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Class #6 // java/lang/annotation/Annotation
#6 = Utf8 java/lang/annotation/Annotation
#7 = Utf8 value
#8 = Utf8 ()I
#9 = Utf8 AnnotationDefault
#10 = Integer 0
#11 = Utf8 SourceFile
#12 = Utf8 TestAnnotation.java
#13 = Utf8 RuntimeVisibleAnnotations
#14 = Utf8 Ljava/lang/annotation/Target;
#15 = Utf8 Ljava/lang/annotation/ElementType;
#16 = Utf8 TYPE
#17 = Utf8 Ljava/lang/annotation/Retention;
#18 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#19 = Utf8 RUNTIME
{
public abstract int value();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#10}
SourceFile: "TestAnnotation.java"
RuntimeVisibleAnnotations:
0: #14(#7=[e#15.#16])
1: #17(#7=e#18.#19)

可以看到注解本身也是一个接口,那么它是如何实例化和调用的呢,这里应该能想到动态代理了,关于动态代理可以参考相关笔记:设计模式. 代理模式

1
2
3
4
5
6
7
@TestAnnotation
public class Test {
public void test(){
TestAnnotation annotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println(annotation.value());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public void test();
Code:
0: ldc #1 // class example/fom/Test
2: ldc #15 // class example/fom/TestAnnotation
4: invokevirtual #17 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
7: checkcast #15 // class example/fom/TestAnnotation
10: astore_1
11: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream;
14: aload_1
15: invokeinterface #29, 1 // InterfaceMethod example/fom/TestAnnotation.value:()I
20: invokevirtual #33 // Method java/io/PrintStream.println:(I)V
23: return

指令invokeinterface也说明了annotation.value()是对接口的调用,如果debug,可以看到上面annotation的实例类型为com.sun.proxy.$Proxy1,并且其中InvocationHandler的实现为AnnotationInvocationHandler,它主要是将注解的方法实例化为Method实例并进行缓存,然后在invoke调用的时候实际上就是根据方法名调用对应的Method。


参考:

  1. https://www.cnblogs.com/throwable/p/9747595.html