「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』:所有的注解类型都继承自这个普通的接口,因此,注解的本质就是一个继承了 Annotation 接口的接口
而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。
典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名
注解就像一个标签,是贴在程序代码上供另一个程序读取的
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解是
作用在其他注解的注解(或者说 元注解)是:
@Retention的可能取值:
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了,比如@Override */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}
@Target的可能取值:
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
我们可以自己写一个注解
@Target(METHOD)
@Retention(RUNTIME)
public @interface MyAnnotation {
String value() default "hello";
}
发现什么奇怪的问题没有,不说是接口了,就算是类也不能给方法赋值啊。而且这么操作有什么用吗
我们定义了注解的作用域、作用目标之后,在注解内写的方法可以看作注解蕴含的信息,因此只要用到注解,必然有三角关系:定义注解、使用注解、读取注解
如果注解本质上是继承了 Annotation 接口的接口,那是不是可以通过反射获得这个注解所包含的信息了,获得信息就可以对其标记的东西进行操作了
来个实例吧,这是个被我自定义注解标记的类
public class JustTest {
@MyAnnotation
public void sayHello(){
System.out.println("say hello");
}
}
测试的类,这里面的操作就是读取注解的程序,这部分一般被框架隐藏起来
@Test
public void test() throws IllegalAccessException, InstantiationException, InvocationTargetException {
// 1.先找到测试类的字节码
Class clazz = JustTest.class;
Object obj = clazz.newInstance();
// 2.获取类中的公共方法
Method[] methods = clazz.getMethods();
// 3.测试一下是否包含自定义注解,拿到并输出自定义注解中的值,执行该方法
System.out.println(methods[0].isAnnotationPresent(MyAnnotation.class));
System.out.println(methods[0].getAnnotation(MyAnnotation.class).value());
methods[0].invoke(obj);
}
你可以通过反射拿到这个注解的代理类,也就是我们这里的 getAnnotation 方法,其实 JDK 通过动态代理机制生成一个实现我们注解(接口)的代理类
代理类实现接口 Hello 并重写其所有方法,包括 value 方法以及接口 Hello 从 Annotation 接口继承而来的方法
那这个代理类是如何生成的?直接说结论,用以下方式生成动态代理对象
public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
}
});
}
InvocationHandler 的实例是 AnnotationInvocationHandler,AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
...
我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
// 如果所调用的方法是父类的方法,直接调用
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// memberValues是一个map,里面以方法名为键、对应的数据为值,此时result就已经是需要的值了
Object result = memberValues.get(member);
// 异常处理
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
// 如果拿到的值是数组的话,需要将一个个元素克隆
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
总之这个方法的作用就是通过方法名返回注解属性值