简介
Spring框架经常用到两种注入方式:一种是注解,比如@Controller,@Service,@Repository
,另一种是XML。采用注解可以提供更大的便捷性,易于维护修改,但耦合度高,而 XML 相对于注解则是相反的。两者各有优劣,本文主要讲注解。
例子
/**
* 注解接口
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int set() default 0;
}
@MyAnnotation(set=12)
public class TestAnnotation {
public static void main(String[] args) {
// 检查TestAnnotation类是否有注解
if (TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = (MyAnnotation) TestAnnotation.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.set());
}
}
}
输出:
12
注解本质
java.lang.annotation.Annotation
接口
//The common interface extended by all annotation types
public interface Annotation
注释说明了:所有的注解类型都继承自这个普通的接口(Annotation)。
比如上面的MyAnnotation
,它本质上就是
public interface MyAnnotation extends Annotation{
}
注解语法
通过上面的例子:
注解通过 @interface
关键字进行定义。
public @interface MyAnnotation{
}
它的形式跟接口很类似,不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 MyAnnotation的注解。
不过这样注解还不能工作,还需要一些标识告诉编译器这个注解用在什么地方,以及它的生命周期等。这些标识就是元注解。
元注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
元标签有@Retention、@Documented、@Target、@Inherited、@Repeatable
5 种。
(1)@Retention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
RetentionPolicy.SOURCE
:只在本编译单元的编译过程中保留,并不写入Class文件中。这种注解主要用于在本编译单元(这里一个Java源码文件可以看作一个编译单元)内触发注解处理器(annotation processor)的相关处理,例如说可以让注解处理器相应地生成一些代码,或者是让注解处理器做一些额外的类型检查,等等。例如:@Override
、@SuppressWarnings
RetentionPolicy.CLASS
:在编译的过程中保留并且会写入Class文件中,但是JVM在加载类的时候不需要将其加载为运行时可见的(反射可见)的注解。这里很重要的一点是编译多个Java文件时的情况:假如要编译A.java源码文件和B.class文件,其中A类依赖B类,并且B类上有些注解希望让A.java编译时能看到,那么B.class里就必须要持有这些注解信息才行。同时我们可能不需要让它在运行时对反射可见(例如说为了减少运行时元数据的大小之类),所以会选择CLASS而不是RUNTIME。
RetentionPolicy.RUNTIME
:在编译过程中保留,会写入Class文件,并且JVM加载类的时候也会将其加载为反射可见的注解。这就不用多说了,例如说Spring的依赖注入就会在运行时通过扫描类上的注解来决定注入啥。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
上面的代码中,我们指定 MyAnnotation可以在程序运行周期被获取到,因此它的生命周期非常的长。
(2)@Documented
当我们执行 JavaDoc 文档打包时会被保存进 doc 文档。
(3)@Target
Target 是目标的意思,@Target
指定了注解运用的地方。
@Target
有下面的取值
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
(4)@Inherited
是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
例子:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解@ Test
被 @Inherited
修饰,之后类 A 被 @Test
注解,类 B 继承 A,类 B 也拥有@Test
这个注解。
(5)@Repeatable
Repeatable 自然是可重复的意思。@Repeatable
是 Java 1.8 才加进来的,所以算是一个新的特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注意上面的代码,@Repeatable
注解了 Person
。而 @Repeatable
后面括号中的类相当于一个容器注解。
Class 类中提供了以下一些方法用于反射注解
getAnnotation:返回指定的注解
isAnnotationPresent:判定当前元素是否被指定注解修饰
getAnnotations:返回所有的注解
getDeclaredAnnotation:返回本元素的指定注解
getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
注解原理
Debug上面的代码,
可以看出
MyAnnotation
本质上通过由JDK生成动态代理类来进行调用的在
main
方法加入
public static void main(String[] args) {
//把生成的代理字节码写到com/sun/proxy包下
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 检查TestAnnotation类是否有注解
if (TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = (MyAnnotation) TestAnnotation.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.set());
}
}
在com\sun\proxy
目录下生成$Proxy1.class
反编译以后
public final class $Proxy1
extends Proxy
implements MyAnnotation
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public $Proxy1(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
public final String toString()
throws
{
return (String)this.h.invoke(this, m2, null);
}
public final int set()
throws
{
return ((Integer)this.h.invoke(this, m3, null)).intValue();
}
public final Class annotationType()
throws
{
return (Class)this.h.invoke(this, m4, null);
}
public final int hashCode()
throws
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
static
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.huige.MyAnnotation").getMethod("set", new Class[0]);
m4 = Class.forName("com.huige.MyAnnotation").getMethod("annotationType", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
}
从上面可以看出我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类
InvocationHandler
既然MyAnnotation
具体是通过Proxy1调用最终会传递给绑定的InvocationHandler实例的invoke方法处理,那InvocationHandler
是谁呢?
从上图可以看出java提供了
AnnotationInvocationHandler
来处理注解跟踪进去
最终调用
sun.reflect.annotation.AnnotationInvocationHandler#invoke
方法
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
看看invoke方法是如何处理我们annotation.set()方法的调用的
debug进去
上图可以看出,在
default
环节进行获取值的,明显memberValues
是一个LinkedHashMap
,set方法的返回值是从一个LinkedHashMap中获取到的。这个LinkedHashMap以key(注解方法名)—value(注解方法对应的值)存储
那memberValues这个Map对象是怎么生成的,继续调试
通过方法调用栈找到memberValues的本源
调用栈如下:
可以看出在执行
TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)
时就生成memberValues
了具体创建
LinkedHashMap
是在sun.reflect.annotation.AnnotationParser#parseAnnotation2
方法中
private static Annotation parseAnnotation2(ByteBuffer var0, ConstantPool var1, Class<?> var2, boolean var3, Class<? extends Annotation>[] var4) {
//。。省略
LinkedHashMap var10 = new LinkedHashMap(var8.memberDefaults());
//。。。省略:var10就是LinkedHashMap
return annotationForMap(var6, var10);
}
}
具体获取值在sun.reflect.annotation.AnnotationParser#parseConst
方法中
即从
运行时常量池
地址为30
中取出12,最后调用
AnnotationInvocationHandler
构造器创建AnnotationInvocationHandler
对象,把已经生成的map赋值给memberValues
番外篇
上面提到MyAnnotation继承Annotation接口,翻了源码没发现在哪里继承了,到底是什么回事呢?
我们查看下MyAnnotation
的字节码
D:\project\src\main\java\com\huige>javap -verbose MyAnnotat
ion.class
Classfile /D:/project/src/main/java/com/huige/MyAnnotation.
class
Last modified 2019-1-16; size 439 bytes
MD5 checksum c1dd0a0bf103c12955a919a57ce48806
Compiled from "MyAnnotation.java"
public interface com.huige.MyAnnotation extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #18 // com/huige/MyAnnotation
#2 = Class #19 // java/lang/Object
#3 = Class #20 // java/lang/annotation/Annotation
#4 = Utf8 set
#5 = Utf8 ()I
#6 = Utf8 AnnotationDefault
#7 = Integer 0
#8 = Utf8 SourceFile
#9 = Utf8 MyAnnotation.java
#10 = Utf8 RuntimeVisibleAnnotations
#11 = Utf8 Ljava/lang/annotation/Target;
#12 = Utf8 value
#13 = Utf8 Ljava/lang/annotation/ElementType;
#14 = Utf8 TYPE
#15 = Utf8 Ljava/lang/annotation/Retention;
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 com/huige/MyAnnotation
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/annotation/Annotation
{
public abstract int set();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#7}
SourceFile: "MyAnnotation.java"
RuntimeVisibleAnnotations:
0: #11(#12=[e#13.#14])
1: #15(#12=e#16.#17)
从上面的字节码可以看出
(1)MyAnnotation继承了Annotation的接口
(2)虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
RuntimeVisibleAnnotations:记录所有运行时可见的Annotation
RuntimeInVisibleAnnotations:记录所有运行时不可见的注解RuntimeVisibleParameterAnnotations:记录所有运行时可见的方法参数注解
RuntimeInVisibleParameterAnnotations:记录所有运行时不可见的方法参数注解
AnnotationDefault:注解类元素的默认值
所以字节码倒数第三行采用RuntimeVisibleAnnotations
来保存Target跟Retention注解,这两个都是运行时驻留的
RuntimeVisibleAnnotations:
0: #11(#12=[e#13.#14]) //Ljava/lang/annotation/Target;
1: #15(#12=e#16.#17) //Ljava/lang/annotation/Retention;
顺便看看RuntimeVisibleAnnotations
属性表都有哪些字段
网友评论