深入理解Java注解类型

版权声明:本文为博主石月的原创文章,转载请注明出处 https://blog.csdn.net/liuxingrong666/article/details/84336964

Java注解在实际应用中很广泛,目前很多主流的框架也采用了注解来提高效率,其实注解就是Java代码中的一个标记,也可以将它理解为对象,它有自己的相关属性和值,只是不实现相关方法而已。下面我们通过一个例子来分析一下注解。

public class Test {

    //添加自定义注解
    @FunAnno(name="我是方法a")
    public void fun_a(){
        LogUtils.d("执行方法a");
    }

    //添加java内置的注解
    @Deprecated
    @SuppressWarnings("uncheck")
    public void fun_b(){

    }


    /**
     * 定义一个注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FunAnno{
        String name() default "";
    }
}

上面代码中我们自定义了一个注解@FunAnno,注解用@interface来声明,@Target(ElementType.METHOD)代表此注解应用在方法上,@Retention(RetentionPolicy.RUNTIME)代表此注解的生命周期保存到运行时,String name() default ""用来声明一个String类型的注解元素,我们可以在使用注解的时候对其进行赋值,当然在声明的时候要用default定义其初始值。然后我们在fun_a方法中使用了@FunAnno注解。@Deprecated和@SuppressWarnings(“uncheck”)都是Java内置的注解,下面我们将会介绍它们的意义。

一:注解的语法

在上面例子中,我们定义注解的时候用到了@Target和@Retention注解,其实这两个是Java提供的元注解,所谓的元注解就是标记其他注解的注解,我们常见的元注解有:@Target、@Retention、@Documented、@Inherited、@Repeatable(java8新增)。

1. @Target

用来约束注解应用的地方(比如方法、类、字段等等),其注解元素接收一个枚举数组ElementType[],代表注解应用范围

//Target注解源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

//value范围
public enum ElementType {

   /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,

    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,

    /** 标明该注解可以用于方法声明 */
    METHOD,

    /** 标明该注解可以用于参数声明 */
    PARAMETER,

    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,

    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,

    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,

    /** 标明注解可以用于包声明 */
    PACKAGE,

    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     */
    TYPE_PARAMETER,

    /**
     * 类型使用声明(1.8新加入)
     */
    TYPE_USE

}

当Target未表明任何value时,代表此注解可以应用到任何元素上,还可以采用数组的方式表明value值,例如:@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})表明注解可以应用的范围是{}内的所有类型。

2.@Retention

该注解用来约束注解的生命周期,其接收三个值,分别是SOURCE(源码级别)、CLASS(类文件级别)、RUNTIME(运行时级别),详解如下:

  • SOURCE:该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里。

  • CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解@SuppressWarnning等。

  • RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)。

3.@Documented

@Documented 可以让被修饰的注解上传javadoc

4.@Inherited

可以让注解被继承,这里继承的意思是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,如下:

public class InheritedTest {


    /**
     * 被Inherited修饰的注解
     */
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoA {

    }

    /**
     * 没有Inherited修饰的注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoB {

    }

    @AnnoA
    class A{
    }

    class B extends A{

    }

    @AnnoB
    class C{

    }

    class D extends C{

    }

    public  void test(){
//        A instanceA=new A();
//        D instanceD=new D();

        LogUtils.d("使用了Inherited注解的情况:"+ Arrays.toString(B.class.getAnnotations()));
        LogUtils.d("没有使用了Inherited注解的情况:"+ Arrays.toString(D.class.getAnnotations()));
    }

}

运行结果如下:

上面我们讲解了注解的基本语法结构和常用的元注解分析,下面我们看看注解的元素值及其数据结构类型

二:注解元素及其数据类型

上面我们看到在用@FunAnno注解的时候,传进了一个值name=“我是方法a”,这个name就是注解元素,其数据类型为String。我们在定义注解的时候,常常都会包含一些元素及其值,方便注解处理器使用。如下:

/**
 * 使用注解标记Person类,给注解元素赋值
 */
@Person.PersonInfo(name = "王大锤",age = 18,male = true)
public class Person {


