一、概述
注解在Java开发中常见,但是很多人不知其中的实现原理,接下来谈谈我对注解的理解和认识
二、首先,注解的出现是从Java1.5开始的,注解的分类本人分为了大致的三类:
a、Java自带的标准注解:@Override、@deprecated、@supporswarning
@Override:表示方法的重写
@deprecated:表示方法过时、过时的方法会有横杠,但是不影响使用
@supporswarning:表示忽略警告信息
b、元注解:@Retention 、@Target 、 @Documented 、 @Inherited、@Native、@Repeate
@Retention :表示注解的生效范围
@Target :表示注解作用的对象
@Documented :表示可以生成文档
@Inherited:表示注解可以遗传
@Native:表示该注解是使用的本地的方法
@Repeate:表示该注解在同一位置可以重用
c、自定义注解:可以通过元注解声明,其中@Retention和@Target注解是必不可少的
三、下面我们讲一下自定义注解如何定义
实现自定义注解必不可少的是@Retention和@Target,同时,需要使用@interface来表示这是一个注解类,@Interface可以理解为是注解类的标志,没有其他含义。使用@interface,可以让JVM知道这是一个注解类。如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnno {
public String title() default "cheng";
public String profile() default "world";
}
该注解表示的含义是作用在类,接口或枚举上的,作用范围是运行期间,同时有两个属性。
再来看看它的应用,如下
@MyMethodAnno(title = "xixi",profile = "name")
public class Test1 {
public static void main(String[] args) {
//Test1.handleAnno();
Class cl = Test1.class;
//Annotation[] annotations = cl.getDeclaredAnnotations();
Annotation ann = cl.getDeclaredAnnotation(MyMethodAnno.class);
System.out.println(ann);
}
}
我们会很好奇,它是怎么运作的,又是怎么解析的呢?带着疑问,我们继续往下看。通过idea的字节码工具:view->show ByteCode,我们看一下MyMethodAnno注解的字节码,如下图
MyMethodAnno注解的字节码中其实是实现了Annotation接口,所以,MyMethodAnno也是一个接口,属性变成了抽象方法。到这里,我们可以推断出,从JVM的角度来看,注解就是接口!
Annotation接口是所有注解的一个顶层接口,所有注解都必须要实现这个接口。
接下来我们通过断点的方式来看看它底层是怎么运行的,如下图
我们可以看到,通过反射,返回的注解对象是一个代理对象Proxy。同时看到处理类有AnnotationInvocationHandle这个类。顺藤摸瓜,我们来看下AnnotationInvocationHandle这个类,根据字面意思,也能清楚的知道,这个类是注解调用处理类。如下
这个类中的invoke方法中打断点,因为这个方法一看就是核心的执行方法。继续执行断点,会跳转到AnnotaitonParse类中的parseAnnotation方法,然后执行annotationForMap方法
parseAnnotation方法
static Annotation parseAnnotation(ByteBuffer var0, ConstantPool var1, Class<?> var2, boolean var3) {
return parseAnnotation2(var0, var1, var2, var3, (Class[])null);
}
annotationForMap方法
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));
}
});
}
在annotationForMap方法中,调用Proxy的newProxyInstance来创建注解的代理对象,这就能很好的解释上述中出现的代理对象。
AnnotationInvocationHandle类的invoke执行完毕后,会进入到Class类中执行createAnnotationData方法,这个方法是用来创建注解的数据并保存。
private AnnotationData createAnnotationData(int classRedefinedCount) {
Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
Class<?> superClass = getSuperclass();
Map<Class<? extends Annotation>, Annotation> annotations = null;
if (superClass != null) {
Map<Class<? extends Annotation>, Annotation> superAnnotations =
superClass.annotationData().annotations;
for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
Class<? extends Annotation> annotationClass = e.getKey();
if (AnnotationType.getInstance(annotationClass).isInherited()) {
if (annotations == null) { // lazy construction
annotations = new LinkedHashMap<>((Math.max(
declaredAnnotations.size(),
Math.min(12, declaredAnnotations.size() + superAnnotations.size())
) * 4 + 2) / 3
);
}
annotations.put(annotationClass, e.getValue());
}
}
}
if (annotations == null) {
// no inherited annotations -> share the Map with declaredAnnotations
annotations = declaredAnnotations;
} else {
// at least one inherited annotation -> declared may override inherited
annotations.putAll(declaredAnnotations);
}
return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);
}
在这个方法中,返回的值是AnnotationData对象,这个对象中又有两个重要的属性:declaredAnnotations和annotations。
之前学习过反射,应该都知道,带declared前缀的,是本类中声明的,不带的,表示父类和本类的。在这个方法中,就能够体现。
当执行完这个方法后,整个注解的执行解析就结束了。
同时,注意这个方法,调用parseAnnotations方法时传入了getConstantPool,意味着注解的数据是从JVM中的常量池中取得。
我们再来回顾一下整个执行的过程:AnnotationInvocationHandler 类中的invoke方法开始执行注解的逻辑处理-》调用AnnotationParser类的parseAnnotations方法开始解析-》执行AnnotationParser类中的annotationForMap方法来生成注解的代理对象-》invoke方法执行完毕-》Class类中的createAnnotationData方法封装注解的数据,返回AnnotationData对象-》结束
再回到最初的使用反射的地方,如下
@MyMethodAnno(title = "xixi",profile = "name")
public class Test1 {
public static void main(String[] args) {
//Test1.handleAnno();
Class cl = Test1.class;
//Annotation[] annotations = cl.getDeclaredAnnotations();
Annotation ann = cl.getDeclaredAnnotation(MyMethodAnno.class);
System.out.println(ann);
}
}
通过字节码对象调用getDeclaredAnnotation方法,我们进去看看,如下
其实就是从AnnotationData对象中拿到declaredAnnotations属性,这个属性上述也说过,是一个map类型,我们传入注解对象后,就能得到注解对象的所有数据value。
上述就是注解的整个解析,运行和调用的一个闭环,也是本人对注解的整体认识,如有差错,望不吝赐教。