java-apt的实现之Element详解

基本介绍

element指的是一系列与之相关的接口集合,它们位于javax.lang.model.element包下面

element_1.png

以下是官方文档对element的定义

Represents a program element such as a package, class, or method. Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine)

即element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。element是java-apt(编译时注解处理器)技术的基础,因此如果要编写此类框架,熟悉element是必须的。

element及其子接口

  • 各种element所代表的元素类型
element_2.png

通过上图可以看到element元素及其子接口所指代的元素,有点类似Type类型,Type的用法可以参考另一篇博客java Type详解,那么element接口族和Type接口族之间有什么区别呢?element所代表的元素只在编译期可见,用于保存元素在编译期的各种状态,而Type所代表的元素是运行期可见,用于保存元素在编译期的各种状态

简单的例子

要在编译期得到工程的element信息,我们需要采用apt技术(以Android开发为例,在Android studio中):

  1. 创建注解

创建注解(在单独的一个java module中)

//注解的声明周期声明为source,意即只在编译源文件的过程中有效
@Retention(RetentionPolicy.SOURCE)
//通常一个注解只注释类,方法,或者变量,这样可以使结构更清晰。这里为了演示不同的element,把这个注解的使用对象定义为类,变量,参数,方法
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
public @interface AAAAA {
    String value();
}

  1. 在目标类中使用注解(主module)
package com.lu.aptdemo;

import com.lu.annotation.AAAAA;

/**
 * @Author: luqihua
 * @Time: 2018/6/20
 * @Description: Test
 */
@AAAAA("this is a class")
public class Test<T> {

    @AAAAA("this is a field")
    String hello;

    @AAAAA("this is a method")
    public String say(@AAAAA("this is a parameter") String arg1) {
        return "hello world";
    }
}
  1. 创建处理器Processor(单独的一个java-module),需要用到下面三个库
 implementation 'com.google.auto:auto-common:0.10'
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {

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

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //扫描整个工程   找出含有AAAAA注解的元素(包括类,)
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AAAAA.class);
        //由于编译器的输出无法打印到控制台,因此这里借助javapoet库把需要输出的信息写入到一个新的类
        //这个是我封装的一个简单的工具
        ProcessorTool.Builder builder = new ProcessorTool.Builder().setProcessingEnv(processingEnv);
        for (Element element : elements) {
            AAAAA aaaaa = element.getAnnotation(AAAAA.class);
            if (element instanceof TypeElement) {
                builder.addArgs(" TypeElement: " + aaaaa.value());

                /*===============打印包信息=================*/
                builder.addArgs("=============================打印包信息================================");
                PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(element);
                builder.addArgs("packageElement:  " + packageElement.getSimpleName().toString());
                builder.addArgs("packageElement:  " + packageElement.getQualifiedName());


                builder.addArgs("=============================打印泛型信息================================");
                List<? extends TypeParameterElement> typeParameters = ((TypeElement) element).getTypeParameters();
                for (TypeParameterElement typeParameter : typeParameters) {
                    builder.addArgs(typeParameter.getSimpleName().toString());
                }
                builder.addArgs("=============================================================");

            } else if (element instanceof ExecutableElement) {
                builder.addArgs("ExecutableElement: " + aaaaa.value());
            } else if (element instanceof VariableElement) {
                builder.addArgs(" VariableElement: " + aaaaa.value());
            }
        }
        builder.build().printLog();
        return true;
    }
}



//==========================
在主工程module的build->generate->source->apt->test->Logger.java

查看输出

package test;

import java.lang.String;

class Logger {
  void test() {
    String arg0=" TypeElement: this is a class";
    String arg1="=============================打印包信息================================";
    String arg2="packageElement.getSimpleName(): aptdemo";
    String arg3="packageElement.getQualifiedName(): com.lu.aptdemo";
    String arg4="=============================打印泛型信息================================";
    String arg5="T";
    String arg6="=============================================================";
    String arg7=" VariableElement: this is a field";
    String arg8="ExecutableElement: this is a method";
    String arg9=" VariableElement: this is a parameter";
  }
}



ProcessorTool类


public class ProcessorTool {
    private ProcessingEnvironment processingEnv;
    private List<String> args = new ArrayList<>();

    public ProcessorTool(ProcessingEnvironment env) {
        this.processingEnv = env;
    }

    public ProcessorTool addArgs(String arg) {
        args.add(arg);
        return this;
    }

