美文网首页Java学习笔记技术干货我爱编程
生成Java源文件 (javawriter, javapoet,

生成Java源文件 (javawriter, javapoet,

作者: 元亨利贞o | 来源:发表于2016-09-20 21:29 被阅读3275次

    开发工具为Android Studio

    一. 使用JavaWriter生成java源文件

    • (1) 介绍
      JavaWritersquare开源项目javapoet中的一个分支, JavaWriter的整个库中有一个关键的类com.squareup.javawriter.JavaWriter(一共只有两个类), 主要用来生成Java源文件, 使用链式调用依次构建Java源文件. JavaWriter生成Java源文件的方式比较原始。由于整个库一共才两个类, 因此没有对Java源代码进行建模, 如class、field、method等没有对应的数据结构, 只有对应的方法来构建生成这些固定的概念。JavaWriter使用起来比较简单,整个库也非常小。官网介绍如下:

    JavaWriter
    is a utility class which aids in generating Java source files.
    Source file generation can useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.

    • (2) 使用步骤
      • 在Android Studio中创建一个Java library Module
      • 在上一步创建的module的构建脚本<module>/build.gradle中添加JavaWriter的依赖, 如下:
    dependencies {
          compile 'com.squareup:javawriter:2.5.1'
    }
    
    • 编写java源文件生成代码

    • (3) 使用案例

    import com.squareup.javawriter.JavaWriter;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.util.EnumSet;
    
    import javax.lang.model.element.Modifier;
    
    public class Demo1 {
    
        public static void main(String[] args) throws IOException {
            testJavaWriter();
        }
    
        /**
         * javawriter的github地址: https://github.com/square/javapoet/tree/javawriter_2
         * 使用下面语句引用该库 (仓库为jcenter):
         * compile 'com.squareup:javapoet:1.7.0'
         *
         * 使用JavaWriter生成java源文件
         * @throws IOException
         */
        private static void testJavaWriter() throws IOException {
            String packageName = "com.example.javawriter.generate";
            String className = "GenerateClass";
            File outFile = new File("java-demo/src/main/java/" + packageName.replaceAll("\\.", "/") + "/" + className + ".java");
            if(!outFile.getParentFile().exists()) {
                outFile.getParentFile().mkdirs();
            }
            if (!outFile.exists()) {
                outFile.createNewFile();
            }
            System.out.println(outFile.getAbsolutePath());
            OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outFile));
            JavaWriter jw = new JavaWriter(writer);
            jw.emitPackage(packageName)
                    .beginType(packageName + "." + className, "class", EnumSet.of(Modifier.PUBLIC, Modifier.FINAL))
                    .emitField("String", "firstName", EnumSet.of(Modifier.PRIVATE))
                    .emitField("String", "lastName", EnumSet.of(Modifier.PRIVATE))
                    .emitJavadoc("Return the person's full name")
                    .beginMethod("String", "getName", EnumSet.of(Modifier.PUBLIC))
                    .emitStatement("return firstName + \" - \" + lastName")
                    .endMethod()
                    .beginMethod("String", "getFirstName", EnumSet.of(Modifier.PUBLIC))
                    .emitStatement("return firstName")
                    .endMethod()
                    .beginMethod("String", "getLastName", EnumSet.of(Modifier.PUBLIC))
                    .emitStatement("return lastName") //注意不要使用分号结束return语句
                    .endMethod()
                    .endType()
                    .close();
        }
    
    }
    

    运行程序, 生成源文件如下:

    使用JavaWriter生成的Java源代码.png

    详细用法可以参考官网用例https://github.com/square/javapoet/blob/javawriter_2/src/test/java/com/squareup/javawriter/JavaWriterTest.java

    二. 使用javapoet生成Java源文件

    • (1) 介绍
      javapoet是大名鼎鼎的square公司开源的一个项目, github地址: https://github.com/square/javapoet. javapoet要比JavaWriter稍微复杂. 此库定义了一系列的数据结构来表示Java源文件中某些固定的概念, 如class、interface、annoation、field、method等。javapoet对java源文件的结构进行了建模, 其模型如下:
    2C698ACC-98EF-47A5-B829-4C5BDF03A3C6.png

    javapoet官网对其介绍如下:

    JavaPoet
    JavaPoet is a Java API for generating .java source files.
    Source file generation can be useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.

    • (2) 使用步骤
      • 在Android Studio中创建一个Java Library Module
      • 在上一步创建的Module的<module>/build.gradle构建脚本中添加javapoet库的依赖, 如下:
    dependencies {
            compile 'com.squareup:javapoet:1.7.0'
    }
    
    • 编写生成Java源文件的代码

    • (3) 使用案例

    import com.squareup.javapoet.FieldSpec;
    import com.squareup.javapoet.JavaFile;
    import com.squareup.javapoet.MethodSpec;
    import com.squareup.javapoet.TypeName;
    import com.squareup.javapoet.TypeSpec;
    
    import java.io.File;
    import java.io.IOException;
    
    import javax.lang.model.element.Modifier;
    
    /**
     * @author stone
     * @date 16/9/12
     */
    public class Demo2 {
    
        public static void main(String[] args) {
            testJavaPoet();
        }
    
    
        /**
         * 库: https://github.com/square/javapoet/
         * 使用下面语句引用javapoet (仓库为jcenter):
         * compile 'com.squareup:javawriter:2.5.1'
         *
         * 使用javapoet生成java源文件的步骤 (1,2,3步骤可以交换):
         * 1. 构建成员变量
         * 2. 构建构造方法
         * 3. 构建方法(static/concrete)
         * 4. 构建类型(enum/annotation/interface/class)
         * 5. 构建java源文件
         * 6. 输出java源文件到文件系统
         */
        private static void testJavaPoet() {
            String packageName = "com.stone.demo.javawriter";
            String className = "HelloWorld";
    
            //1. 生成一个字段
            FieldSpec fieldSpec = FieldSpec.builder(String.class, "var", Modifier.PUBLIC).build();
    
            //2. 生成一个方法 (方式一: 面向代码, 更为底层的构建方式)
            MethodSpec mainMethod = MethodSpec.methodBuilder("main")  //设置方法名称
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)   //添加修饰符
                    .addParameter(String[].class, "args")             //添加参数
                    .returns(TypeName.VOID)                           //添加返回值
                    .addStatement("$T.out.println($S)", System.class, "Hello world !")  //添加代码语句 (结束语句的分号不需要, 注意与CodeBlock的区别)
                    .build();
    
            //2. 生成一个方法 (方式二: 对方法建模, 结构化的构建)
    //        ParameterSpec parameterSpec = ParameterSpec.builder(String[].class, "args").build();  //构建参数模型
    //        CodeBlock codeBlock = CodeBlock.of("$T.out.println($S);", System.class, "Hello world"); //构建代码块 (语句结束的分号不能少)
    //        MethodSpec methodSpec = MethodSpec.methodBuilder("main")    //设置方法名称
    //                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)     //添加修饰符
    //                .returns(TypeName.VOID)                             //添加返回值
    //                .addParameter(parameterSpec)                        //添加方法参数
    //                .addCode(codeBlock)                                 //添加代码块
    //                .build();
    
    
            //3. 生成类型(enum/class/annotation/interface)
            TypeSpec hellworld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC)
                    .addField(fieldSpec)
                    .addMethod(mainMethod)
    //                .addMethod(methodSpec)
                    .build();
            
            //4. 构建Java源文件
            JavaFile javaFile = JavaFile.builder(packageName, hellworld).build();
    
            //5. 输出java源文件到文件系统
            try {
                //输出到控制台
    //            javaFile.writeTo(System.out);
    
                //生成java源文件到AndroidStudio的当前Module中
                generateToCurrentAndroidStudioModule(javaFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 生成到当前module的源文件目录下
         *
         * @param javaFile
         * @throws IOException
         */
        private static void generateToCurrentAndroidStudioModule(JavaFile javaFile) throws IOException {
            String targetDirectory = "java-demo/src/main/java"; //输出到和用例程序相同的源码目录下
            File dir = new File(targetDirectory);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            javaFile.writeTo(dir); //JavaFile.write(), 参数为源码生成目录(源码的classpath目录)
        }
    
    
    }
    

    运行程序, 生成如下源文件:

    使用javapoet生成Java源文件.png

    详细用法, 参考官网用例:
    https://github.com/square/javapoet/tree/master/src/test/java/com/squareup/javapoet

    三. 使用codemodel生成Java源文件

    • (1) 介绍
      官网如是说:

    CodeModel project
    CodeModel is a Java library for code generators; it provides a way to generate Java programs in a way much nicer than PrintStream.println(). This project is a spin-off from the JAXB RI for its schema compiler to generate Java source files.
    ------ From here: https://codemodel.java.net/

    IBM Developers是酱纸介绍的:

    CodeModel 是用于生成 Java 代码的 Java 库,它提供了一种通过 Java 程序来生成 Java 程序的方法。
    CodeModel 项目是 JAXB 的子项目。JAXB(Java Architecture for XML Binding)是一项可以根据 XML Schema 产生 Java 类的技术,它提供了将 XML 实例文档反向生成 Java 对象树的方法,并能将 Java 对象树的内容重新写到 XML 实例文档。JAXB 是 JDK 的组成部分。JAXB RI(Reference Implementation)即 schema compiler 能够将 XML 的 schema 文件映射为相应的 Java 元素。
    ------ From here: http://www.ibm.com/developerworks/cn/java/j-lo-codemodel/

    我觉的:
    它就是一个生成Java源代码的库 ! (哈! (⌒^⌒)b) !
    但是我还是想多说几句, codemodel和javapoet差不多, 都对java源文件进行了建模, 都有相关的数据结构来表述源文件中固定的概念, 这样用户使用起来会更加方便, 只是增加了复杂度和理解上的困难. 其实只要我们按coding的顺序(先声明包...再import依赖包...再声明class...然后生命成员变量...再然后声明方法......)来构建也是挺好理解的.
    下面贴几张图:

    codemodel官网.png

    对此图有两点说明, 右下角显示:
    a. codemodel版权属于oracle公司
    b. 此库已经很久没有更新了

    点击左边导航栏的Download按钮跳到codemodel的maven仓库, 这里可以下载codemodel的jar包和源码. 这里再贴一图:

    codemodel下载.png
    此图说明codemodel从2011年开始就不再更新了 (自从sun被oracle收购之后, oracle对很多java业务就不再关心. 因为某些鸡肋的业务不赚钱啊...呵呵...), 有好心的开发者fork了codemodel源码并进行了维护升级. 如下:
    https://github.com/UnquietCode/JCodeModel
    https://github.com/phax/jcodemodel

    喂喂, 楼主, 这是写侦探小说么?! 不喜勿喷哈 ヾ _

    • (2) 使用步骤
      • 在Android Studio中创建一个Java Library Module
      • 在上一步创建的Module的<module>/build.gradle构建脚本中添加codemodel的依赖库, 如下:

    dependencies {
    compile 'com.sun.codemodel:codemodel:2.6'
    }

      * 编写生成Java源文件的代码
    
    * (3) 使用案例
    
    

    package com.example.javawriter;

    // 注意不要引用了错误的包, 有internal的包是jdk内部使用的包
    // import com.sun.codemodel.internal.ClassType;
    // import com.sun.codemodel.internal.JDefinedClass
    // import com.sun.codemodel.internal.JBlock
    // ....

    // 下面的包是独立出来的codemodel库的包, 这个包是没有internal的
    import com.sun.codemodel.ClassType;
    import com.sun.codemodel.JBlock;
    import com.sun.codemodel.JClassAlreadyExistsException;
    import com.sun.codemodel.JCodeModel;
    import com.sun.codemodel.JConditional;
    import com.sun.codemodel.JDefinedClass;
    import com.sun.codemodel.JExpr;
    import com.sun.codemodel.JFieldVar;
    import com.sun.codemodel.JMethod;
    import com.sun.codemodel.JMod;
    import com.sun.codemodel.JVar;

    import java.io.File;
    import java.io.IOException;

    public class Demo3 {

    public static void main(String[] args) throws IOException, JClassAlreadyExistsException {
        testCodeModel();
    }
    
    /**
     * 使用codemodel生成Java源文件:
     * 在构建脚本中添加codelmodel的依赖库 (仓库为jcenter):
     * compile 'com.sun.codemodel:codemodel:2.6'
     *
     * @throws JClassAlreadyExistsException
     * @throws IOException
     */
    private static void testCodeModel() throws JClassAlreadyExistsException, IOException {
        final String className = "com.stone.generate.Person";
    
        /************ 生成一个Java源文件模型 ************/
        JCodeModel model = new JCodeModel();
    
        /************ 为模型添加一个顶级类型 (添加一个类) ************/
        JDefinedClass klass = model._class(JMod.PUBLIC, className, ClassType.CLASS);
    
        /************ 添加一个静态成员变量 ************/
        int modifier = JMod.PRIVATE + JMod.STATIC + JMod.FINAL;
        JFieldVar jStaticFieldVar = klass.field(modifier, String.class, "TAG");
        //jStaticFieldVar.assign(JExpr.lit(klass.fullName()));  //error, 不能对未初始化成员变量进行赋值, 要先进行初始化;
        jStaticFieldVar.init(JExpr.lit(klass.fullName()));
    
        /************ 添加一个成员变量 ************/
        //原始类型变量(int, byte,char ....)才可以使用JType.parse(model, "int"), Object类型直接使用Object.class
        //JFieldVar jFieldVar = klass.field(JMod.PRIVATE, JType.parse(model, "String"), "name"); //java.lang.IllegalArgumentException: Not a primitive type: String
        JFieldVar jFieldVar = klass.field(JMod.PRIVATE, String.class , "name");
        jFieldVar.annotate(MyAnnotation.class); //给字段添加一个注解
    
        /************ 添加一个构造方法 ************/
        JMethod constructor = klass.constructor(JMod.PRIVATE);
        constructor.param(String.class, "name");  //为构造方法添加一个参数
        JBlock constructorBlock = constructor.body();
        constructorBlock.assign(JExpr.refthis("name"), constructor.params().get(0));        //初始化成员变量
        constructorBlock.directStatement("System.out.println(\"Constructor invoked !\");"); //直接定义语句
    
    
        /************ 添加一个成员方法 ************/
        JMethod jMethod = klass.method(JMod.PUBLIC, Void.TYPE, "setName");  //参数依次为: 修饰符, 返回类型, 方法名
        //jMethod.param(JType.parse(model, "String"), "name"); //java.lang.IllegalArgumentException: Not a primitive type: String
        jMethod.param(String.class, "name");
        JBlock methodBlock = jMethod.body();                                //构建方法体
        methodBlock.assign(JExpr.refthis("name"), jMethod.params().get(0)); //在方法体中生成一句赋值语句 (为成员变量赋值)
    
        //在方法块中定义两个局部变量
        //JVar var = jBlock.decl(model.INT, "age");                     //先声明变量, 再进行初始化 (两步完成)
        //var.init(JExpr.lit(23));
        JVar var = methodBlock.decl(model.INT, "age", JExpr.lit(100));  //声明变量, 同时进行初始化 (一步到位)
        JVar isAgeGreatThan_25 = methodBlock.decl(model.BOOLEAN, "isAgeGreatThan_25", var.gt(JExpr.lit(25)));
    
    
        //构造一个if...else...语句块
        JBlock if_else_block =  new JBlock();
        JConditional jConditional = if_else_block._if(isAgeGreatThan_25);
        jConditional._then().directStatement("System.out.println(\"Age great than 25\");"); //语句结束时不要忘了分号
        jConditional._else().directStatement("System.out.println(\"Age less than 25\");");
    
        //将if...else...语句块添加到方法语句块中
        methodBlock.add(if_else_block);
    
    
        /************ 构建生成一个Java源文件 ************/
        model.build(new File("java-demo/src/main/java"));
    }
    

    }

    上面用到的自定义注解如下: 
    
    

    package com.example.javawriter;

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

    @Retention(RetentionPolicy.CLASS)
    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
    public @interface MyAnnotation {
    String value() default "Read the fucking source code !";
    }

    
    运行程序, 生成如下Java源文件:  
    
    ![codemodel生成的Java源文件.png](https://img.haomeiwen.com/i1642441/497df81a186a5f3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
     
    
    四. 三者的异同
    1. 三个库都是用来生成java源文件的
    2. JavaWriter就是一个工具类, 使用起来简单容易理解, 但是要手动拼接源文件中的语句, 因此容易出出现拼写错误. 
    3. javapoet和codemodel都对java源文件进行了建模. 结构化java源文件后, 用户使用时必须使用library提供的数据结构, 代码的拼接生成由库处理, 因此不会产生拼写错误, 使用起来也比较方便. 
    4. 个人感觉javapoet比codemodel使用起来更加方便, 抽象出来的概念也更少, 更加容易理解. codemodel属于重量级的库, 它几乎对java源文件中的所有概念都进行了抽象, 如: 类、字段、方法、变量、语句块、循环......等等 , 因此使用起来非常繁琐, 理解起来也更加困难.
    5. javapoet和codemodel生成java源文件的步骤是相反的: javapoet是先构建字段、方法、构造器、语句 ...... 等等, 然后添加到一个类(TypeSpec)中, 也就是说, javapoet是先构建细节, 然后再组织整体骨架, 是先分后总的逻辑; codemodel恰恰相反, codemodel构建java源文件非常类似于构建一颗DOM树, 先构建根节点(JCodeModel), 然后再构建其他分支节点(JDefinedClass、JFieldVar、JMethod ......), 最后再构建比分支节点更细节的支节点 (JBlock、JExpr、JConditional ......)。
    
    五. 使用场景
    这三个库主要就是用来生成Java源文件. 那么什么时候需要生成java源文件呢? 使用注解或解析注解的地方需要(生成那些需要我们重复劳动的代码 --- 减少我们的负担!). 那么什么地方会使用注解和解析呢? 使用注解的地方非常多, 如大部分的ORM框架([Realm](https://realm.io/)、[OrmLite](http://ormlite.com/)、[DBFlow](http://www.appance.com/dbflow/)、[ActiveAndroid](http://www.activeandroid.com/)、[greenDAO](http://greenrobot.org/greendao/)... 等)、依赖注入框架([Dagger](http://square.github.io/dagger/)、[ButterKnife](http://jakewharton.github.io/butterknife/)、 [guice](https://github.com/google/guice)、[RoboGuice](https://github.com/roboguice/roboguice) ... 等)、编译时检查框架(support-annotations、[jcip](https://github.com/jcip/jcip.github.com) ...等)以及很多其他优秀框架([Retrofit](http://square.github.io/retrofit/)、[retrolambda](https://github.com/orfjackal/retrolambda), [PermissionsDispatcher](http://hotchemi.github.io/PermissionsDispatcher/), [RxPermissions](https://github.com/tbruyelle/RxPermissions), [EventBus](http://greenrobot.org/eventbus/) ... 等) ......
    
    
    references 
    [用 Java 生成 Java - CodeModel 介绍](http://www.ibm.com/developerworks/cn/java/j-lo-codemodel/)
    [codemodel](https://codemodel.java.net/)
    [javapoet](https://github.com/square/javapoet)

    相关文章

      网友评论

        本文标题:生成Java源文件 (javawriter, javapoet,

        本文链接:https://www.haomeiwen.com/subject/knzcettx.html