Android Annotation-让你的代码和设计更加优雅(一)

栏目: Android · 发布时间: 7年前

内容简介:最近我写了一篇关于组件化的开源框架源码分析的文章(传送门在下面儿)。那么现在组件化小有名气的JIMU框架,也是我下一个要给大家分享的源码分析文章。但因为其中涉及到了很多“

最近我写了一篇关于组件化的开源框架源码分析的文章(传送门在下面儿)。那么现在组件化小有名气的JIMU框架,也是我下一个要给大家分享的源码分析文章。但因为其中涉及到了很多 Java Annotation 相关的知识。所以不得不在这里,先安利一下本篇,这也是本篇的由来。

优秀框架源码分析系列(一)让解耦更轻松!多进程组件化框架-ModularizationArchitecture

注解 ”,在 Java 世界里随处可见,但通常情况下,多数人对其是视而不见的。但当我们设计SDK,设计基础库的时候,运用注解,可以起到 简化配置 的作用。熟悉 ButterKnife 的朋友都知道,它就是通过注解来在编译期间,增加Java代码来实现的。如果你还不知道它是如何实现的,那么相信你食用完本篇和下一篇以后,就会明白这一切了。

食用路线

学习新知识的时候,要掌握正确的进食方法,脑子里必须先对知识结构有预期,学习完之后再回顾结构,根据结构记住知识。本篇将按照导图的结构,来进行讲解。

Android Annotation-让你的代码和设计更加优雅(一)

扎实打基础

基础知识-四大元注解

元注解,就是用来修饰注解的注解。

@Target(value=ElementType)

@Target 被用来指明此Annotation 所修饰的对象范围 (即:被描述的注解可以用在什么地方)。Java中,注解( Annotation )可被用于以下位置 :

  • package、types(类、接口、枚举、Annotation类型)
  • 类型成员(方法、构造方法、成员变量、枚举值)
  • 方法参数和本地变量(如循环变量、catch参数)

注解的使用范围,通过 Target 的取值来指定。指定好以后, @Target 修饰的元素一定是与其取的 value 相匹配的,否则编译会报错。

value 取值( ElementType )常见的有:

ElementType 含义
CONSTRUCTOR 用于描述构造器
FIELD 用于描述域(包括enum常量)
LOCAL_VARIABLE 用于描述局部变量
METHOD 用于描述方法
PACKAGE 用于描述包
PARAMETER 用于描述参数
TYPE 用于描述类、接口(包括注解类型) 或enum声明

注:PACKAGE,它并不是使用在一般的类中,而是用在固定的文件package-info.java中。这里需要强调命名一定是“package-info”

这里需要特殊说明的是,在以前的Java版本中,开发者只能将注解(Annotation)写在声明中。但从Java 8开始,注解可以写在使用类型的任何地方,例如声明、泛型和强制类型转换等语句:

@Encrypted String str;
List<@NonNull String> strs;
test = (@Immutable Test) tmpTest;
复制代码

针对这个拓展,JAVA8对原有的@Target的取值做了扩充,引入了新的 类型(TYPE) 注解,即 ElmentType 增加了:

ElementType 含义
TYPE_PARAMETER 表示注解可以用在类型变量的声明语句中(如 class Test {...})
TYPE_USE 表示注解可以用在使用类型的任何语句中(如声明语句、泛型和强转)

关于 类型 的解释参考上文。

@Retention(value=RetentionPolicy)

@Retention ,翻译为 保留 ,指示了一个注解被保留的时间期限,一个被其修饰的注解会被保留到其 value 指定三个阶段的其中之一,如果注解类型声明中不存在 Retention 注解,则保留策略默认为 CLASS

RetentionPolicy 含义
RetentionPolicy.SOURCE 只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS 在编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME 被JVM保留,所以他能在运行时被JVM或其他使用反射机制的代码所读取和使用

@Documented

被@Documented修饰的注解,用来表示这个被修饰注解应该被 javadoc工具记录。默认情况下,javadoc是不包括注解的。但如果声明注解时指定了@Documented,则它会被javadoc之类的 工具 处理,所以注解类型信息也会被包括在生成的文档中。

