谈谈注解

注解(Annotation):从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

通常来说我们可以依靠注解少些一些重复性的代码,

一、JAVA提供了五个基本的注解

  • @Override 限定父类重写方法

当子类继承父类重写其中某个方法时,加上此注解来确保准确 性(方法名,参数等)。

  • @Deprecated 标示已过时

这个注解用于表示某个程序元素类,方法等已过时,当其他程序使用已过时的类,方法时编译器会给出警告(删除线)

  • @SuppressWarnings 抑制编译器警告

被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告,例如修饰一个类,那他的字段,方法都是显示警告

  • @SafeVarargs “堆污染”警告

就是把不带泛型的对象赋给一个带泛型的对象,为什么不行?很简单,因为不带泛型的话,默认会给泛型设定为object,意思就是什么类型都可以往里面塞,那你一个不带泛型的怎么可能给一个带泛型塞呢。

List list = new ArrayList(); 
list.add(20);
List ls = list;
System.out.println(ls.get(0));
则会抛出堆污染异常Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Test.Test1.main(Test1.java:29)

注意:可变参数更容易引发堆污染异常,因为java不允许创建泛型数组,可变参数恰恰是数组。
抑制这个警告的方法有三个:

  1. @SafeVarargs修饰引发该警告的方法或构造器
  2. 使用@suppressWarnings("unchecked")
  3. 编译时使用-Xlint:varargs
  • @Functionallnterface 函数式接口

什么是函数式?如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法)
接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。
接口里面不能有私有的方法或变量。
这个注解有什么用?这个注解保证这个接口只有一个抽象方法,注意这个只能修饰接口

二、JDK中的@Annotation

在我们的java中给我们有liang zhong 注解,一种是元注解,另一种就是基于元注解定义的注解;
元注解主要包括几种:

  1. @Rention :用来修饰注解定义的,作用是被修饰的注解可以保存多久,这个注解需要使用参数类型是RetentionPolicy,所以使用这个注解就要对value赋值。

RetentionPolicy是枚举类,里面有三个value值

  • RetenionPolicy.CLASS :编译器把该注解记录在class文件中。当运行java程序时,JVM不可获取注解信息。这是默认值!
  • RetenionPolicy.RUNTIME :编译器把该注解记录在class文件中。当运行java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息
  • RetenionPolicy.SOURCE :该注解只保存在源代码中,编译器直接丢弃该注解
  1. @Target :修饰一个注解定义,作用是指定被修饰的注解能用于修饰哪些程序单元,@Target也包含了一个value值,他的值只能是下面的枚举类ElementType下的value
序号 取值 注解使用范围
1 METHOD 可用于方法上
2 TYPE 可用于类或者接口上
3 ANNOTATION_TYPE 可用于注解类型上(被@interface修饰的类型)
4 CONSTRUCTOR 可用于构造方法上
5 FIELD 可用于域上
6 LOCAL_VARIABLE 可用于局部变量上
7 PACKAGE 用于记录java文件的package信息
8 PARAMETER 可用于参数上

例如:

//定义一个用来修饰方法的注解 
@Target(ElementType.METHOD) (这是简写)
public @interface Action()
3.使用@Documented

这个注解用于指定被修饰的注解类将被javadoc工具提取成文档,如果定义注解类时使用了这个注解修饰,则所有使用该注解修饰的程序员苏API文档将会包含该注解说明。

例如:@Documentedpublic @interface Testable{}

4.使用@Inherited

这个注解指定被他修饰的注解将具有继承性——如果某个类使用了@Xxx,则其子类将自动被@Xxx修饰

5.使用@Result

作用是在同一个程序元素前使用多个相同类型的注解在java8之前只能通过@Results配置,

java8简化了它的写法例如:

@test(age=5)@test(age=8)public void resultTest(){}

三、自定义注解,通过上面的元注解定义我们自己的个性注解

  1. 基本定义,与定义接口类似
//定义
public @interface SIngleClick {
}

//使用
@SIngleClick
 public class AnnotationTest{
 } 
  1. 定义成员变量 ,注解只有成员变量,没有方法,注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,返回值定义了该成员变量的类型,
//定义
public @interface SIngleClick {
  int age();
  String name();
}

//使用
@SIngleClick(age = 20,name=“注解小王子”)
 public class AnnotationTest{
 } 
这样定义的注解的变量age=20,name=“注解小王子”,方便后续的操作,与参数类似,我们也可以设置一个默认的值,不传则用默认值,如下:
//定义
public @interface SIngleClick {
  int age() default 20;
  String name() default “注解小王子”;
}
  1. 定义运行时注解
    上面说到@Retention 是用来定义保留策略,这三个注解生命周期长度SOURCE
//定义运行时注解
@Retention(RetentionPolicy.RUNTIME)
public @interface SIngleClick {
  int age() default 20;
  String name() default “注解小王子”;
}
//定义编译时注解
@Retention(RetentionPolicy.CLASS)
public @interface SIngleClick {
  int age() default 20;
  String name() default “注解小王子”;
}
//常见的@override的源代码
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

三、注解处理器

如果没有注解处理器那么注解写上了也不会有太大作用,对于不同注解我们有不同的处理器,虽然注解处理器编写千变万化,但是也有其处理标准,如:针对运行时注解会采用反射处理,针对编译时注解会采用AbstractProcessor来处理,首先看针对运行时注解的注解处理器,

1. 运行时注解处理器

