您好,欢迎来到源码搜藏!分享精神,快乐你我!提示:担心找不到本站?在百度搜索“源码搜藏”,网址永远不丢失!
  • 首 页
  • 在线工具
  • 当前位置:首页 > 安卓源码 > 技术博客 >

    JavaPoet动态生成java文件源码初探

    时间:2016-09-27 09:49 来源:互联网 作者:源码搜藏 浏览:收藏 挑错 推荐 打印

    JavaPoet是用于代码生成的开源编程框架,利用JavaPoet可以方便生成.java文件。代码生成技术相当于元编程,可用于编译期根据注解等元数据动态生成java类。广泛使用的dagger,butterknife框架就是利用JavaPoet对注入注解生成所需类。相关地址:github地址 Java

    JavaPoet是用于代码生成的开源编程框架,利用JavaPoet可以方便生成.java文件。代码生成技术相当于元编程,可用于编译期根据注解等元数据动态生成java类。广泛使用的dagger,butterknife框架就是利用JavaPoet对注入注解生成所需类。相关地址:github地址

    JavaPoet使用

      JavaPoet使用比较简便,以简单的helloworld代码为例,如下:

    package com.example.helloworld;
    
    public final class HelloWorld {
      public static void main(String[] args) {
        System.out.println("Hello, JavaPoet!")
      使用JavaPoet生成helloword代码如下所示:
    MethodSpec main = MethodSpec.methodBuilder("main")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
        .returns(void.class)
        .addParameter(String[].class, "args")
        .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
        .build();
    
    TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
        .addMethod(main)
        .build();
    
    JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
        .build();
    
    javaFile.writeTo(System.out);

      其中,JavaFile是对.java文件的抽象,TypeSpec是类的抽象,MethodSpec是方法的抽象。JavaPoet提供T,S等标识符替代字符串,TclassS代表string。

    Java文件模型

      JavaPoet中对.java文件及其组成进行抽象, 其java文件模型如下:

    JavaPoet动态生成java文件源码初探

      可以看出,JavaPoet定义java文件分为注释、import代码、类型定义三部分,其中类型(TypeSepc)和方法(MethodSepc)为框架中核心类,这对应java文件的元素组成逻辑也不难理解。

      在JavaPoet中,所有java文件的抽象元素都定义了emit方法,如TypeSepc,ParameterSepc等,emit方法传入CodeWriter对象输出字符串。上层元素调用下层元素的emit方法,如JavaFile的emit方法调用TypeSpec的emit方法,从而实现整个java文件字符串的生成。

      所有的java文件抽象元素的emit方法最终都会调用CodeWriter的emit方法,CodeWriter是对字符串输出的抽象。

    TypeSpec/MethodSpec

      TypeSpec是类型元素的抽象,通过Kind枚举定义class、interface、enum、annotation四种类型。TypeSpec使用Builder根据传入不同的Kind常量创建对象。TypeSpec中包含FiledSpec和MethodSpec的列表对象,在emit中遍历列表调用其emit方法。

      MethodSpec代表方法的抽象。MethodSpec包含ParameterSepc列表对象和CodeBlock对象,CodeBlock是代码块的抽象类。CodeBlock中提供了beginControlFlow,nextControlFlow,endControlFlow等方法便于生成控制代码,同时,CodeBlock提供了L,S,T,N占位符替代字符串。

      MethodSpec的emit方法代码如下:

    void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
            throws IOException {
        codeWriter.emitJavadoc(javadoc);
        codeWriter.emitAnnotations(annotations, false);
        codeWriter.emitModifiers(modifiers, implicitModifiers);
    
        if (!typeVariables.isEmpty()) {
            codeWriter.emitTypeVariables(typeVariables);
            codeWriter.emit(" ");
        }
    
        if (isConstructor()) {
            codeWriter.emit("$L(", enclosingName);
        } else {
            codeWriter.emit("$T $L(", returnType, name);
        }
    
        boolean firstParameter = true;
        for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) {
            ParameterSpec parameter = i.next();
            if (!firstParameter) codeWriter.emit(", ");
            parameter.emit(codeWriter, !i.hasNext() && varargs);
            firstParameter = false;
        }
    
        codeWriter.emit(")");
    
        if (defaultValue != null && !defaultValue.isEmpty()) {
            codeWriter.emit(" default ");
            codeWriter.emit(defaultValue);
        }
    
        if (!exceptions.isEmpty()) {
            codeWriter.emit(" throws");
            boolean firstException = true;
            for (TypeName exception : exceptions) {
                if (!firstException) codeWriter.emit(",");
                codeWriter.emit(" $T", exception);
                firstException = false;
            }
        }
    
        if (hasModifier(Modifier.ABSTRACT)) {
            codeWriter.emit(";\n");
        } else if (hasModifier(Modifier.NATIVE)) {
            codeWriter.emit(code);
            codeWriter.emit(";\n");
        } else {
            codeWriter.emit(" {\n");
    
            codeWriter.indent();
            codeWriter.emit(code);
            codeWriter.unindent();
    
            codeWriter.emit("}\n");
         以上可以看出,MethodSepc通过调用codeWriter的emit方法依次输出javadoc,annotation,parameter,codeblock等方法组成元素。

    自动插入import代码

      JavaPoet框架另一个设计巧妙的地方在于能自动识别需要import的类型,如下所示:

    ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
    ClassName list = ClassName.get("java.util", "List");
    ClassName arrayList = ClassName.get("java.util", "ArrayList");
    TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
    
    MethodSpec beyond = MethodSpec.methodBuilder("beyond")
        .returns(listOfHoverboards)
        .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
        .addStatement("result.add(new $T())", hoverboard)
        .addStatement("return result")
        .build();
    
    // 生成代码
    package com.example.helloworld;
    import com.mattel.Hoverboard;
    import java.util.ArrayList;
    import java.util.List;
    
    public final class HelloWorld {
      List<Hoverboard> beyond() {
        List<Hoverboard> result = new ArrayList<>();
        result.add(new Hoverboard());
        return result;
      }
    }

      JavaPoet能自动识别需要import的类型并生成import代码,如上代码中的Hoverboard和ArrayList类。其中ClassName代表类名的抽象。

      在实现上,JavaPoet两次生成java文件字符串,在第一次中生成字符串用于收集import的类型信息,第二次才输出字符串到文件中。JavaFile中的writeTo方法代码如下:

    public void writeTo(Appendable out) throws IOException {
        // First pass: emit the entire class, just to collect the types we'll need to import.
        CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports);
        emit(importsCollector);
        Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();
    
        // Second pass: write the code, taking advantage of the imports.
        CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports);
        emit(codeWriter);
      上面代码可以看出,第一次输出字符串到NULL_APPENDABLE对象中,只作收集import类型。CodeWriter中包含importedTypes数据列表,记录需要import的类型。

    总结

      利用JavaPoet框架在编译期动态生成java文件,相当于提供了一种java的元编程方式,而在一些对性能要求较高的环境,动态生成java代码技术是替代运行时反射技术的一个很好的选项。

    JavaPoet动态生成java文件源码初探转载http://www.codesocang.com/anzhuoyuanma/boke/33708.html
    标签:网站源码