    /**
     * 定义一个注解,包含有三个注解元素,分别代表名字、年龄、性别
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface PersonInfo{
        String name() default "";
        int age() default -1;
        boolean male() default false;
    }

    /**
     * 注解元素的简化使用方式,只定义value()即可
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Age{
        int value() default -1;
    }


    /**
     * 注解的简化使用
     */
    @Age(20)
    class Lily{
        
    }
}

上面代码我们展示了注解元素的定义及其使用,注意简化方式的使用,如果简化方式中不止一个元素的话, 还是要使用value=“”的方式去赋值元素的。下面是注解元素支持的数据类型:

  • 所有基本类型(int,float,boolean,byte,double,char,long,short)

  • String

  • Class

  • enum

  • Annotation 注解嵌套

  • 上述类型的数组

下面我们用例子来展示所有注解元素数据类型的使用:

public class AnnoElement {

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoRef{
        int value() default -1;
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoElements{

        //定义性别枚举
        enum Gender{MALE,FEMALE};

        //声明枚举
        Gender gender() default Gender.FEMALE;

        //声明string
        String name() default "";

        //声明int
        int age() default -1;

        //声明class类型
        Class<?> Person() default Void.class;

        //注解嵌套
        AnnoRef ref() default @AnnoRef;

        //数组类型
        String[] strs() default {""};
    }


    /**
     * 注解元素的使用
     */
    @AnnoElements(gender = AnnoElements.Gender.MALE,name = "java",
            age = 100,Person = Person.class,ref = @AnnoRef(10),strs = {"a","b"})
    class Test{
    }
}

注解元素对默认值有严格的要求,对象不能用null表示,所以一般我们都用一些没有意义的值来表示默认值。像这些注解元素及其对应的值,我们的注解处理器是可以获取的,从而可以作为我们后面相关处理的依据。

 

三:Java内置注解

Java内置注解主要有三个,我们分别来看一下:

  • @Override:用于标明此方法覆盖了父类的方法,源码如下
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • Deprecated:用于标明已经过时的方法或类,源码如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
  • @SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

其中value值接收如下:

deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告; 
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; 
finally:任何 finally 子句不能正常完成时的警告; 
all:关于以上所有情况的警告。
 

四:注解与反射的关系

在反射包的相关类中实现了都实现了AnnotatedElement接口,通过此接口我们可以利用反射技术获得对应类的相关注解信息,反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口,下面我们来看看AnnotatedElement中的相关方法:

方法名称 返回值 说明
getAnnotation(Class<A> annotationClass) <A extends Annotation> 如果此元素存在指定类型的注解,那么返回这些注解,否则返回null
getAnnotations() Annotation[] 返回此元素的所有注解,包括从父类继承的
isAnnotationPresent(Class<? extends Annotation> annotationClass) boolean 如果指定类型的注解存在次元素上,则返回true,否则返回false

getDeclaredAnnotations()

Annotation[] 返回次元素上的所有注解,不包括从父类继承的注解

案例演示如下:

public class AnnoElementTest{

    @AnnoElementTest.AnnoA
    public static class A{

    }

