在框架遇到了很多注解 junit的@Test(测试用),特别是Spring框架中遇到了@configuration,@Repository,@Attowired,@Service一大堆,我就重新又看回注解了。
这篇文章大部分从http://www.open-open.com/lib/view/open1423558996951.html转载过来
为编译器提供辅助信息 — Annotations可以为编译器提供而外信息,以便于检测错误,抑制警告等.
编译源代码时进行而外操作 — 软件工具可以通过处理Annotation信息来生成原代码,xml文件等等.
运行时处理 — 有一些annotation甚至可以在程序运行时被检测,使用.
总之,注解是一种元数据,起到了”描述,配置“的作用。
元注解:
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。简单的理解的话就是标明该注解的作业域。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
-
ElementType.TYPE:能修饰类、接口或枚举类型
-
ElementType.FIELD:能修饰成员变量
-
ElementType.METHOD:能修饰方法
-
ElementType.PARAMETER:能修饰参数
-
ElementType.CONSTRUCTOR:能修饰构造器
-
ElementType.LOCAL_VARIABLE:能修饰局部变量
-
ElementType.ANNOTATION_TYPE:能修饰注解
-
ElementType.PACKAGE:能修饰包
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。简单的来说就是定义注解生命周期。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。
Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理.
@Documented:
如果定义注解A时,使用了@Documented修饰定义,则在用javadoc命令生成API文档后,所有使用注解A修饰的程序元素,将会包含注解A的说明。
@Inherited:
@Inherited指定Annotation具有继承性。
示例:
package com.demo2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyTag{
}
package com.demo2;
@MyTag
public class Base {
}
package com.demo2;
//SubClass只是继承了Base类
//并未直接使用@MyTag注解修饰
public class SubClass extends Base {
public static void main(String[] args) {
System.out.println(SubClass.class.isAnnotationPresent(MyTag.class));
}
}
示例中Base使用@MyTag修饰,SubClass继承Base,而且没有直接使用@MyTag修饰,但是因为MyTag定义时,使用了@Inherited修饰,具有了继承性,所以运行结果为true。
如果@MyTag注解没有被@Inherited修饰,则运行结果为:false。
基本Annotation
JDK默认提供了如下几个基本Annotation:
-
@Override
限定重写父类方法。对于子类中被@Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override 只能作用于方法,不能作用于其他程序元素。
-
@Deprecated
用于表示某个程序元素(类、方法等)已过时。如果使用被@Deprecated修饰的类或方法等,编译器会发出警告。
-
@SuppressWarning
抑制编译器警告。指示被@SuppressWarning修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法.....)取消显示指定的编译器警告。例如,常见的@SuppressWarning(value="unchecked")
-
@SafeVarargs
@SafeVarargs是JDK 7 专门为抑制“堆污染”警告提供的。
2. 提取Annotation信息(反射)
java.lang.annotation.Annotation接口的主要方法是annotationType( ),用于返回该注解的java.lang.Class。
java.lang.reflect.AnnotatedElement接口 主要方法有:
-
isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
-
getAnnotation(Class<T> annotationClass):返回该程序元素上存在的指定类型的注解,如果该类型的注解不存在,则返回null
-
Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
java.lang.reflect.AnnotatedElement接口是所有程序元素(例如java.lang.Class、java.lang.reflect.Method、java.lang.reflect.Constructor等)的父接口。类图结构如下:
所以程序通过反射获取了某个类的AnnotatedElement对象(例如,A类method1()方法的java.lang.reflect.Method对象)后,就可以调用该对象的isAnnotationPresent( )、getAnnotation( )等方法来访问注解信息。
PS:如果想要在运行时提取注解信息,在定义注解时,该注解必须使用@Retention(RetentionPolicy.RUNTIME)修饰。
2.1 标记Annotation
给定一个类的全额限定名,加载类,并列出该类中被注解@MyTag修饰的方法和没被修饰的方法。
注解定义:
package com.demo1;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTag {
}
注解处理:
package com.demo1;
import java.lang.reflect.Method;
public class ProcessTool {
public static void process(String clazz) {
Class targetClass = null;
try {
targetClass = Class.forName(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
for (Method m : targetClass.getMethods()) {
if (m.isAnnotationPresent(MyTag.class)) {
System.out.println("被MyTag注解修饰的方法名:" + m.getName());
} else {
System.out.println("没被MyTag注解修饰的方法名:" + m.getName());
}
}
}
}
测试类:
package com.demo1;
public class Demo {
public static void m1() {
}
@MyTag
public static void m2() {
}
}
package com.demo1;
public class Test {
public static void main(String[] args) {
ProcessTool.process("com.demo1.Demo");
}
}
运行结果:
没被MyTag注解修饰的方法名:m1
被MyTag注解修饰的方法名:m2
没被MyTag注解修饰的方法名:wait
没被MyTag注解修饰的方法名:wait
没被MyTag注解修饰的方法名:wait
没被MyTag注解修饰的方法名:equals
没被MyTag注解修饰的方法名:toString
没被MyTag注解修饰的方法名:hashCode
没被MyTag注解修饰的方法名:getClass
没被MyTag注解修饰的方法名:notify
没被MyTag注解修饰的方法名:notifyAll
想看反射的知识可以看一下我上篇文章
2.2 元数据Annotation
给定一个类的全额限定名,加载类,找出被注解MyTag修饰的方法,并输出每个方法的MyTag注解的属性。
注解定义:
package com.demo1;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTag {
String name() default "我兰";
int age() default 18;
}
注解处理:
package com.demo1;
import java.lang.reflect.Method;
public class ProcessTool {
public static void process(String clazz) {
Class targetClass = null;
try {
targetClass = Class.forName(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
for (Method m : targetClass.getMethods()) {
if (m.isAnnotationPresent(MyTag.class)) {
MyTag tag = m.getAnnotation(MyTag.class);
System.out.println("方法" + m.getName() + "的MyTag注解内容为:" + tag.name() + "," + tag.age());
}
}
}
}
测试类:
package com.demo1;
public class Demo {
public static void m1() {
}
@MyTag
public static void m2() {
}
@MyTag(name = "红薯")
public static void m3() {
}
@MyTag(name = "红薯", age = 30)
public static void m4() {
}
}
package com.demo1;
public class Test {
public static void main(String[] args) {
ProcessTool.process("com.demo1.Demo");
}
}
运行结果:
方法m2的MyTag注解内容为:我兰,18
方法m3的MyTag注解内容为:红薯,18
方法m4的MyTag注解内容为:红薯,30
若要获取注解中的成员变量值,直接调用注解对象的"成员变量民( )"形式的方法就行,例如示例中的tag.name()等。
PS:在编译器编译注解定义时,自动在class文件中,添加与成员变量同名的抽象方法,用于反射时获取成员变量的值。
通过上面的示例可以看出,其实Annotation十分简单,它是对源代码增加的一些特殊标记,这些特殊标记可通过反射获取,当程序获取这些特殊标记后,程序可以做出相应的处理(当然也可以完全忽略这些Annotation)。
3 注解本质
对于示例”2.2 元数据Annotation“中的@MyTag注解,在编译后,生成一个MyTag.class文件。反编译该class文件:
javap -verbose -c MyTag.class > m.txt
MyTag注解的字节码为:
通过分析字节码可知:
-
注解实质上会被编译器编译为接口,并且继承java.lang.annotation.Annotation接口。
-
注解的成员变量会被编译器编译为同名的抽象方法。
-
根据Java的class文件规范,class文件中会在程序元素的属性位置记录注解信息。例如,RuntimeVisibleAnnotations属性位置,记录修饰该类的注解有哪些;flags属性位置,记录该类是不是注解;在方法的AnnotationDefault属性位置,记录注解的成员变量默认值是多少。
我们再反编译下示例”2.3.2 元数据Annotation“中的Demo测试类,查看下”被注解修饰的方法是怎样记录自己被注解修饰的“:
javap -verbose -c Demo.class > d.txt
反编译结果如下:
通过字节码可知:
在字节码文件中,每个方法都有RuntimeVisibleAnnotations属性位置,用来放置注解和注解的成员变量赋值。JVM在解析class文件时,会解析RuntimeVisibleAnnotations属性,并新建相应类型的注解对象,并将成员变量赋值。