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

開(kāi)發(fā)工具為Android Studio

一. 使用JavaWriter生成java源文件

  • (1) 介紹
    JavaWritersquare開(kāi)源項(xiàng)目javapoet中的一個(gè)分支, JavaWriter的整個(gè)庫(kù)中有一個(gè)關(guān)鍵的類com.squareup.javawriter.JavaWriter(一共只有兩個(gè)類), 主要用來(lái)生成Java源文件, 使用鏈?zhǔn)秸{(diào)用依次構(gòu)建Java源文件. JavaWriter生成Java源文件的方式比較原始。由于整個(gè)庫(kù)一共才兩個(gè)類, 因此沒(méi)有對(duì)Java源代碼進(jìn)行建模, 如class勺拣、field、method等沒(méi)有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu), 只有對(duì)應(yīng)的方法來(lái)構(gòu)建生成這些固定的概念蓝丙。JavaWriter使用起來(lái)比較簡(jiǎn)單,整個(gè)庫(kù)也非常小。官網(wǎng)介紹如下:

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中創(chuàng)建一個(gè)Java library Module
    • 在上一步創(chuàng)建的module的構(gòu)建腳本<module>/build.gradle中添加JavaWriter的依賴, 如下:
dependencies {
      compile 'com.squareup:javawriter:2.5.1'
}
  • 編寫(xiě)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
     * 使用下面語(yǔ)句引用該庫(kù) (倉(cāng)庫(kù)為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") //注意不要使用分號(hào)結(jié)束return語(yǔ)句
                .endMethod()
                .endType()
                .close();
    }

}

運(yùn)行程序, 生成源文件如下:

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

詳細(xì)用法可以參考官網(wǎng)用例https://github.com/square/javapoet/blob/javawriter_2/src/test/java/com/squareup/javawriter/JavaWriterTest.java

二. 使用javapoet生成Java源文件

  • (1) 介紹
    javapoet是大名鼎鼎的square公司開(kāi)源的一個(gè)項(xiàng)目, github地址: https://github.com/square/javapoet. javapoet要比JavaWriter稍微復(fù)雜. 此庫(kù)定義了一系列的數(shù)據(jù)結(jié)構(gòu)來(lái)表示Java源文件中某些固定的概念, 如class、interface吁恍、annoation、field播演、method等冀瓦。javapoet對(duì)java源文件的結(jié)構(gòu)進(jìn)行了建模, 其模型如下:
2C698ACC-98EF-47A5-B829-4C5BDF03A3C6.png

javapoet官網(wǎng)對(duì)其介紹如下:

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中創(chuàng)建一個(gè)Java Library Module
    • 在上一步創(chuàng)建的Module的<module>/build.gradle構(gòu)建腳本中添加javapoet庫(kù)的依賴, 如下:
dependencies {
        compile 'com.squareup:javapoet:1.7.0'
}
  • 編寫(xiě)生成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();
    }


    /**
     * 庫(kù): https://github.com/square/javapoet/
     * 使用下面語(yǔ)句引用javapoet (倉(cāng)庫(kù)為jcenter):
     * compile 'com.squareup:javawriter:2.5.1'
     *
     * 使用javapoet生成java源文件的步驟 (1,2,3步驟可以交換):
     * 1. 構(gòu)建成員變量
     * 2. 構(gòu)建構(gòu)造方法
     * 3. 構(gòu)建方法(static/concrete)
     * 4. 構(gòu)建類型(enum/annotation/interface/class)
     * 5. 構(gòu)建java源文件
     * 6. 輸出java源文件到文件系統(tǒng)
     */
    private static void testJavaPoet() {
        String packageName = "com.stone.demo.javawriter";
        String className = "HelloWorld";

        //1. 生成一個(gè)字段
        FieldSpec fieldSpec = FieldSpec.builder(String.class, "var", Modifier.PUBLIC).build();

        //2. 生成一個(gè)方法 (方式一: 面向代碼, 更為底層的構(gòu)建方式)
        MethodSpec mainMethod = MethodSpec.methodBuilder("main")  //設(shè)置方法名稱
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)   //添加修飾符
                .addParameter(String[].class, "args")             //添加參數(shù)
                .returns(TypeName.VOID)                           //添加返回值
                .addStatement("$T.out.println($S)", System.class, "Hello world !")  //添加代碼語(yǔ)句 (結(jié)束語(yǔ)句的分號(hào)不需要, 注意與CodeBlock的區(qū)別)
                .build();

        //2. 生成一個(gè)方法 (方式二: 對(duì)方法建模, 結(jié)構(gòu)化的構(gòu)建)