    //B 继承A
    @AnnoElementTest.AnnoB
    public static class B extends A{

    }

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoA {

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoB {

    }

    public static void test(){
        B instanceB=new B();
        Class<?> classB=instanceB.getClass();

        //根据指定注解类型获得注解
        AnnoB annoB=classB.getAnnotation(AnnoB.class);
        LogUtils.d("根据指定注解获取注解:"+annoB);

        //获取此元素上的所有注解,包括从父类继承的
        Annotation[] annotations=classB.getAnnotations();
        LogUtils.d("获取所有注解包括继承的:"+ Arrays.toString(annotations));

        //获取此元素上所有注解,不包括继承的
        Annotation[] annotations1=classB.getDeclaredAnnotations();
        LogUtils.d("获取所有注解不包括继承的:"+Arrays.toString(annotations1));

        //判断注解AnnoA是否在次元素上
        boolean is=classB.isAnnotationPresent(AnnoB.class);
        LogUtils.d("是否="+is);
    }
}

执行结果如下:

在这里要获取到父类继承的注解,此注解上必须有@Inherited标记,否则获取不到。

 

五:运行时注解和编译时注解

运行时注解,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解,然后做一些操作。

编译时注解,在java的编译阶段,根据注解标识,动态生成一些类或xml文件,在运行时期,这些注解是没有的,我们是依靠动态生成的一些类做操作,由于没有反射, 效率和直接调用方法没什么区别。

所以我们可以看到,编译时注解的效率要比运行时注解高,很多框架如ButterKnife、EventBus都是用的编译时注解。下面我们通过例子去看看,运行时注解和编译时注解是怎么的一个实现过程。

1.运行时注解的处理

我们以通过运行时注解去创建一个数据库表为例展开, 先来看看注解的定义,如下:

/**
 * Created by XR_liu on 2018/11/22.
 * 运行时注解
 */
public class RAnnotation {


    /**
     * 约束注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Contrains{

        //是否为主键
        boolean primaryKey() default false;

        //是否允许为null
        boolean allowNull() default false;

        //是否唯一
        boolean isUnique() default false;
    }

    /**
     * 实体类注解
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Entity{
        String tableName() default "";
    }

    /**
     * 整型字段
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FeildInteger{
        //对应的字段名
        String name() default "";
        //嵌套注解
        Contrains contrain() default @Contrains;
    }

    /**
     * 字符串字段
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FeildString{

        String name() default "";

        int varchar() default 30;

        Contrains contrain() default @Contrains;
    }

}

我们定义的上面几个注解,都是为创建一个数据库表服务的, 注意生命周期都应该标注为RUNTIME,只有这样我们才能在运行时通过反射获取相关信息。下面通过定义一个Person类来看注解的使用:

/**
 * Created by XR_liu on 2018/11/22.
 */
@RAnnotation.Entity
public class Person {

    //主键
    @RAnnotation.FeildString(contrain = @RAnnotation.Contrains(primaryKey = true))
    private String id;

    @RAnnotation.FeildString
    private String name;

    @RAnnotation.FeildInteger
    private int age;

