美文网首页
注解-学习笔记

注解-学习笔记

作者: 巫师Android | 来源:发表于2020-12-22 16:33 被阅读0次

    一、注解

    从JDK 5 开始,Java 新增注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。

    通过使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。

    1.1 注解分类

    注解分为标准注解和元注解。

    1.1.1 标准注解

    标准注解有以下 4 种。
    • @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
    • @Deprecated:对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息。
    • @SuppressWarnings:选择性地取消特定代码段中的警告。
    • @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题。

    1.1.2. 元注解

    元注解用来注解其他注解,从而创建新的注解。元注解有以下几种。

    • @Targe:注解所修饰的对象范围。
    • @Inherited:表示注解可以被继承。
    • @Documented:表示这个注解应该被 JavaDoc 工具记录。
    • @Retention:用来声明注解的保留策略。
    • @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次使用。

    其中@Targe 注解取值是一个 ElementType 类型的数组,其中有以下几种取值,对应不同的对象范围。
    • ElementType.TYPE:能修饰类、接口或枚举类型。
    • ElementType.FIELD:能修饰成员变量。
    • ElementType.METHOD:能修饰方法。
    • ElementType.PARAMETER:能修饰参数。
    • ElementType.CONSTRUCTOR:能修饰构造方法。
    • ElementType.LOCAL_VARIABLE:能修饰局部变量。
    • ElementType.ANNOTATION_TYPE:能修饰注解。
    • ElementType.PACKAGE:能修饰包。
    • ElementType.TYPE_PARAMETER:类型参数声明。
    • ElementType.TYPE_USE:使用类型。

    其中@Retention 注解有 3 种类型,分别表示不同级别的保留策略。
    • RetentionPolicy.SOURCE:源码级注解。注解信息只会保留在.java 源码中,源码在编译后,注解信息被丢弃,不会保留在.class 中。
    • RetentionPolicy.CLASS:编译时注解。注解信息会保留在.java 源码以及.class 中。当运行 Java 程序时,JVM 会丢弃该注解信息,不会保留在 JVM 中。
    • RetentionPolicy.RUNTIME:运行时注解。当运行 Java 程序时,JVM 也会保留该注解信息,可以通过反射获取该注解信息。

    1.2 定义注解

    1.2.1 基本定义

    定义新的注解类型使用@interface 关键字,这与定义一个接口很像,如下所示:

    public @interface AnnotationCreateMan {
    
    }
    

    定义完注解后,就可以在程序中使用该注解:

    @AnnotationCreateMan
    public class AnnotationTest {
    }
    

    1.2.2 定义成员变量

    注解只有成员变量,没有方法。注解的成员变量在注解定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,其返回值定义了该成员变量的类型:

    public @interface AnnotationCreateMan {
        String name();
    
        int age();
    }
    

    上面的代码定义了两个成员变量,这两个成员变量以方法的形式来定义。定义了成员变量后,使用该注解时就应该为该注解的成员变量指定值:

    @AnnotationCreateMan(name = "Tom", age = 35)
    public class AnnotationTest {
        @AnnotationCreateMan(name = "Jim", age = 22)
        public void fighting() {
    
        }
    }
    

    也可以在定义注解的成员变量时,使用 default 关键字为其指定默认值,如下所示:

    public @interface AnnotationCreateMan {
        String name() default "Jack";
    
        int age() default 25;
    }
    

    因为注解定义了默认值,所以使用时可以不为这些成员变量指定值,而是直接使用默认值:

    @AnnotationCreateMan(name = "Tom", age = 35)
    public class AnnotationTest {
        @AnnotationCreateMan
        public void fight() {
    
        }
    }
    

    1.2.3 定义运行时注解

    可以用@Retention 来设定注解的保留策略,这 3 个策略的生命周期长度为 SOURCE < CLASS < RUNTIME 。生命周期短的能起作用的地方,生命周期长的一定也能起作用。一般如果需要在运行时去动态获取注解信息,那只能用 RetentionPolicy.RUNTIME;如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 RetentionPolicy.CLASS;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 RetentionPolicy.SOURCE。当
    设定为 RetentionPolicy.RUNTIME 时,这个注解就是运行时注解,如下所示:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnnotationCreateMan {
        String name() default "Jack";
    
        int age() default 25;
    }
    

    1.2.4 定义编译时注解

    同样地,如果将@Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解,如下所示:

    @Retention(RetentionPolicy.CLASS)
    public @interface AnnotationCreateMan {
        String name() default "Jack";
    
        int age() default 25;
    }
    

    1.3 注解处理器

    如果没有处理注解的工具,那么注解也不会有什么大的作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写会千变万化,但是其也有处理标准,比如:针对运行时注解会采用反射机制处理,针对编译时注解会采用 AbstractProcessor 来处理。

    1.3.1 运行时注解处理器

    处理运行时注解需要用到反射机制。首先我们要定义运行时注解,如下所示:

    @Documented
    @Target(METHOD)
    @Retention(RUNTIME)
    public @interface GET {
        String value() default "";
    }
    

    上面的代码是 Retrofit 中定义的@GET 注解。其定义了@Target(METHOD),这等效于@Target(ElementType.METHOD),意味着 GET 注解应用于方法。接下来应用该注解,如下所示:

    public class AnnotationTest {
        @GET(value = "http://ip.taobao.com/59.108.54.3")
        public String getIpMsg() {
            return "";
        }
    
        @GET(value = "http://ip.taobao.com/")
        public String getIp() {
            return "";
        }
    }
    

    上面的代码为@GET 的成员变量赋值。接下来写一个简单的注解处理器,如下所示:

    public class AnnotationProcessor {
        private static final String TAG = "AnnotationProcessor";
    
        public static void initAnnotation() {
            Method[] methods = AnnotationTest.class.getDeclaredMethods();
            for (Method m : methods) {
                GET get = m.getAnnotation(GET.class);
                Log.v(TAG, get.value());
            }
        }
    }
    

    上面的代码用到了两个反射方法:getDeclaredMethods 和 getAnnotation,它们都属于AnnotatedElement 接口,Class、Method 和 Filed 等类都实现了该接口。调用 getAnnotation 方法返回指定类型的注解对象,也就是 GET。最后调用 GET 的 value 方法返回从 GET 对象中提取元素的值。

    调用注解处理器:

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            AnnotationProcessor.initAnnotation();
        }
    }
    

    输出结果为:

    注解输出结果

    1.3.2 编译时注解处理器

    处理编译时注解的步骤稍微有点多,首先要定义注解。
    (1)定义注解
    这里首先在项目中新建一个 Java Library 来专门存放注解,这个 Library 名为 annotations。接下来定义注解,如下所示:

    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.FIELD)
    public @interface BindView {
        int value() default 1;
    }
    

    上面代码中定义的注解类似于 ButterKnife 的@BindView 注解。
    (2)编写注解处理器
    我们在项目中再新建一个 Java Library 来存放注解处理器,这个 Library 名为 processor。我们来配置 processor 库的 build.gradle:

    apply plugin: 'java'
    dependencies {
     compile fileTree(include: ['*.jar'], dir: 'libs')
     compile project(':annotations')
    }
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
    

    接下来编写注解处理器 ClassProcessor,它继承 AbstractProcessor,如下所示:

    public class ClassProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //TODO 处理注解
            return false;
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotations = new LinkedHashSet<String>();
            annotations.add(BindView.class.getCanonicalName());
            return annotations;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    }
    
    

    process 方法的实现会在后文讲到,这里首先分别介绍这 4 个方法的作用。
    • init:被注解处理工具调用,并输入 ProcessingEnviroment 参数。ProcessingEnviroment提供很多有用的工具类,比如 Elements、TypesFiler 和 Messager 等。
    • process:相当于每个处理器的主函数 main(),在这里写你的扫描、评估和处理注解的代码,以及生成 Java 文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。
    • getSupportedAnnotationTypes:这是必须指定的方法,指定这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
    • getSupportedSourceVersion :用来指定你使用的 Java 版本,通常这里返回SourceVersion.latestSupported()。 在Java 7 以后,也可以使用注解来代替getSupportedAnnotationTypes 方法getSupportedSourceVersion方法,如下所示:

    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    @SupportedAnnotationTypes("com.lzw.annotations.BindView")
    public class ClassProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            return false;
        }
    }
    

    但是考虑到 Android 兼容性的问题,这里不建议采用这种注解的方式。接下来编写还未实现的 process 方法,如下所示:

        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Messager messager = processingEnv.getMessager();
            for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
                if (element.getKind() == ElementKind.FIELD) {
                    messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:"
                            + element.toString());
                }
            }
            return true;
        }
    

    这里用到 Messager 的 printMessage 方法来打印出注解修饰的成员变量的名称,这个名称会在 Android Studio 的 Gradle Console 窗口中打印出来。

    (3)注册注解处理器
    为了能使用注解处理器,需要用一个服务文件来注册它。现在我们就来创建这个服务文件。

    • 首先在 processor 库的 main 目录下新建 resources 资源文件夹
    • 接下来在 resources 中再建立META-INF/services 目录文件夹。
    • 最后在 META-INF/services 中创建 javax.annotation.processing.Processor 文件,这个文件中的内容是注解处理器的名称。
      这里我们的 javax.annotation.processing.Processor 文件的内容为 com.lzw.annotationprocessor.ClassProcessor。

    整个项目的目录结构如图 9-1 所示


    注解demo项目结构

    如果你觉得前面创建服务文件的步骤比较麻烦,也可以使用 Google 开源的 AutoService,它用来自动生成 METAINF/services/javax.annotation.processing.Processor 文件。首先我们添加该开源库,可以在 File→Project Structure 搜索“auto-service”查找该库并添加,如下图所示。也可以在 processor 的 build.gradle 中直接添加如下代码:

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation project(":annotations")
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
        compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
    }
    

    最后在注解处理器 ClassProcessor 中添加@AutoService(Processor.class)就可以了:

    @AutoService(Processor.class)
    public class ClassProcessor extends AbstractProcessor {
    ...
    }
    

    (4)应用注解
    接下来在我们的主工程项目(app)中引用注解。首先要在主工程项目的 build.graldle 中引用 annotations 和 processor 这两个库:

    dependencies {
    ...
        implementation project(':annotations')
        annotationProcessor project(':annotationProcessor')
    }
    

    接下来在 MainActivity 中应用注解,如下所示:

    public class MainActivity extends AppCompatActivity {
    
        //编译时注解
        @BindView(value = 111)
        TextView tv_show_name;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //运行时注解
            AnnotationProcessor.initAnnotation();
        }
    }
    

    最后,我们先 Clean Project 再 Make Project,在 Gradle Console 窗口中打印的结果如图所示:

    编译时注解结果

    可以发现编译时会打印出@BindView 注解修饰的成员变量名:tv_show_name。

    (5)使用 android-apt 插件(这个问题目前还未解决)
    我们的主工程项目(app)中引用了 processor 库,但注解处理器只在编译处理期间需要用到,编译处理完后就没有实际作用了,而主工程项目添加了这个库会引入很多不必要的文件。为了处理这个问题我们需要引入插件 android-apt。它主要有两个作用:
    • 仅仅在编译时期去依赖注解处理器所在的函数库并进行工作,但不会打包到 APK 中。
    • 为注解处理器生成的代码设置好路径,以便 Android Studio 能够找到它。
    接下来介绍如何使用它。首先需要在整个工程(Progect)的 build.gradle 中添加如下语句:

    buildscript {
     ...
     dependencies {
     ...
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
     } }
    

    接下来在主工程项目(app)的 build.gradle 中以 apt 的方式引入注解处理器 processor,如下所示:

    ...
    apply plugin: 'com.neenbedankt.android-apt'
    ...
    dependencies {
    ...
     //compile project(':processor')
     apt project(':processor')
    }
    

    相关文章

      网友评论

          本文标题:注解-学习笔记

          本文链接:https://www.haomeiwen.com/subject/gpgrnktx.html