Java 注解初探

之前在用 Spring 的时候,也没少使用注解,但是对于注解并没有深入去了解,今天趁着有时间就了解一下。

注解 Annotation

注解是 JDK 1.5 中引入的新技术

源代码的元数据

通俗来讲 相当于生活中的标签(注解时代码的标签),标签本身是没有意义的,标签需要依赖与其他元素的存在

注解的本质:

  1. 附属品,依赖于其他元素(方法,类,属性,接口,包等)存在
  2. 需要外部程序解析来产生作用

作用:

  1. 简化配置
  2. 增加代码的可读性
  3. 提高代码的可维护性

注解的分类:

  • 按照运行机制分类:
级别 存在范围 功能与用途
SOURCE 源码级别 注解只存在源码中 功能是与编译器交互,用于代码检测。如@Override,@SuppressWarings。额外效率损耗发生在编译时
CLASS 字节码级别 注解存在源码与字节码文件中 主要用于编译时生成额外的文件,如XML,Java文件等,但运行时无法获得。这个级别需要添加JVM加载时候的代理(javaagent),使用代理来动态修改字节码文件
RUNTIME 运行时级别 注解存在源码,字节码与Java虚拟机中 主要用于运行时反射获取相关信息
  1. source 源码注解
  2. class 编译时注解 (@Override、@Deprecated、@SuppressWarnings)
  3. runtime 运行时注解 (@Autowired)
  • 按照来源来分
  1. JDK内置注解 (@Override、@Deprecated、@SuppressWarnings)
  2. 第三方注解 (事务,缓存等)
  3. 自定义注解

JDK 内置的注解

JDK 中常见的注解有一般有三种:

  1. @Override:此注解能够实现编译时检查,当某方法前边添加此注解时,表示此方法为重写父类中的方法。如果此方法不是父类的方法。例如本来想重写toString,却写成了tostring,则编译无法通过,会提示错误。
  2. @Deprecated:此注解是对不应该或被弃用的方法进行标识,当开发者使用时会给予警告提示。这个功能在一些 jar 包,框架中经常可以看到,还保留这些方法是为了向前兼容。
  3. @SuppressWanings:此注解的作用是告诉编译器对被批注的元素内部的某些警告保持静默,此注解里边需要制定参数,比如 unused、all、 deprecation 等。

其中 @Override@SuppressWanings 比较常见,@Deprecated 平时则见到的比较少,这里介绍一下。

假如我定义了一个 Student 接口,其中有一个 call 方法后来觉得不太合适,但是由于可能有其他代码在使用这个接口,我不能直接删掉,这个时候就可以使用 @Deprecated 接口对其进行标记。

Student 接口:

1
2
3
4
5
public interface Student {

@Deprecated // 感觉这个方法不太合适,但是不能直接删除
public void call();
}

这时候其他人的其他代码如果使用了这个接口,将会产生警告。

annotation_deprecated_warning

实现该接口:

1
2
3
4
5
public class CollegeStudents implements Student {
public void call() {
System.out.println("CollegeStudents call.");
}
}

同时在执行时也将产生警告信息。

Main 方法:

1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Student student = new CollegeStudents();
student.call();
}
}

annotation_deprecated_main

执行时的警告信息:

annotation_main_running

这个时候如果我们需要忽略这个警告信息可以使用 @SuppressWanings 注解来压制警告:

1
2
3
4
5
6
7
public class Test {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
Student student = new CollegeStudents();
student.call();
}
}

压制警告后:

annotation_main_suppress_warnings

元注解

元注解指的是注解的注解。

  • Retention : 定义注解的生命周期,可选值为 source、class、runtime。
  • Documented : 文档化注解,会被 JacaDoc 工具文档化。
  • Inherited : 注解是自动继承的,想让一个类和他的子类都包含某个注解,就可以使用它来修饰这个注解。
  • Target : 通过 ElementType 来指定注解可使用范围的枚举集合,限制注解的使用范围。

自定义注解

1、使用@interface关键字定义注解。
2、成员必须以无参数无异常的方式进行声明。
3、可以使用 default 为成员指定一个默认值。
4、成员类型是受限的,合法类型除了基本数据类型以外,还包括 String、Class、Annotation、Enumeration。
5、如果注解类只有一个成员,则成员名是value(),使用时才可以省略成员名和等号。
6、注解类可以没有成员,没有成员的注解成为标识注解

解读 @SuppressWanings 注解

在自定义注解之前,我们先来看一下系统内置注解 @SuppressWanings 的代码。

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
package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
* Indicates that the named compiler warnings should be suppressed in the
* annotated element (and in all program elements contained in the annotated
* element). Note that the set of warnings suppressed in a given element is
* a superset of the warnings suppressed in all containing elements. For
* example, if you annotate a class to suppress one warning and annotate a
* method to suppress another, both warnings will be suppressed in the method.
*
* <p>As a matter of style, programmers should always use this annotation
* on the most deeply nested element where it is effective. If you want to
* suppress a warning in a particular method, you should annotate that
* method rather than its class.
*
* @author Josh Bloch
* @since 1.5
* @jls 4.8 Raw Types
* @jls 4.12.2 Variables of Reference Type
* @jls 5.1.9 Unchecked Conversion
* @jls 5.5.2 Checked Casts and Unchecked Casts
* @jls 9.6.3.5 @SuppressWarnings
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}