//        ParameterSpec parameterSpec = ParameterSpec.builder(String[].class, "args").build();  //構(gòu)建參數(shù)模型
//        CodeBlock codeBlock = CodeBlock.of("$T.out.println($S);", System.class, "Hello world"); //構(gòu)建代碼塊 (語(yǔ)句結(jié)束的分號(hào)不能少)
//        MethodSpec methodSpec = MethodSpec.methodBuilder("main")    //設(shè)置方法名稱
//                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)     //添加修飾符
//                .returns(TypeName.VOID)                             //添加返回值
//                .addParameter(parameterSpec)                        //添加方法參數(shù)
//                .addCode(codeBlock)                                 //添加代碼塊
//                .build();


        //3. 生成類型(enum/class/annotation/interface)
        TypeSpec hellworld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC)
                .addField(fieldSpec)
                .addMethod(mainMethod)
//                .addMethod(methodSpec)
                .build();
        
        //4. 構(gòu)建Java源文件
        JavaFile javaFile = JavaFile.builder(packageName, hellworld).build();

        //5. 輸出java源文件到文件系統(tǒng)
        try {
            //輸出到控制臺(tái)
//            javaFile.writeTo(System.out);

            //生成java源文件到AndroidStudio的當(dāng)前Module中
            generateToCurrentAndroidStudioModule(javaFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成到當(dāng)前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(), 參數(shù)為源碼生成目錄(源碼的classpath目錄)
    }


}

運(yùn)行程序, 生成如下源文件:

使用javapoet生成Java源文件.png

詳細(xì)用法, 參考官網(wǎng)用例:
https://github.com/square/javapoet/tree/master/src/test/java/com/squareup/javapoet

三. 使用codemodel生成Java源文件

  • (1) 介紹
    官網(wǎng)如是說(shuō):

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 庫(kù),它提供了一種通過(guò) Java 程序來(lái)生成 Java 程序的方法写烤。
CodeModel 項(xiàng)目是 JAXB 的子項(xiàng)目翼闽。JAXB(Java Architecture for XML Binding)是一項(xiàng)可以根據(jù) XML Schema 產(chǎn)生 Java 類的技術(shù),它提供了將 XML 實(shí)例文檔反向生成 Java 對(duì)象樹(shù)的方法洲炊,并能將 Java 對(duì)象樹(shù)的內(nèi)容重新寫(xiě)到 XML 實(shí)例文檔感局。JAXB 是 JDK 的組成部分。JAXB RI(Reference Implementation)即 schema compiler 能夠?qū)?XML 的 schema 文件映射為相應(yīng)的 Java 元素暂衡。
------ From here: http://www.ibm.com/developerworks/cn/java/j-lo-codemodel/

我覺(jué)的:
它就是一個(gè)生成Java源代碼的庫(kù) ! (哈! (⌒^⌒)b) !
但是我還是想多說(shuō)幾句, codemodel和javapoet差不多, 都對(duì)java源文件進(jìn)行了建模, 都有相關(guān)的數(shù)據(jù)結(jié)構(gòu)來(lái)表述源文件中固定的概念, 這樣用戶使用起來(lái)會(huì)更加方便, 只是增加了復(fù)雜度和理解上的困難. 其實(shí)只要我們按coding的順序(先聲明包...再import依賴包...再聲明class...然后生命成員變量...再然后聲明方法......)來(lái)構(gòu)建也是挺好理解的.
下面貼幾張圖:

codemodel官網(wǎng).png

對(duì)此圖有兩點(diǎn)說(shuō)明, 右下角顯示:
a. codemodel版權(quán)屬于oracle公司
b. 此庫(kù)已經(jīng)很久沒(méi)有更新了

點(diǎn)擊左邊導(dǎo)航欄的Download按鈕跳到codemodel的maven倉(cāng)庫(kù), 這里可以下載codemodel的jar包和源碼. 這里再貼一圖:

codemodel下載.png

此圖說(shuō)明codemodel從2011年開(kāi)始就不再更新了 (自從sun被oracle收購(gòu)之后, oracle對(duì)很多java業(yè)務(wù)就不再關(guān)心. 因?yàn)槟承╇u肋的業(yè)務(wù)不賺錢啊...呵呵...), 有好心的開(kāi)發(fā)者fork了codemodel源碼并進(jìn)行了維護(hù)升級(jí). 如下:
https://github.com/UnquietCode/JCodeModel
https://github.com/phax/jcodemodel

喂喂, 樓主, 這是寫(xiě)偵探小說(shuō)么?! 不喜勿噴哈 ヾ _?

  • (2) 使用步驟
    • 在Android Studio中創(chuàng)建一個(gè)Java Library Module
    • 在上一步創(chuàng)建的Module的<module>/build.gradle構(gòu)建腳本中添加codemodel的依賴庫(kù), 如下:

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

  * 編寫(xiě)生成Java源文件的代碼

