生成Java源文件 (javawriter, javapoet, codemodel)

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,441评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,571评论 18 399
  • 关键字:TextInputLayout、TextInputEditText、材料设计项目地址:AboutMater...
    Arnold_J阅读 545评论 0 2
  • 看到了第十一集,只能说感同身受。回忆如潮水涌来:高考结束时告别的目光、吃醋时趴桌上生无可恋的表情、体检前温柔的别怕...
    米花小子阅读 241评论 0 0