Java 注解 (Annotation)
注解是什么
《Java 核心技术 卷2》
中对注解的说明:
注解是那些插入到源代码中使用其他工具对其进行处理的标签。这些工具可以在源代码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。
注解不会改变程序的编译方式。Java 编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。
JavaGuide 网站上对注解的说明,个人认为这个说明比较通俗易懂:
Annotation (注解)是 Java 5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
注解本质上是一个继承了
Annotaion
的特殊接口。
Java 为什么要支持注解
如果只定义注解,然后在代码中插入注解,这样并没有什么作用,注解只有被程序解析处理之后才会起作用。
《Java 编程的逻辑》
中两段话很好地说明了注解的作用:
注解似乎有某种神奇的力量,通过简单的声明,就可以达到某种效果。这是声明式编程风格,这种风格降低了编程的难度,为应用程序员提供了更为高级的语言,使得程序员可以在更高的抽象层次上思考和解决问题,而不是限于底层的细节实现。
注解提升了 Java 语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现,借助反射实现通用功能,提供注解给应用程序员使用,应用程序员可以专注于应用功能,通过简单的声明式注解与框架/库进行协作。
如何使用注解
定义注解
注解接口
如果是使用框架/库提供的注解,就不需要自己定义注解。但是也需要能看懂框架/库提供的注解的定义,才能知道注解应该加在什么地方。
注解是由注解接口 @interface
来定义的,例如下面定义了一个名为 Label
注解:
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Label {
String value() default "";
}
所有的注解接口都隐式地扩展自 java.lang.annotation.Annotation
接口,这个接口是一个常规接口,不是注解接口。
package java.lang.annotation;
/**
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
元注解
定义注解时会用到元注解,例如上面例子中的 @Target
和 @Retention
,这是两个必须要声明的元注解(如果没有声明这两个注解其实使用的是默认值),元注解是用来定义注解的注解。
@Target
表示注解的目标,即表示注解可以应用到哪些项上,取值是枚举类型 ElementType
,主要可选值有:
元素类型 | 注解适用场合 |
---|---|
TYPE | 类(包括枚举)、接口(包括注解) |
FIELD | 成员变量(包括枚举常量) |
METHOD | 方法 |
CONSTRUCTOR | 构造方法 |
PARAMETER | 方法中的参数 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解 |
@Target
有一个值时写为:@Target(ElementType.FIELD)
,有多个值时写为:@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
。
如果没有声明 @Target
,默认为适用所有类型。
@Retention
表示注解信息保留到什么时候,取值只能有 1 个,类型为枚举类型 RetentionPolicy
,可选值只有 3 个:
枚举值 | 描述 |
---|---|
SOURCE | 只在源代码中保留,编译器将代码编译为字节码文件之后就会丢掉。例如 JDK 提供的 @Override,Lombok 提供的 @Getter、@Setter 等注解。 |
CLASS | 保留在字节码文件中,但 Java 虚拟机不需要将它们载入。 |
RUNTIME | 一直保留到运行时,通过反射 API 可获得它们。例如 Spring 提供的注解、通过反射解析的自定义注解。 |
如果没有声明 @Retention
,默认为CLASS。
元注解除了 @Target
和 @Retention
,常用的还有:
- @Document,表示注解信息包含到 Javadoc 生成的文档中。
- @Inherited,表示如果某个注解用@Inherited声明了,父类使用了此注解,则子类就自动有了此注解。
注解元素
例子中 String value() default "";
就是注解元素。
注解元素的类型为下列之一:
- 基本类型
- String
- Class
- 枚举类型
- 注解类型
- 由前面所述类型组成的数组
对于名为 value
的注解元素赋值时,如果没有其它注解元素要赋值,就可以忽略掉元素名称 value
和等号,只写 value
元素的值,例如 @Label("姓名") 等价于 @Label(value = "姓名")。
在代码中使用注解
根据注解 @Target
的定义,注解应用于哪些目标上,就在对应目标上插入注解,并根据需要给注解元素赋值。例如上面定义的 @Label
注解可以应用于成员变量上,就可以在类的成员变量上使用此注解,如:
public class Student {
@Label("姓名")
private String name;
@Label("分数")
private Double score;
public Student(String name, Double score) {
this.name = name;
this.score = score;
}
}
解析注解
如果是框架/库提供的注解,注解的解析由框架/库完成,我们只需要根据注解的功能在代码中插入注解即可。如果是自定义注解,我们就需要通过对注解进行解析。类 Class、Field、Method、Constructor 都实现了 java.lang.reflect.AnnotatedElement
接口,有如下方法:
/**
* @author Josh Bloch
*/
public interface AnnotatedElement {
/**
* Returns true if an annotation for the specified type
* is <em>present</em> on this element, else false. This method
* is designed primarily for convenient access to marker annotations.
*/
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
/**
* Returns this element's annotation for the specified type if
* such an annotation is <em>present</em>, else null.
*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/**
* Returns annotations that are <em>present</em> on this element.
*/
Annotation[] getAnnotations();
/**
* Returns annotations that are <em>associated</em> with this element.
*/
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
T[] result = getDeclaredAnnotationsByType(annotationClass);
if (result.length == 0 && // Neither directly nor indirectly present
this instanceof Class && // the element is a class
AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
Class<?> superClass = ((Class<?>) this).getSuperclass();
if (superClass != null) {
// Determine if the annotation is associated with the
// superclass
result = superClass.getAnnotationsByType(annotationClass);
}
}
return result;
}
/**
* Returns this element's annotation for the specified type if
* such an annotation is <em>directly present</em>, else null.
*/
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
// Loop over all directly-present annotations looking for a matching one
for (Annotation annotation : getDeclaredAnnotations()) {
if (annotationClass.equals(annotation.annotationType())) {
// More robust to do a dynamic cast at runtime instead
// of compile-time only.
return annotationClass.cast(annotation);
}
}
return null;
}
/**
* Returns this element's annotation(s) for the specified type if
* such annotations are either <em>directly present</em> or
* <em>indirectly present</em>. This method ignores inherited
* annotations.
*/
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
return AnnotationSupport.
getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
collect(Collectors.toMap(Annotation::annotationType,
Function.identity(),
((first,second) -> first),
LinkedHashMap::new)),
annotationClass);
}
/**
* Returns annotations that are <em>directly present</em> on this element.
* This method ignores inherited annotations.
*/
Annotation[] getDeclaredAnnotations();
}
可以使用 Filed 类的 getAnnotation
方法对上面定义的 @Label
进行解析:
public class SimpleFormatter {
public static String format(Object obj) {
try {
Class<?> cls = obj.getClass();
StringBuilder sb = new StringBuilder();
for (Field f : cls.getDeclaredFields()) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
Label label = f.getAnnotation(Label.class);
String name = label != null ? label.value() : f.getName();
Object value = f.get(obj);
sb.append(name + ": " + value + "\n");
}
return sb.toString();
} catch (IllegalAccessException e){
throw new RuntimeException(e);
}
}
}
这样在上一步使用注解时就可以用 SimpleFormatter#format 方法对一个对象输出成员变量标记后的信息,如:
Student student = new Student("张三", 80.9d);
System.out.println(SimpleFormatter.format(student));
输出为:
姓名:张三
分数:80.9
Spring 中的注解说明
组合注解 (composed annotation)原理
@RestController
就是一个组合注解,使用它就相当于同时使用了 @Controller
和 @ResponseBody
两个注解。
/**
* A convenience annotation that is itself annotated with
* {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
* <p>
* Types that carry this annotation are treated as controllers where
* {@link RequestMapping @RequestMapping} methods assume
* {@link ResponseBody @ResponseBody} semantics by default.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
Spring 解析被 @RestController 注解的类时,可以先获取到 @RestController 注解,再获取 @RestController 的注解,就能获取到 @Controller 和 @ResponseBody 注解,通过递归就能获取到一个类上的所有注解。获取注解的注解时,要用 annotation.annotationType().getAnnotations()
。
for (Annotation annotation : f.getAnnotations()) {
for (Annotation annotation1 : annotation.annotationType().getAnnotations()) {
System.out.println("annotation1 = " + annotation1);
}
}
一个注解要能在加在别的注解上面,它的 @Target 必须是 ElementType.TYPE 或者 ElementType.ANNOTATION_TYPE,否则编译器就会报错。
上面 @AliasFor(annotation = Controller.class) 表示 @RestController 的 value 值会同时设置 @Controller 的value 值,@AliasFor 同时也可以设置同一个注解内两个不同的注解元素,表示这两个注解元素的值会相互设置。
参考资料
-
《Java 核心技术 卷2》 Cay S. Horstmann 著
-
《Java 编程的逻辑》马俊昌 著
-
JavaGuide 网站:https://javaguide.cn
-
https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model