* (3) 使用案例

package com.example.javawriter;

// 注意不要引用了錯(cuò)誤的包, 有internal的包是jdk內(nèi)部使用的包
// import com.sun.codemodel.internal.ClassType;
// import com.sun.codemodel.internal.JDefinedClass
// import com.sun.codemodel.internal.JBlock
// ....

// 下面的包是獨(dú)立出來(lái)的codemodel庫(kù)的包, 這個(gè)包是沒(méi)有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源文件:
 * 在構(gòu)建腳本中添加codelmodel的依賴庫(kù) (倉(cāng)庫(kù)為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";

    /************ 生成一個(gè)Java源文件模型 ************/
    JCodeModel model = new JCodeModel();

    /************ 為模型添加一個(gè)頂級(jí)類型 (添加一個(gè)類) ************/
    JDefinedClass klass = model._class(JMod.PUBLIC, className, ClassType.CLASS);

    /************ 添加一個(gè)靜態(tài)成員變量 ************/
    int modifier = JMod.PRIVATE + JMod.STATIC + JMod.FINAL;
    JFieldVar jStaticFieldVar = klass.field(modifier, String.class, "TAG");
    //jStaticFieldVar.assign(JExpr.lit(klass.fullName()));  //error, 不能對(duì)未初始化成員變量進(jìn)行賦值, 要先進(jìn)行初始化;
    jStaticFieldVar.init(JExpr.lit(klass.fullName()));

    /************ 添加一個(gè)成員變量 ************/
    //原始類型變量(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); //給字段添加一個(gè)注解

    /************ 添加一個(gè)構(gòu)造方法 ************/
    JMethod constructor = klass.constructor(JMod.PRIVATE);
    constructor.param(String.class, "name");  //為構(gòu)造方法添加一個(gè)參數(shù)
    JBlock constructorBlock = constructor.body();
    constructorBlock.assign(JExpr.refthis("name"), constructor.params().get(0));        //初始化成員變量
    constructorBlock.directStatement("System.out.println(\"Constructor invoked !\");"); //直接定義語(yǔ)句


    /************ 添加一個(gè)成員方法 ************/
    JMethod jMethod = klass.method(JMod.PUBLIC, Void.TYPE, "setName");  //參數(shù)依次為: 修飾符, 返回類型, 方法名
    //jMethod.param(JType.parse(model, "String"), "name"); //java.lang.IllegalArgumentException: Not a primitive type: String
    jMethod.param(String.class, "name");
    JBlock methodBlock = jMethod.body();                                //構(gòu)建方法體
    methodBlock.assign(JExpr.refthis("name"), jMethod.params().get(0)); //在方法體中生成一句賦值語(yǔ)句 (為成員變量賦值)

    //在方法塊中定義兩個(gè)局部變量
    //JVar var = jBlock.decl(model.INT, "age");                     //先聲明變量, 再進(jìn)行初始化 (兩步完成)
    //var.init(JExpr.lit(23));
    JVar var = methodBlock.decl(model.INT, "age", JExpr.lit(100));  //聲明變量, 同時(shí)進(jìn)行初始化 (一步到位)
    JVar isAgeGreatThan_25 = methodBlock.decl(model.BOOLEAN, "isAgeGreatThan_25", var.gt(JExpr.lit(25)));


    //構(gòu)造一個(gè)if...else...語(yǔ)句塊
    JBlock if_else_block =  new JBlock();
    JConditional jConditional = if_else_block._if(isAgeGreatThan_25);
    jConditional._then().directStatement("System.out.println(\"Age great than 25\");"); //語(yǔ)句結(jié)束時(shí)不要忘了分號(hào)
    jConditional._else().directStatement("System.out.println(\"Age less than 25\");");

    //將if...else...語(yǔ)句塊添加到方法語(yǔ)句塊中
    methodBlock.add(if_else_block);


    /************ 構(gòu)建生成一個(gè)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 !";
}


運(yùn)行程序, 生成如下Java源文件:  

![codemodel生成的Java源文件.png](http://upload-images.jianshu.io/upload_images/1642441-497df81a186a5f3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


 

四. 三者的異同
1. 三個(gè)庫(kù)都是用來(lái)生成java源文件的
2. JavaWriter就是一個(gè)工具類, 使用起來(lái)簡(jiǎn)單容易理解, 但是要手動(dòng)拼接源文件中的語(yǔ)句, 因此容易出出現(xiàn)拼寫(xiě)錯(cuò)誤. 
3. javapoet和codemodel都對(duì)java源文件進(jìn)行了建模. 結(jié)構(gòu)化java源文件后, 用戶使用時(shí)必須使用library提供的數(shù)據(jù)結(jié)構(gòu), 代碼的拼接生成由庫(kù)處理, 因此不會(huì)產(chǎn)生拼寫(xiě)錯(cuò)誤, 使用起來(lái)也比較方便. 
4. 個(gè)人感覺(jué)javapoet比codemodel使用起來(lái)更加方便, 抽象出來(lái)的概念也更少, 更加容易理解. codemodel屬于重量級(jí)的庫(kù), 它幾乎對(duì)java源文件中的所有概念都進(jìn)行了抽象, 如: 類蓝厌、字段、方法古徒、變量、語(yǔ)句塊读恃、循環(huán)......等等 , 因此使用起來(lái)非常繁瑣, 理解起來(lái)也更加困難.
5. javapoet和codemodel生成java源文件的步驟是相反的: javapoet是先構(gòu)建字段隧膘、方法、構(gòu)造器寺惫、語(yǔ)句 ...... 等等, 然后添加到一個(gè)類(TypeSpec)中, 也就是說(shuō), javapoet是先構(gòu)建細(xì)節(jié), 然后再組織整體骨架, 是先分后總的邏輯疹吃; codemodel恰恰相反, codemodel構(gòu)建java源文件非常類似于構(gòu)建一顆DOM樹(shù), 先構(gòu)建根節(jié)點(diǎn)(JCodeModel), 然后再構(gòu)建其他分支節(jié)點(diǎn)(JDefinedClass、JFieldVar西雀、JMethod ......), 最后再構(gòu)建比分支節(jié)點(diǎn)更細(xì)節(jié)的支節(jié)點(diǎn) (JBlock萨驶、JExpr、JConditional ......)艇肴。

五. 使用場(chǎng)景
這三個(gè)庫(kù)主要就是用來(lái)生成Java源文件. 那么什么時(shí)候需要生成java源文件呢? 使用注解或解析注解的地方需要(生成那些需要我們重復(fù)勞動(dòng)的代碼 --- 減少我們的負(fù)擔(dān)!). 那么什么地方會(huì)使用注解和解析呢? 使用注解的地方非常多, 如大部分的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) ... 等)、編譯時(shí)檢查框架(support-annotations丑孩、[jcip](https://github.com/jcip/jcip.github.com) ...等)以及很多其他優(yōu)秀框架([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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市温学,隨后出現(xiàn)的幾起案子略贮,更是在濱河造成了極大的恐慌,老刑警劉巖枫浙,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刨肃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡箩帚,警方通過(guò)查閱死者的電腦和手機(jī)真友,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)紧帕,“玉大人盔然,你說(shuō)我怎么就攤上這事∈鞘龋” “怎么了愈案?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鹅搪。 經(jīng)常有香客問(wèn)我站绪,道長(zhǎng),這世上最難降的妖魔是什么丽柿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任恢准,我火速辦了婚禮,結(jié)果婚禮上甫题,老公的妹妹穿的比我還像新娘馁筐。我一直安慰自己,他們只是感情好坠非,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布敏沉。 她就那樣靜靜地躺著,像睡著了一般炎码。 火紅的嫁衣襯著肌膚如雪盟迟。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天潦闲,我揣著相機(jī)與錄音队萤,去河邊找鬼。 笑死矫钓,一個(gè)胖子當(dāng)著我的面吹牛要尔,可吹牛的內(nèi)容都是我干的舍杜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赵辕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼既绩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起还惠,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饲握,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蚕键,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體救欧,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年锣光,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笆怠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡誊爹,死狀恐怖蹬刷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情频丘,我是刑警寧澤办成,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站搂漠,受9級(jí)特大地震影響迂卢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桐汤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一而克、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惊科,春花似錦、人聲如沸亮钦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜂莉。三九已至蜡娶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間映穗,已是汗流浹背窖张。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚁滋,地道東北人宿接。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓赘淮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親睦霎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梢卸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)副女,斷路器蛤高,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評(píng)論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法碑幅,內(nèi)部類的語(yǔ)法戴陡,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法沟涨,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,581評(píng)論 18 399
  • 關(guān)鍵字:TextInputLayout恤批、TextInputEditText、材料設(shè)計(jì)項(xiàng)目地址:AboutMater...
    Arnold_J閱讀 545評(píng)論 0 2
  • 看到了第十一集拷窜,只能說(shuō)感同身受开皿。回憶如潮水涌來(lái):高考結(jié)束時(shí)告別的目光篮昧、吃醋時(shí)趴桌上生無(wú)可戀的表情赋荆、體檢前溫柔的別怕...
    米花小子閱讀 241評(píng)論 0 0