可以发现:

  • 注解的定义使用了 @interface 关键字。
  • 该注解使用了 @Target @Retention 两个注解
  • 有一个长得比较奇怪的属性 String[] value();

String[] value(); 看起来像是一个方式,其实是一个属性。
该 String 数组可以接受的参数如下:

关键字 用途
all to suppress all warnings
boxing to suppress warnings relative to boxing/unboxing operations
cast to suppress warnings relative to cast operations
dep-ann to suppress warnings relative to deprecated annotation
deprecation to suppress warnings relative to deprecation
fallthrough to suppress warnings relative to missing breaks in switch statements
finally to suppress warnings relative to finally block that don’t return
hiding to suppress warnings relative to locals that hide variable
incomplete-switch to suppress warnings relative to missing entries in a switch statement (enum case)
nls to suppress warnings relative to non-nls string literals
null to suppress warnings relative to null analysis
rawtypes to suppress warnings relative to un-specific types when using generics on class params
restriction to suppress warnings relative to usage of discouraged or forbidden references
serial to suppress warnings relative to missing serialVersionUID field for a serializable class
static-access to suppress warnings relative to incorrect static access
synthetic-access to suppress warnings relative to unoptimized access from inner classes
unchecked to suppress warnings relative to unchecked operations
unqualified-field-access to suppress warnings relative to field access unqualified
unused to suppress warnings relative to unused code

参考:https://help.eclipse.org

再来说一下 @Target@Retention 注解。

@Target

@Target 的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}

我们可以看到,其中有个 ElementType[] value() 属性值。

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
32
33
34
35
36
37
38
39
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,

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

/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}

源代码注释已经写的很清楚了,不同的值代表了不同的使用场景。

@Retention

@Retention 的源代码如下:

1
2
3
4
5
6
7
8
9
10
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

我们再来看看 RetentionPolicy value()

RetentionPolicy 也为枚举类型,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}
  • SOURCE 表示编译器在编译时将丢弃注解,注解仅存在于代码中。
  • CLASS 表示作用于字节文件中会保留,但是在 JVM 运行该类时不会保留,这个值是默认值。
  • RUNTIME 表示注解在运行时可以通过反射获取。

实现注解

下面就实现一个简单的自定义注解。

注解的基本语法

定义注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.METHOD;

@Target({METHOD}) // 注解可以修饰的对象(这里设置为可以修饰方法)
@Retention(RetentionPolicy.RUNTIME) // 注解保留的策略,一般自定义注解都将使用 RUNTIME
public @interface MyAnnotation {
/**
* 我们在定义注解元素(属性)时,经常使用空字符串、0 作为默认值,也经使用负数(比如 -1)来表示不存在的含义
*/
String name() default "Default_Str"; // 定义一个属性(参数),并设置一个默认值,这里将其设置为 Default_Str

String[] device() default {}; // 也可以定义数组
}

注释里边已经说的很清楚了,这里就不再解释了。

使用注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;

public class CustomTest {
@Test
@MyAnnotation() // 这里我们使用注解时未传入参数,将会使用默认值 Default_Str
public void testDefault() {
System.out.println("testDefault");
}

@Test
@MyAnnotation(name = "CodingFanlt", device = {"MacBook Pro", "iPhone7", "iPad2017"})
public void testParameter() {
System.out.println("testParameter");
}
}

执行以上测试代码,结果如下:

1
2
3
4
testParameter
testDefault

Process finished with exit code 0

我们发现似乎并没有什么卵用,我们的注解并没有对原有的代码产生什么影响。

其实,这就是注解的本质,它本身并没有什么作用,要想让它起作用,则必须有其他代码对注解进行解析和执行。

一般我们使用 Java 反射机制Spring AOP来解析和执行相应的注解。

使用反射来解析注解

我们再写一个类来使用反射来获取注解。

Reflection 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.junit.Test;
import java.lang.reflect.Method;

public class Reflection {
@Test
public void reflectionGetAnnotation() {
Class clazz = CustomTest.class; // 获取该类
Method[] methods = clazz.getMethods(); // 获取方法列表

Class clazzAnno = MyAnnotation.class; // 获取注解类

for (Method method : methods) {
if (method.isAnnotationPresent(clazzAnno)){ // 如果该方法上有该注解类的注解
System.out.println(method.getAnnotation(clazzAnno)); // 打印相关注解
}
}

}
}

执行结果如下:

1
2
3
4
@annotation.custom_annotation.MyAnnotation(name=Default_Str, device=[])
@annotation.custom_annotation.MyAnnotation(name=CodingFanlt, device=[MacBook Pro, iPhone7, iPad2017])

Process finished with exit code 0

可以看到,我们获取到了该类的方法上的注解以及其内容。

既让注解和参数都拿到了,想怎么玩就看心情喽。