@Inherited

如果一个用来修饰class的注解,希望被这个class的sub-class继承,则可以对这个注解使用@Inherited修饰。上面这句话强调了以下两点:

  • 注解的可继承性。当自定义的注解希望被继承时,就要使用@Inherited修饰
  • @Inherited只在修饰class时有效,修饰其他类型时无效

自定义注解如何写-Java Annotation的语法

自定义注解,通过@符号和interface关键字来定义。类似于class的写法,例如:

package com.xm.annotation;
import java.lang.annotation.Documented;
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;

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnno{
    String name();
    String website() default "example.com";
    int version() default 1;
}
复制代码

当注解需要参数的时候,我们需要先定义注解的方法,方法名即参数名。这里有点类似于接口的定义,如果参数需要默认值,则在方法后加 default + 默认值 的方式来实现。

例如本例中,我们指定了三个参数, name、website、version 。分别定义了三个方法, name 没有指定默认值,所以它默认为null, website 指定了默认值为 example.com ,而 version 则指定了1为其默认值。

这里有三点规则强调一下:

  1. 注解方法不带参数,比如 name()website()
  2. 注解方法返回值类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型
  3. 注解方法可有默认值,比如 default "example.com" ,默认website=”example.com”

当我们在使用时,需要给参数传递值,很简单:

@TestAnno(name="xiaoming", website="example.com", version=1)
复制代码

后文对这个例子中的其他部分,还会有详细的解释。

高效学精髓

为了方便学习,我们拿最常见的Java内建的注解 @Override 来食用。把握一下自定义注解实现时的几个步骤。

1. 编写定义注解的Java文件

要自定义注解,首先要创建一个以注解名字命名的Java文件,并使用@interface关键字来定义注解

Override.java

package java.lang

public @interface Override {
}
复制代码

2. 确定自定义注解的作用范围

这个也很容易理解,Override注解用来修饰方法,所以作用范围就指定为METHOD。

3. 确定自定义注解的作用时限

SOURCE、CLASS、RUNTIME的保留时限依次增加。而对于Override来说,我们日常编写代码时,通过这个注解可以知道哪些方法是被重写的。这个提示,也仅仅停留在了源码层面,所以这里使用SOURCE。

4. 确定自定义注解的参数、方法

Override并没有使用任何的参数和方法,这里也忽略了,后面我们实战例子里会重点介绍。

基于上述分析,我们写出了如下的定义源码:

package java.lang;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
复制代码

通过以上的三个步骤,我们就定义好了一个注解。当我们使用时,直接通过@符号加注解名称即可。那么,加了注解的元素,有什么用呢?

其一,我们可以通过编译期处理,为注解所在的元素自动生成其他代码。像 ButterKnife 等均是如此,它帮助我们生成了View与组件的绑定,实现了点击监听器的绑定等等。保留到此时限的注解,我们也称其为编译期注解。

其二,我们在运行时,可以通过反射,获取到注解信息,这些注解信息,往往是程序编写者希望传递给运行时使用的一些信息或配置,可以起到简化配置的作用。像 Spring2.5 以后,运用了大量的运行时注解,它在实现AOP方面,有着广泛的应用。需要强调的是,这里的注解,都是 RUNTIME 规则的,只有这样才能保留到运行时。所以我们称其为运行时注解。

本篇我们重点介绍代码编写阶段的提示性注解和运行时注解。下面我们再通过两个实际例子来理解一下。

大胆练实战(一)

提示性注解示例

我们在java中,除了 Override ,还会经常见到一些其他的内建注解。例如 SuppressWarnings (压制警告),他用于告知编译器忽略特定的警告信息,如在泛型中使用原始数据类型。

package java.lang;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,
        ElementType.PARAMETER, ElementType.CONSTRUCTOR,
        ElementType.LOCAL_VARIABLE })
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The list of warnings a compiler should not issue.
     */
    public String[] value();
}
复制代码

按我们上节所需编写自定义注解的几个步骤,来分别分析一下:

注解的作用范围

SuppressWarnings 可用于除注解类型声明和包名之外的所有元素。所以这里的 Target 做了相应的指定。