    //允许为null
    @RAnnotation.FeildString(contrain = @RAnnotation.Contrains(allowNull = true))
    private String address;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

下面我们就开始自己编写一个注解处理器,关键逻辑是通过Class对象去获取有注解的字段,然后再获取对应注解的相关信息,然后就可以根据这些信息去拼接一个SQL语句了,如下:

/**
     * 通过一个实体类返回一个创建数据库表的SQL语句
     *
     * @return
     */
    public static String createTableSql(Class<?> cl) {
        String sql = null;
        //获取class对象
        Class<?> clazz = cl;

        RAnnotation.Entity entity = clazz.getAnnotation(RAnnotation.Entity.class);

        String tabelName; //数据库名称
        if (entity == null || entity.tableName() == "") {
            tabelName = clazz.getSimpleName().toUpperCase();
        }
        tabelName = entity.tableName();

        //所有字段名的集合
        List<String> columNames = new ArrayList<>();

        //通过反射获取class的所有字段
        for (Field field : clazz.getDeclaredFields()) {
            String columName = null;
            //获取字段上的所有注解
            Annotation[] annotations = field.getDeclaredAnnotations();
            if (annotations.length < 1) {
                continue; //不属于表的字段
            }
            //整型字段
            if (annotations[0] instanceof RAnnotation.FeildInteger) {
                RAnnotation.FeildInteger feildInteger = (RAnnotation.FeildInteger) annotations[0];
                //给字段名赋值
                columName = "".equals(feildInteger.name()) ? field.getName().toUpperCase() : feildInteger.name();
                //保存构建SQL语句片段
                columNames.add(columName + " INT" + getContrains(feildInteger.contrain()));
            }

            //string字段
            if (annotations[0] instanceof RAnnotation.FeildString) {
                RAnnotation.FeildString feildString = (RAnnotation.FeildString) annotations[0];
                columName = "".equals(feildString.name()) ? field.getName().toUpperCase() : feildString.name();
                columNames.add(columName + " VARCHAR(" + feildString.varchar() + ")" + getContrains(feildString.contrain()));
            }

            //构建数据库表语句
            StringBuilder createSql = new StringBuilder("CREATE TABLE " + tabelName + "(");

            for (String colum : columNames) {
                createSql.append("\n " + colum + ",");
            }
            sql = createSql.substring(0, createSql.length() - 1) + ")";

        }
        return sql;

我们来看一下执行结果:

到这里成功地返回了一个正确的SQL语句!附上源码:https://github.com/jiusetian/AndroidStudyData/tree/master/app/src/main/java/com/androidstudydata/annotation

上面是运行时注解的例子,其中关键的是利用Class对象去寻找注解的相关信息,下面我们来看看编译时注解又是怎么回事的。

2.编译时注解

编译时注解主要利用的注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解的工具,就是在源代码的编译阶段,通过注解处理器,我们可以获得文件中注解的相关内容。常见的用途是,我们在获得注解相关数据之后,通过去生成有规律的代码,解决编程过程中的重复工作,大大提高了效率,比如ButterKnife、Dagger2等框架。

下面我们模仿butterknife的实现原理,自己去实现一个类似功能的简单例子,首先我们在注解实现的Java Module的Gradle文件中添加如下配置:

    compile 'com.google.auto.service:auto-service:1.0-rc2'//谷歌的帮助我们快速实现注解处理器
    compile 'com.squareup:javapoet:1.7.0'//用来生成java文件的

定义一个注解@BindView,如下:

/**
 * Created by XR_liu on 2018/11/24.
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}

当然注解的生命周期是编译阶段,适用范围为字段。定义一个注入view实例公共接口,如下

/**
 * Created by XR_liu on 2018/11/24.
 */
public interface ViewInject<T> {
    //T是指使用注解的类,viewOwner是注解view的持有者
    void inject(T t, Object viewOwner);
}

下面我们来看看怎么去实现这个注解处理器的,这个是关键,先看看代码:

/**
 * Created by XR_liu on 2018/11/24.
 */
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor{

    private Elements elementUtils;
    private Map<String, CodeCreater> codeCreaterMap = new HashMap<String, CodeCreater>();


    @Override
    public SourceVersion getSupportedSourceVersion()
    {
        return SourceVersion.latestSupported();
    }

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

        //Elements mElementUtils;跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。
        elementUtils = processingEnv.getElementUtils();
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(BindView.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        codeCreaterMap.clear();

        //element所代表的元素只在编译期可见,用于保存元素在编译期的各种状态,而Type所代表的元素是运行期可见,用于保存元素在编译期的各种状态
        //通过roundEnv.getElementsAnnotatedWith拿到我们通过@BindView注解的元素,这里返回值,按照我们的预期应该是VariableElement集合,因为我们用于成员变量上。
        Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(BindView.class); //所有被Bind注解标识的元素,此时的Element是一个通用接口
        //一、收集信息

        //接下来for循环我们的元素,首先检查类型是否是VariableElement(代表变量、字段)
        for (Element element : elesWithBind)
        {
            //检查element类型
            checkAnnotationValid(element, BindView.class);
            //field type
            VariableElement variableElement = (VariableElement) element; //因为上面检查过了,所以这里强转
            //class type,拿到对应的类元素
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            //full class name,类的全路径名
            String fqClassName = classElement.getQualifiedName().toString();

            //如果没有生成才会去生成一个新的,。
            CodeCreater codeCreater = codeCreaterMap.get(fqClassName);
            if (codeCreater == null)
            {
                //如果对应的类还没有生成代理类,才去创建一个并且保存
                codeCreater = new CodeCreater(elementUtils, classElement);
                codeCreaterMap.put(fqClassName, codeCreater);
            }
            //接下来,会将与该类对应的且被@BindView声明的VariableElement加入到codeCreater中去,key为我们声明时填写的id,即View的id
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class); //获取对应的注解对象
            int id = bindAnnotation.value();
            //如果这个字段属于某个类,这里就会保存到同一个codeCreater中,因为不同类的时候,这里的codeCreater是不同的
            codeCreater.injectVariables.put(id , variableElement);
        }

        //二、生成代理类
        for (String key : codeCreaterMap.keySet())
        {
            CodeCreater codeCreater = codeCreaterMap.get(key);
            try
            {
                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(
                        codeCreater.getCreaterClassFullName(),
                        codeCreater.getTypeElement());
                Writer writer = jfo.openWriter();
                //写入自动生成的代码
                writer.write(codeCreater.generateJavaCode());
                writer.flush();
                writer.close();
            } catch (IOException e)
            {
                error(codeCreater.getTypeElement(),
                        "Unable to write injector for type %s: %s",
                        codeCreater.getTypeElement(), e.getMessage());
            }

        }
        return true;
    }

    //要field字段和非private修饰才有效
    private boolean checkAnnotationValid(Element annotatedElement, Class clazz)
    {
        if (annotatedElement.getKind() != ElementKind.FIELD)
        {
            error(annotatedElement, "%s must be declared on field.", clazz.getSimpleName());
            return false;
        }
        //是否为私有的字段
        if (annotatedElement.getModifiers().contains(PRIVATE))
        {
            error(annotatedElement, "%s() must can not be private.", annotatedElement.getSimpleName());
            return false;
        }

        return true;
    }

    private void error(Element element, String message, Object... args)
    {
        if (args.length > 0)
        {
            message = String.format(message, args);
        }
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element);
    }
}

上面通过继承系统的AbstractProcessor来自己实现注解处理器,我们主要重写process方法,上面涉及到了一个很重要的接口Element(元素),这个接口有几个实现,代表字段元素、类元素、方法元素等等。就是通过这些元素,我们才能获得元素对应的注解信息。其中CodeCreater类是一个代码生成器,通过它我们可以为每一个使用注解的activity或view自动生成一个对应的ViewInject类,这个类就是给被BindView注解标识的元素赋值的。我们来看看代码:

public class CodeCreater {
    private String packageName;
    private String createrClassName;
    private TypeElement typeElement;

