深入理解Java注解类型

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 "";
    }
}

定义一个注解首先用@interface来声明,例子中的@Target(ElementType.METHOD)代表此注解应用在方法之上,@Retention(RetentionPolicy.RUNTIME)代表此注解的生命周期是保存到运行时,String name() default ""是一个注解元素,注解元素可以在使用的时候传值,在声明的时候用default给予一个初始值

定义注解跟定义类有点相似,如果拿类来做比较的话,我认为可以这样理解,@interface对应于类的class关键字,FunAnno对应于类的类名,大括号里面的name对应类的属性字段,和类最大的不同的是注解上面还有类似于@Target、@Retention这样的字段,其实这些也是注解,只不过它们是修饰注解的注解,我们称之为元注解,元注解是注解中重要的一部分,很大程度上定义了这个注解的属性,下面来详细解析一下什么是元注解

一:元注解

常见的元注解有:@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 {

    }

    //有Inherited的注解修饰父类A
    @AnnoA
    class A{
    }

    class B extends A{
    }


    //没有Inherited的注解修饰父类C
    @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的时候,大括号内有一个String类型的元素name,这个就是注解元素。在定义注解的时候,通常都会包含一些注解元素,如下:


     /**
     * 定义一个注解PersonInfo,包含有三个注解元素,分别代表名字、年龄、性别
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface PersonInfo{

        //注解元素及其默认值
        String name() default "";

        int age() default -1;

        boolean male() default false;
    }

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


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


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

上面展示了注解元素的定义及其使用,注解元素的作用可以为注解定义各种类型的变量,注解元素和元注解构成了注解的最重要两部分,元注解定义注解的性质,比如生命周期、应用范围等等,注解元素则定义了注解的属性变量

下面是注解元素支持的数据类型:

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

  • String

  • Class

  • enum

  • Annotation 注解嵌套

  • 上述类型的数组

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

public class AnnoElement {

    //定义一个注解
    @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 {""};
    }

    //嵌套的注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface AnnoRef{
        int value() default -1;
    }


    /**
     * 注解元素的使用
     */
    @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.运行时注解

下面是在运行时通过注解来构建一个SQL语句,进而创建数据库表的例子

首先定义一个包含注解的类,如下:

/**
 * 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 {

    //主键,属于一个string类型的字段,然后指定是否为主键为true
    @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;
        //获取class对象中的Entity注解
        RAnnotation.Entity entity = clazz.getAnnotation(RAnnotation.Entity.class);

        String tabelName; //数据库名称
        //如果没有指定数据库名称,就把类名称作为数据库名称
        if (entity == null || entity.tableName() == "") {
            tabelName = clazz.getSimpleName().toUpperCase();
        }else{
            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; //字段没有注解则不处理
            }

            //如果当前字段的注解是FeildInteger,说明当前字段是一个Integer类型
            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;

    }

    /**
     * 获取注解的相关约束值
     *
     * @param cons
     * @return
     */
    public static String getContrains(RAnnotation.Contrains cons) {
        String constraints = "";
        if (!cons.allowNull())
            constraints += " NOT NULL";
        if (cons.primaryKey())
            constraints += " PRIMARY KEY";
        if (cons.isUnique())
            constraints += " UNIQUE";
        return constraints;
    }

我们来看一下执行结果:

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

上面是一个利用运行时注解拼接SQL语句的例子,关键是利用Class对象去找到注解的相关信息,下面我来看看编译时注解的应用

2.编译时注解

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

下面我们模仿butterknife的实现原理,实现一个类似功能的简单例子,首先在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);
}

来看看注解处理器的实现,这个很关键:

@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集合,
        // 因为我们用于成员变量上
        //所有被Bind注解标识的元素,此时的Element是一个通用接口
        Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(BindView.class);
        //一、收集信息

        //遍历所有被注解BindView标识的元素
        for (Element element : elesWithBind) {
            //检查element类型是否有效
            if (!checkAnnotationValid(element, BindView.class)) continue;
            //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);
            }
            //获取元素的注解和注解元素的值,即view的id
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            //将元素和对应的id保存起来,其实variableElement是View,id是View的id
            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(元素),这个接口有几个实现,代表字段元素、类元素、方法元素等等,可以通过Element获得元素对应的注解信息。CodeCreater是一个代码生成器,通过它可以为每一个使用注解的activity或fragment自动生成一个ViewInject类,ViewInject类就是在编译阶段自动的生成的代码了,它的作用是给标识了注解的view变量赋值,来看看CodeCreater:

package com.lib_java.compileAnnotation;

import java.util.HashMap;
import java.util.Map;

import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;

public class CodeCreater {

    private String packageName;
    //自动生成类的全路径名:使用注解的类的包路径 + 使用注解的类的名称$$ViewInject
    private String createrClassName;
    //使用注解的类
    private TypeElement typeElement;

    //保存findViewById要用到View元素和对应的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;
    }
    
    /**
     * 生成对应的Java代码
     * @return
     */
    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();

    }

    /**
     * 生成对应方法
     * @param builder
     */
    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”

完成了注解处理器以后,在编译阶段就会自动生成一个初始化view变量的代理类,这个代理类大概是这样的:

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( 2131230772));

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

    }
  }

}

代理类通过实现ViewInject接口,这个接口也是我们提前定义好的,如下

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

inject方法是View的注入方法,是使用的时候通过调用代理类的inject方法来完成View的注入,此方法有两个参数,第一个参数是使用注解那个类的类全路径名,第二个参数是这个类的类型

最后为了方便使用,我们定义一个对外的接口ViewInjector类,通过injectView方法,调用对应代理类的注入方法inject,然后就可以对注解的View变量赋值了,如下

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");

    }

成功通过,其实到最后还是通过findViewById方法给view赋值的,只是我们通过编译时的注解处理器,将这些代码自动生成了而不是自己手动去编写,这样节省了很多时间,提高了工作效率

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值