java 注解学习

本贴最后更新于 2106 天前,其中的信息可能已经时移世易

背景

注解(Annontation)是 Java5 中引入的新特征。其用途有:

  • 生成文档:如 @param, @return
  • 配置管理:如 spring 中的注解配置
  • 格式检查:编译时进行格式检查,如 @override

第一个例子

VehicleInfo

VehicleInfo.java

package testAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VehicleInfo {
	int id() default 0;

	String type() default "";
}

Car

Car.java

package testAnnotation;

public class Car {
	@VehicleInfo(id = 1, type = "汽车")
	private String name;

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

Train

Train.java

package testAnnotation;

public class Train {
	@VehicleInfo(id = 2, type = "火车")
	private String name;

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

VehicleUtil

VehicleUtil.java

package testAnnotation;

import java.lang.reflect.Field;

public class VehicleUtil {

	public static void getTrafficInfo(Class<?> clazz) {
		Field[] fields = clazz.getDeclaredFields();

		for (Field field : fields) {
			if (field.isAnnotationPresent(VehicleInfo.class)) {
				VehicleInfo info = (VehicleInfo) field.getAnnotation(VehicleInfo.class);
				System.out.println("编号: " + info.id());
				System.out.println("类别: " + info.type());
			}
		}
	}
}

MainVehicle

MainVehicle.java

package testAnnotation;

public class MainVehicle {
	public static void main(String[] args) {
		System.out.println("Car info: ");
		VehicleUtil.getTrafficInfo(Car.class);

		System.out.println("Train info: ");
		VehicleUtil.getTrafficInfo(Train.class);

		System.out.println("BMW info: ");
		Car car = new Car();
		car.setName("BMW");
		VehicleUtil.getTrafficInfo(car.getClass());
		System.out.println("Name: " + car.getName());
	}
}

执行结果

Car info: 
编号: 1
类别: 汽车

Train info: 
编号: 2
类别: 火车

BMW info: 
编号: 1
类别: 汽车
Name: BMW

其中的技术细节:

  1. VehicleUtil.getTrafficInfo 中,通过反射方法(SoftReference)拿到 fields.
  2. 查找 field 中匹配的 Annotation
  3. 注解是作用在类上,而不是类实例出的对象上。

原理

注解定义时使用了 @interface,这是一个继承了 java.lang.annotation.Annotation 接口的特殊接口。

VehicleUtil.getTrafficInfo 中的 info.id() 是怎么调用的呢?

如果是通常的类实例调用过程,info 是类实例,id() 是类方法。

在本示例中,info$Proxy1 类的实例,这个 proxy 类是 Java 运行时动态生成的代理类。

代理类的类方法调用都会实现了 java.lang.reflect.InvocationHandler 接口,具体到注解代理类中,类方法的调用实际执行了 sun.reflect.annotation.AnnotationInvocationHandler 类。

id() 类方法的执行过程为:

  1. 调用了 AnnotationInvocationHandler.invoke(Object, Method, Object[]) 方法
  2. memberValues 中,本例的值为 {type=汽车, id=1},找到 id 对应的值 1
  3. 返回 1

所以本质上,注解是一个继承了 Annotation 的特殊接口的内置的特殊接口。

元注解

在自定义注解时,需要用到一些内置的注解。这些内置的注解即为元注解,用来即注解其它的注解。

元注释有 4 个:

Documented

如果有 @Documented 注解,即做为了 public contract 的一部分,可以被工具调用,如 javadoc。

反之,如果没有 @Documented 注解,即不做为 public contract 的一部分,不可以被工具调用。

Retention

注解作用的阶段,其类型为 RetentionPolicy,值为

  • SOURCE: 会被编译器抛弃
  • CLASS: 会编译在 class 文件中,但是不需要 VM 运行时加载。这个为默认值。
  • RUNTIME: 会编译在 class 文件中,同时需要在 VM 运行时加载。

Target

注解作用的范围,即在哪个位置用这个注解是合法的。常见的值为:

  • TYPE: 类,接口,枚举类型
  • FIELD: 变量
  • METHOD: 类
  • PARAMETER: 形式参数
  • CONSTRUCTOR: 构造方法
  • LOCAL_VARIABLE: 局部变量
  • ANNOTATION_TYPE: 注解
  • PACKAGE: 包
  • TYPE_PARAMETER: @Since 1.8, 类型参数
  • TYPE_USE: @Since 1.8, 类型使用
  • MODULE: @Since 9, 模块

Inherited

类型是被继承的。本文不展开解释。

Native

@Since 1.8

常量字段中的值可以被 native 代码使用。本文不展开解释。

Repeatable

@Since 1.8

注解类型是可以重复的。本文不展开解释。

常见的标准注解

  • Override
  • Deprecated
  • SuppressWarnings
  • SafeVarargs: Since 1.7
  • FunctionalInterface: Since 1.8

参考

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3167 引用 • 8207 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...