    /**
     * 用于打印log到文件
     */
    public void printLog() {
        TypeSpec.Builder builder = TypeSpec.classBuilder("Logger");

        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("test");

        int len = args.size();
        for (int i = 0; i < len; i++) {
            String arg = args.get(i);
            methodBuilder.addStatement("$T arg" + i + "=$S", String.class, arg);
        }

        builder.addMethod(methodBuilder.build());

        JavaFile javaFile = JavaFile.builder("test", builder.build())
                .build();
        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

element 接口方法

type的介绍中我们可以通过type的一系列方法获得元素信息,尤其是通过type我们可以在运行期间获取注解信息,再通过反射动态代理实现AOP设计,而element类既然在编译期可见,自然我们想到了在编译期获取元素的各类信息,结合apt技术实现动态的代码生成。

  • <R,P> R accept(ElementVisitor<R,P> v, P

接收一个ElementVisitor类,它的作用类似于一个if(element instanceof ExecutableElement) 则调用 visitExecutable(ExecutableElement executableElement, Void aVoid);两个泛型一般写Void就行,如果需要接收accept方法的返回值,则根据返回值的类型定义R,P基本上是用不到的,写Void就行

示例代码

element.accept(new SimpleElementVisitor7<Void, Void>() {
                @Override
                public Void visitType(TypeElement typeElement, Void aVoid) {
                    return super.visitType(typeElement, aVoid);
                    //这是一个TypeElement
                }

                @Override
                public Void visitExecutable(ExecutableElement executableElement, Void aVoid) {
                    return super.visitExecutable(executableElement, aVoid);
                    //这是一个executableElement
                }

                @Override
                public Void visitPackage(PackageElement packageElement, Void aVoid) {
                    return super.visitPackage(packageElement, aVoid);
                    //这是一个PackageElement
                }

            }, null);
  • TypeMirror asType()

返回一个TypeMirror是元素的类型信息,包括包名,类(或方法,或参数)名/类型,在生成动态代码的时候,我们往往需要知道变量/方法参数的类型,以便写入正确的类型声明

示例代码

 for (Element element : elements) {
            if (element instanceof TypeElement) {

                TypeName typeName = ClassName.get(element.asType());

                TypeSpec typeSpec = TypeSpec.classBuilder("GenerateTest")
                        .addField(typeName, "test")
                       //添加泛型信息
                       .addTypeVariable(TypeVariableName.get(((TypeElement) element).getTypeParameters().get(0)))
                        .build();

                try {
                    JavaFile.builder("com.test", typeSpec)
                            .build()
                            .writeTo(processingEnv.getFiler());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

//===================生成代码


package com.test;
//通过ClassName包装之后,在生成对应代码中会自动导入类型的包
import com.lu.aptdemo.Test;

class GenerateTest<T> {
  Test<T> test;
}

  • <A extends Annotation> A getAnnotation(Class<A> annotationType)

根据传入的注解类型获取该元素上的注解

  • List<? extends AnnotationMirror> getAnnotationMirrors()

获取该元素上的注解的类型信息,AnnotationMirror类似于TypeMirror

  • List<? extends Element> getEnclosedElements()

返回该元素直接包含的子元素,通常对一个PackageElement而言,它可以包含TypeElement;对于一个TypeElement而言,它可能包含属性VariableElement,方法ExecutableElement,

示例代码

 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AAAAA.class);
        //这个是我封装的一个简单的工具
        ProcessorTool tool = new ProcessorTool(processingEnv);
        for (Element element : elements) {
            if (element instanceof TypeElement) {
                 for (Element element1 : element.getEnclosedElements()) {
                    tool.addArgs(element1.getSimpleName().toString());
                }
            } 
        }
        tool.printLog();
        
//输出 Test类包含构造方法,属性Hello  方法say()

class Logger {
  void test() {
    String arg0="<init>";
    String arg1="hello";
    String arg2="say";
  }
}
        
  • Element getEnclosingElement()

返回包含该element的父element,与上一个方法相反,VariableElement,方法ExecutableElement的父级是TypeElemnt,而TypeElemnt的父级是PackageElment

示例代码

Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AAAAA.class);
        //这个是我封装的一个简单的工具
        ProcessorTool tool = new ProcessorTool(processingEnv);
        for (Element element : elements) {
            tool.addArgs(element.getSimpleName().toString()+".getEnclosingElement(): "+element.getEnclosingElement().getSimpleName().toString());
        }
        tool.printLog();

//输出代码

class Logger {
  void test() {
    String arg0="Test.getEnclosingElement(): aptdemo";
    String arg1="hello.getEnclosingElement(): Test";
    String arg2="say.getEnclosingElement(): Test";
    String arg3="arg1.getEnclosingElement(): say";
  }
}

  • ElementKind getKind()

返回element的类型,判断是哪种element

  • Set<Modifier> getModifiers()

获取修饰关键字,入public static final等关键字

  • Name getSimpleName()

获取名字,不带包名

  • Name getQualifiedName()

这个方法是element的子接口所带的方法,element本身并不指代具体的元素,因此没有改方法。获取全限定名,如果是类的话,包含完整的报名路径

ExecutableElement
  • List<? extends VariableElement> getParameters()

用于获取方法的参数元素,每个元素是一个VariableElement

  • TypeMirror getReturnType()

获取方法元素的返回值,返回衣蛾TypeMirror表示

VariableElement
  • Object getConstantValue()

如果属性变量被final修饰,则可以使用该方法获取它的值

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容

  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 6,514评论 4 31
  • 在经过一次没有准备的面试后,发现自己虽然写了两年的android代码,基础知识却忘的差不多了。这是程序员的大忌,没...
    猿来如痴阅读 2,843评论 3 10
  • Chapter 6 Enums and Annotations 枚举和注解 JAVA supports two s...
    LaMole阅读 816评论 0 2
  • 1,认识新朋友 那是一个初夏,她今年13岁,正是无忧无虑的时候。然后,她今年上初中了。 “呼呼呼...
    废物迷妹i阅读 238评论 0 2
  • 2018年3月15日 星期4 雨 昨晚睡得迷迷糊糊感觉下雨了,想起来收衣服的,硬是起不着。就感觉被什么东西拉住起不...
    艺龄菇凉阅读 279评论 0 1