    //保存findViewById要用到的id值和对应的变量元素
    public Map<Integer, VariableElement> injectVariables = new HashMap<>();

    public static final String SUFFIX = "ViewInject";

    public CodeCreater(Elements elementUtils, TypeElement classElement) {
        this.typeElement = classElement;
        //获取注解元素类的全包名
        PackageElement packageElement = elementUtils.getPackageOf(classElement);
        String packageName = packageElement.getQualifiedName().toString();
        //classname
        String className = classElement.getQualifiedName().toString().substring(packageName.length() + 1);
        this.packageName = packageName;
        this.createrClassName = className + "$$" +SUFFIX;
    }


    public String generateJavaCode() {
        StringBuilder builder = new StringBuilder();
        builder.append("/* Generated code. Do not modify!*/\n");
        builder.append("package ").append(packageName).append(";\n\n"); //包路径
        builder.append("import com.lib_java.compileAnnotation.*;\n"); //导入包路径
        builder.append('\n');

        //类的声明并且继承ViewInject<T>接口,其中T是使用注解的那个类
        builder.append("public class ").append(createrClassName).append(" implements " + CodeCreater.SUFFIX +
                "<" + typeElement.getQualifiedName() + ">");
        builder.append(" {\n\n");

        generateMethods(builder);
        builder.append('\n');

        builder.append("}\n");
        return builder.toString();

    }


