java Annotation:An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.[3]
意思是:java注解是元数据的一种形式,它可以被添加到源代码中。类,方法,变量,参数和包都可以添加注解。注解不会对代码有直接影响。
按照我个人的理解:注解就是在类,方法,变量或者包上面附加一些数据,这些数据对代码的执行不会有直接的影响,等需要用到这些附加的数据的时候,可以通过反射来取这些数据,取数据有专门的api,后面会讲到。
对于没接触过注解的人,可能会觉得有点摸不着门道,但是一旦入了门就会发现其实不难。主要分为定义注解个使用注解。
定义注解:
我们平时看得最多的注解应该是Override,这个注解代表这个方法是重写的方法。点进去看看Override的实现是这样的:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
一句一句来解释:首先public是注解的修饰符,@interface是定义注解的关键字(就像class ,interface一样,就是一个关键字),Override是注解的名称。
@Target(ElementType.METHOD)和@Retention(RetentionPolicy.SOURCE)是用来修饰注解的,来给咱们定义的注解做一些限制,像Target和Retention这类注解被称为元注解,元注解就四个:Target,Retention,Documented,Inherited。分别解释一下这个四个元注解的作用和用法。
Target:表示Annotation可以用来修饰哪些程序元素,如 TYPE(作用于:比如class, interface或者枚举), METHOD(作用于方法), CONSTRUCTOR(作用于构造方法), FIELD(作用于字段(定义的变量)), PARAMETER (作用于参数)等,未标注则表示可修饰所有。
Retention:表示Annotation作用范围和保留时间,可选值SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为标记的注解,这类Annotation 大都用来校验,比如Override, SuppressWarnings。
Documented:表示所修饰的Annotation连同自定义Annotation所修饰的元素一同保存到Javadoc文档中。
Inherited :表示父类Annotation可以被子类继承。使用了@Inherited修饰的annotation类型被用于一个class之时,则这个annotation将被用于该class的相应子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect
去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类
继承结构的顶层。就是说, 查看查找@Inherited过的Annotation之时,需要反复的向上查找,方才可以。
了解了以上这些内容,现在我们完全可以自己去定义一个注解了,是不是觉得很简单。看下面的例子:
@Documented
@Target(ElementType.METHOD )//如果想作用于多个地方可以这样写@Target({ElementType.FIELD,ElementType.METHOD} ),如果想作用于所有地方则可以不加参数
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
public String myName();
int myAge() default 20;
}
这个例子里面,初学者可能对public String myName()和int myAge() default 20这两行不太理解,那么,还是一个单词一个单词的来解释吧,很简单的。
首先要了解一点:这两行看似定义了两个方法,其实是为我们定义的这个注解加了两个属性而已。我们在使用这个属性的时候要这样用:
@MyAnnotation(myName = “张三”,myAge = 24)。这样就分别为这两个属性赋值了。下面是一些定义属性的规则:
a) 所有方法没有方法体,方法名即为属性名,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常。
b) 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组,返回类型即为属性类型。
c) 若只有一个默认属性,可直接用 value() 函数(意思就是只有一个属性的时候,可以直接定义一个value()方法,使用的时候就可以直接加入值就可以了,不用写类似myName = “张三”,而是直接写“张三”)。一个属性都没有表示该 Annotation 为 Mark Annotation。
d) 可以加 default 表示默认值,null不能作为成员默认值。
使用注解:
现在就使用上文定义的那个注解:
@MyAnnotation(myName = "张三",myAge = 24)
public void test(){
}
myAnnotation作用是为test()方法添加myName和myAge两个信息,那么添加的这两个信息有什么用,怎么用?我们是这么用的:把注解的内容解析出来,然后再根据解析出来
的内容在进行一些判断和操作。怎样解析呢?对于Rentention为RUNTIEM的注解,在运行时进行解析,采用的是反射;对于Rentention为CLASS和SOURCE时,是
编译时进行解析,采用的是apt(java annotation processing tools),下面分别讲解这两种解析方式。
1.Rentention为RUNTIEM时的解析方式(运行时解析):
基础API
AnnotatedElement代表能够被注解的元素,如方法,成员变量,方法参数,包,类都是这个接口的实现,AnnotatedElement有方法如下表:
方法名 | 用法 |
---|---|
Annotation getAnnotation(Class annotationType) | 获取注解在其上的annotationType |
Annotation[] getAnnotations() | 获取所有注解 |
isAnnotationPresent(Class annotationType) | 判断当前元素是否被annotationType注解 |
Annotation[] getDeclareAnnotations() | 与getAnnotations() 类似,但是不包括父类中被Inherited修饰的注解 |
返回的Annotation是注解的实例,可以反射Method获取需要的值,即是获取@MyAnnotation中MyName和MyAge的值。看下具体实现就明白:
/**
* 打印类的注解
* @throws ClassNotFoundException
*/
public void parseType()throws ClassNotFoundException {
Class clazz = Class.forName("com.jieliu.annotation.Test");
Annotation[] mAnnotations = clazz.getAnnotations();
for(Annotation mAnnotation:mAnnotations){
MyAnnotation mMyAnnotation = (MyAnnotation)mAnnotation;
System.out.println(mMyAnnotation.myName());
}
}
/**
* 打印方法的注解
*/
public void parseMethod(){
Method[] mMethods = Test.class.getDeclaredMethods();
for(Method mMethod:mMethods){
boolean hasAnnotation = mMethod.isAnnotationPresent(MyAnnotation.class);//判断是否有此注解
if(hasAnnotation){
MyAnnotation mMyAnnotation = mMethod.getAnnotation(MyAnnotation.class);
System.out.println(mMyAnnotation.myName()+" "+mMyAnnotation.myAge());
}
}
}
/**
* 打印构造方法的注解
*/
public void parseConstructor()throws NoSuchMethodException{
Constructor[] mConstructors = Test.class.getConstructors();
for(Constructor mConstructor:mConstructors){
boolean hasAnnotation = mConstructor.isAnnotationPresent(MyAnnotation.class);
if(hasAnnotation){
MyAnnotation mMyAnnotation = (MyAnnotation) mConstructor.getAnnotation(MyAnnotation.class);
System.out.println(mMyAnnotation.myName()+" "+mMyAnnotation.myAge());
}
}
}
只要取出了这些注解里面设置的值,接下来的工作就好办了,你可以用这些值做你想做的事。
2.Rentention为CLASS和SOURCE时的解析方式(编译时解析):
需要做的
a) 自定义类集成自AbstractProcessor
b) 重写其中的 process 函数
实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理.
@SupportedAnnotationTypes({ "com.jieliu.annotation.MyAnnotation" })
public class MethodInfoProcessor extends AbstractProcessor {
@Override
public boolean process(Set<?extends TypeElement> annotations, RoundEnvironment env) {
HashMap<String, String> map = new HashMap<String, String>();
for (TypeElement te : annotations) {
for (Element element : env.getElementsAnnotatedWith(te)) {
MyAnnotation mMyAnnotation = element.getAnnotation(MyAnnotation.class);
map.put(element.getEnclosingElement().toString(), mMyAnnotation.myName);
}
}
return false;
}
}
这里只简单的介绍了下用apt解析注解的方法。
至此,java Annotation的主要用法已经讲完了,可能有些细节地方还不够详细,最后推荐连篇文章:
Java中@Inherited注解的运用: https://blog.youkuaiyun.com/qq_20521573/article/details/82054088
apt的使用讲解:http://xcqxcyy39.blog.163.com/blog/static/12885113720104128818553/ https://blog.youkuaiyun.com/qq_20521573/article/details/82321755
apt原理(重点):https://www.jianshu.com/p/89ac9a2513c4