处理运行时注解时要用到反射机制,首先定义运行时注解:

//运行时注解 
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
          String value() default "";
}

使用

public class AnnotationTest{
    @SingleClick(value="注解小王子")
    public void test(){
    }
}

定义处理器

public class AnnotationProcessor{
    public static void main(Strings argments){
        Method[] methods=AnnotationTest.class.getDeclaredMethods();
        for(Method m :methods){
            SingleClick sl=m.getAnnotation(SingleClick.class);
            Log.e(TAG,sl.value);
        }
    }
}

这里涉及到了反射:

反射 (Reflection): 对于任意一个类,都能知道这个类的所有属性和方法;
对于任何一个对象,都能够调用它的任何一个方法和属性;

获取对象的的class有三种方式:

  1. class c=Test.class;
  2. class c=class.forname(Test);
  3. Test test=new Test(); Class c2=test.getClass();

在class中提供了一些方法,如:

image.png

同时我们可以看到Class 源码时他实现了AnnotatedElement(注解相关的接口),

public final class Class implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;
    private static final int FINALIZABLE = 0x80000000;
public interface AnnotatedElement {
 
    default boolean isAnnotationPresent(Class annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

     T getAnnotation(Class annotationClass);

    /**
     * Returns annotations that are present on this element.
     * @return annotations present on this element
     */
    Annotation[] getAnnotations();

    /**
     * Returns annotations that are associated with this element.  
    */
    default  T[] getAnnotationsByType(Class annotationClass) {
        
        return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
    }

    default  T getDeclaredAnnotation(Class annotationClass) {
        Objects.requireNonNull(annotationClass);
        // Loop over all directly-present annotations looking for a matching one
        for (Annotation annotation : getDeclaredAnnotations()) {
            if (annotationClass.equals(annotation.annotationType())) {
                // More robust to do a dynamic cast at runtime instead
                // of compile-time only.
                return annotationClass.cast(annotation);
            }
        }
        return null;
    }
    default  T[] getDeclaredAnnotationsByType(Class annotationClass) {
        return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
    }
    Annotation[] getDeclaredAnnotations();
}

所以我们可以通过class拿到该class的注解相关信息,同样MethodFiled等类都实现了该AnnotatedElement接口,同样可以获取相关的注解信息。所以上面我们可以通过下面的拿到该类里的所有方法,

Method[] methods=AnnotationTest.class.getDeclaredMethods();

通过getAnnotation拿到某一个方法的注解,从而获取该注解内的变量value值(sl.value)

        SingleClick sl=m.getAnnotation(SingleClick.class);
  1. 编译时注解处理器,处理编译时注解比较复杂,
    第一种 :手动处理,
    1.在项目中新建一个java Library来存放注解annotations,
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}

2.编写注解处理器
在项目中新建Java LIbrary名为processor,然后将annotations引入此项目

compile project(':annotations')

接下来编写ClassProcessor,继承自AbstractProcessor

public class ClassProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        Messager messager=processingEnv.getMessager();
        for(Element element:roundEnvironment.getElementsAnnotatedWith(BindView.class)){
            if (element.getKind()== ElementKind.FIELD){
                messager.printMessage(Diagnostic.Kind.NOTE,"pritmessage:"+element.toString());
            }
        }
        return false;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    @Override
    public Set getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }
}
  • process 相当于每个处理器的主函数,在这里扫描、评估、处理代码的逻辑;
    以及生成java文件,输入参数RoundEnvironment可以查询出特定注解的被注
    解元素
  • init 被注解处理工具调用,输入ProcessingEnvironment参数,其提供了很多游泳的工具了如 Elements,Meeager、Filed等
  • getSupportedAnnotationTypes 这是必须指定的方法,用来指定处理器是给那个注解的,返回值是一个字符串集合,包含了要注解的合法全称;
  • getSupportedSourceVersion 用来指定你使用的java版本,通常返回SourceVersion.latestSupported();
  1. 注册注解处理器
    为了能使用注解处理器,需要使用一个服务文件来注册,首先在processor的项目main下新建文件夹resources,接下来在其下面新建META_INF/services目录,最后在其下面新建文件javax.annotation.processing.Processor,内容是注解处理器的路径全称
    com.XXXXXXXXX.processor.ClassProcessor即可,
  2. 使用注解
    在项目中引入两个LIbrary,
    compile project(':annotations')
    compile project(':processor')
public class MainActivity extends Activity {

    @BindView(value=R.id.btn_test)
    Button btn_test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

然后clean项目,再make Project,编译时在gradle console里可以看到“pritmessage:btn_test”

  1. 使用android-apt插件
    我们在项目中使用了processor库,但注解处理器只在编译时用到,编译后没有什么作用了,还会添加很多不必要的文件,为了处理这个问题我们可以使用apt插件,主要作用有

1、仅仅在编译时期去掉依赖注解器所在的函数库并进行工作,但不会打包到APK中,
2、为注解处理器规范好路径,以便android Studio能找到他们。在Project的gradle中添加

  calsspath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

在项目的gradle中

apt project(':processor')

即可。

四、注解在项目中的使用

1.AOP+Annotation 防止按钮暴力点击(待续)

  1. butterknife 源码分析(待续)

说明:本文大部分总结自《Android进阶之光》,只为加深印象自己留作笔记,对作者刘望舒表示感谢,感谢生活,感谢科技,感谢分享!

你可能感兴趣的:(谈谈注解)