    private void generateMethods(StringBuilder builder) {

        //参加inject方法,实际上是实现ViewInject接口的方法
        builder.append("   @Override\n ");
        builder.append("  public void inject(" + typeElement.getQualifiedName() + " master, Object viewOwner ) {\n");

        for (int id : injectVariables.keySet()) {
            VariableElement element = injectVariables.get(id); //id对应的变量
            String name = element.getSimpleName().toString(); //变量名
            String type = element.asType().toString(); //变量类型
            //master代表使用注解的那个类的全限定名,所以master.name代表被注解的那个变量,其实也就是对应的activity对象
            builder.append("   if(viewOwner instanceof android.app.Activity){\n"); //如果master是activity
            builder.append("      master." + name).append(" = ");
            builder.append("(" + type + ")(((android.app.Activity)viewOwner).findViewById( " + id + "));\n");
            builder.append("\n   }else{\n");
            builder.append("      master." + name).append(" = ");
            builder.append("(" + type + ")(((android.view.View)viewOwner).findViewById( " + id + "));\n"); //master是View对象
            builder.append("\n    }\n");

        }
        builder.append("  }\n");


    }

    public String getCreaterClassFullName() {
        return packageName + "." + createrClassName;
    }

    public TypeElement getTypeElement() {
        return typeElement;
    }


}

我们可以看到,主要作用就是通过字符串的拼接去生成一个类,这个类的名字为使用者的类名+“$$ViewInject”。什么注解处理器也完成了,下面我们就暴露一个使用接口ViewInjector类,用这个类来完成最后的绑定工作,如下:

public class ViewInjector
{
    private static final String SUFFIX = "$$ViewInject";

    public static void injectView(Activity activity)
    {
        //找到自动生成的代理类
        ViewInject proxyActivity = findProxyActivity(activity);
        proxyActivity.inject(activity, activity); //执行注入方法,两个参数都是对应的activity对象
    }

    //object是持有被注解字段的对象,view是指我们要findViewById那个view对象, 也可以是activity对象
    public static void injectView(Object object, View view)
    {
        ViewInject proxyActivity = findProxyActivity(object);
        proxyActivity.inject(object, view);
    }

    private static ViewInject findProxyActivity(Object activity)
    {
        try
        {
            Class clazz = activity.getClass();
            Class injectorClazz = Class.forName(clazz.getName() + SUFFIX);
            return (ViewInject) injectorClazz.newInstance();
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        } catch (InstantiationException e)
        {
            e.printStackTrace();
        } catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
        throw new RuntimeException(String.format("can not find %s , something when compiler.", activity.getClass().getSimpleName() + SUFFIX));
    }
}

下面我们来测试一下


public class MainActivity extends AppCompatActivity {

    @BindView(R.id.bindView)
    Button BindBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewInjector.injectView(this); //调用注入
        BindBtn.setText("成功绑定了view");

    }

最后测试是成功通过的,最后我们来看看自动生成的那个MainActivity$$ViewInject是怎么样的,如下

/* Generated code. Do not modify!*/
package com.androidstudydata;

import com.lib_java.compileAnnotation.*;

public class MainActivity$$ViewInject implements ViewInject<com.androidstudydata.MainActivity> {

   @Override
   public void inject(com.androidstudydata.MainActivity master, Object viewOwner ) {
   if(viewOwner instanceof android.app.Activity){
      master.BindBtn = (android.widget.Button)(((android.app.Activity)viewOwner).findViewById( 2131230762));

   }else{
      master.BindBtn = (android.widget.Button)(((android.view.View)viewOwner).findViewById( 2131230762));

    }
  }

}

可以看到,其实到最后还是通过findViewById方法去给view赋值的,只是我们通过编译时的注解处理器,将这些代码自动生成了,这样就节省了我们很多时间,提高了效率。

好了,关于注解的知识就讲到这里了!附上源码:

https://github.com/jiusetian/AndroidStudyData/tree/master/lib-java/src/main/java/com/lib_java/compileAnnotation

猜你喜欢

转载自blog.csdn.net/liuxingrong666/article/details/84336964
今日推荐