注解的作用时限

这里的警告,都是静态语法检查类型的警告信息,所以这个注解也只需要保留在源码层面。即 SOURCE

注解的参数方法

SuppressWarning 指定了一个 String 类型的数组。它支持了多个字符串参数。其可取值为需要压制的警告类型,见下表:

参数 含义
deprecation 使用了过时的类或方法时的警告
unchecked 执行了未检查的转换时的警告
fallthrough 当Switch程序块进入进入下一个case而没有Break时的警告
path 在类路径、源文件路径等有不存在路径时的警告
serial 当可序列化的类缺少serialVersionUID定义时的警告
finally 任意finally子句不能正常完成时的警告
all 以上所有情况的警告

使用时,可按如下的方法赋值:

@SupressWarning(value={"uncheck","deprecation"})
复制代码

运行时注解示例

针对我们上一节中自定义的注解TestAnno,我们来实践一下运行时注解。

准备工作

package com.xm.annotation;
import java.lang.annotation.Documented;
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;

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnno{
    String name();
    String website() default "example.com";
    int version() default 1;
}
复制代码

假设我们在如下的代码块中应用了此注解:

package com.xm.annotation;

public class AnnotationDemo {
    @AuthorAnno(name="xiaoming", website="example.com", version=1)
    public static void main(String[] args) {
        System.out.println("I am main method");
    }

    @SuppressWarnings({ "unchecked", "deprecation" })
    @AuthorAnno(name="suby", website="example2.com", version=2)
    public void demo(){
        System.out.println("I am demo method");
    }
}
复制代码

针对注解解析

现在,我们在运行时,通过反射来解析自定义的注解@TestAnno,关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口定义了注释相关的几个核心方法,如下:

返回值 方法 含义
T getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
Annotation[] getAnnotations() 返回此元素上存在的所有注解
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 当存在该元素的指定类型注解,则返回true,否则返回false

前面我们自定义的注解,适用对象为Method。类Method继承类AccessibleObject,而类AccessibleObject实现了AnnotatedElement接口,那么可以利用上面的反射方法,来实现解析@TestAnno的功能(AnnotationParser.java),内容如下:

package com.xm.annotation;
import java.lang.reflect.Method;

public class AnnotationParser {
    public static void main(String[] args) throws SecurityException, ClassNotFoundException {
        String clazz = "com.xm.annotation.AnnotationDemo";
        Method[]  demoMethod = AnnotationParser.class
                .getClassLoader().loadClass(clazz).getMethods();

        for (Method method : demoMethod) {
            if (method.isAnnotationPresent(TestAnno.class)) {
                 AuthorAnno authorInfo = method.getAnnotation(TestAnno.class);
                 System.out.println("method: "+ method);
                 System.out.println("name= "+ authorInfo.name() +
                         " , website= "+ authorInfo.website()
                        + " , version= "+authorInfo.version());
            }
        }
    }
}
复制代码

程序的输出结果:

method: public void com.xm.annotation.AnnotationDemo.demo()
name= suby , website= example2.com , version= 2
method: public static void com.xm.annotation.AnnotationDemo.main(java.lang.String[])
name= xiaoming , website= example.com , version= 1
复制代码

由此可见,我们可以通过在编写代码时,将一些运行时需要的信息,通过注解的方式传递给运行时的代码,以达到信息传递和配置的目的。在Spring中,也大量采用了运行时注解,为程序的配置,以及程序开发,特别是AOP编程,都提供了极大的便利。

下一篇重点介绍编译期注解,在众多的知名框架或工具型SDK中,都能看见它的身影,它可以以一种极度优雅简洁的方式,为我们提供开发上的便捷。

小铭出品,必属精品

欢迎关注xNPE技术论坛,更多原创干货每日推送。

Android Annotation-让你的代码和设计更加优雅(一)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

On LISP

On LISP

Paul Graham / Prentice Hall / 09 September, 1993 / $52.00

On Lisp is a comprehensive study of advanced Lisp techniques, with bottom-up programming as the unifying theme. It gives the first complete description of macros and macro applications. The book also ......一起来看看 《On LISP》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换