Android開(kāi)發(fā)—APT注解處理器詳解

關(guān)于APT

APT(Annotation Processing Tool)是一種注解處理工具脖阵,它會(huì)對(duì)源文件進(jìn)行掃描找出相應(yīng)的Annotation并在注解處理器中進(jìn)行操作黄锤,具體操作由注解處理器也就是用戶自己去實(shí)現(xiàn),比如可以生成一些新的文件或者其他文件等饥侵,最終會(huì)把新生成的文件和源文件一起進(jìn)行編譯。

APT工具常用的有2個(gè),android-apt和Gradle2.2以后的annotationProcessor功能萄凤。

APT處理annotation的基本流程表示:

  • 定義注解,比如@Route
  • 自定義注解處理器搪哪,處理注解(如生成java文件等)
  • 使用注解處理器
APT操作@Route注解的大致步驟

android-apt

官方文檔

一個(gè)Gradle插件幫助Android Studio處理annotation processors靡努,Gradle2.2以后Gradle提供annotationProcessor的功能可以完全代替android-apt,android-apt官網(wǎng)上作者也說(shuō)明了晓折,不再維護(hù)惑朦,并且谷歌明確表示Gradle 3.0.0+ 不再支持 android-apt 插件,所以推薦使用annotationProcessor漓概。

android-apt主要有2個(gè)目的:
1漾月、允許在注解處理器編譯的時(shí)候當(dāng)做依賴,但是在打包apk或者當(dāng)做類庫(kù)的時(shí)候不會(huì)打到里面垛耳;
2栅屏、設(shè)置生成的資源路徑以便能被Android studio正確訪問(wèn)到;

使用插件的時(shí)候如下配置gradle腳本

buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:1.3.0'
        // the latest version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

傳遞編譯的參數(shù)

apt {
    arguments {
            resourcePackageName android.defaultConfig.applicationId
            androidManifestFile variant.outputs[0]?.processResources?.manifestFile
    }
}

由于android-apt已經(jīng)過(guò)時(shí)了堂鲜,并且annotationProcessor也正式被Google扶正栈雳,所以具體apt的使用不在進(jìn)行演示,有興趣的同學(xué)可以訪問(wèn)android-apt主頁(yè)進(jìn)行學(xué)習(xí)缔莲。

annotationProcessor

官方文檔
annotationProcessor是Gradle2.2+內(nèi)置的功能哥纫,不需要額外引入其他插件,可以向下面這樣直接在gradle文件引入痴奏。

dependencies {
    // Adds libraries defining annotations to only the compile classpath.
    compileOnly 'com.google.dagger:dagger:version-number'
    // Adds the annotation processor dependency to the annotation processor classpath.
    annotationProcessor 'com.google.dagger:dagger-compiler:version-number'
}

這是引用第三方的注解處理器蛀骇,我們實(shí)際開(kāi)發(fā)中可以自定義注解處理器,下面我們自定義一個(gè)簡(jiǎn)單的注解處理器读拆。

自定義注解處理器

自定義注解處理器的話需要用到2個(gè)第三方庫(kù)AutoServiceJavaPoet 擅憔,還有Java自帶的AbstractProcessor。

  • AbstractProcessor:Java內(nèi)置注解處理器檐晕,注解處理器核心工作都在這個(gè)類進(jìn)行暑诸。
  • AutoService:Google開(kāi)源用來(lái)自動(dòng)注冊(cè)我們自己的注解處理器。
  • JavaPoet:Java代碼生成器辟灰,方便我們生成Java文件个榕;

我們按照上文說(shuō)的APT處理annotation的基本流程來(lái)自定義。

1芥喇、定義注解西采,比如@Route
新建項(xiàng)目,然后新建一個(gè)Java module继控,叫annotationLib械馆,里面定義我們自己的注解胖眷,關(guān)于注解的相關(guān)知識(shí)這里不再細(xì)說(shuō)具體可以參考這里

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface Route {
    String value() ;
}

2、自定義注解處理器霹崎,處理注解(如生成java文件等)
再新建一個(gè)Java module瘦材,叫annotationCompiler,里面實(shí)現(xiàn)具體的注解處理器仿畸,配置gradle文件引入 AutoServiceJavaPoet食棕,再依賴上我們之前定義的注解模塊。

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.squareup:javapoet:1.8.0'
    implementation project(':annotationLib')
}
sourceCompatibility = "7"
targetCompatibility = "7"

新建一個(gè)注解處理器繼承自AbstractProcessor:

@AutoService(Processor.class)
public class RouteProcessor extends AbstractProcessor {
    private Messager messager;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

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

    public void loggerInfo(String msg) {
        messager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && !set.isEmpty()) {
            loggerInfo("process start");
            StringBuilder printInfo = new StringBuilder();
            Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
            try {
                if (routeElements != null && routeElements.size() > 0) {
                    printInfo.append(routeElements.size() + "個(gè)文件加了@Route注解错沽!");
                }
            } catch (Exception e) {
                loggerInfo(e.getMessage());
            }
            //構(gòu)建參數(shù)
            ParameterSpec msg = ParameterSpec.builder(String.class, "msg")
                    .build();
            //構(gòu)建方法
            MethodSpec method = MethodSpec.methodBuilder("inject")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(msg)
                    .addStatement("$T.out.println($S+msg)", System.class, printInfo.toString())
                    .build();
            //構(gòu)建類
            TypeSpec helloWorld = TypeSpec.classBuilder("InjectHelper")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(method)
                    .build();
             //構(gòu)建文件并指定生成文件目錄
            JavaFile javaFile = JavaFile.builder("com.wzh.annotation", helloWorld)
                    .build();
            loggerInfo("process end");
            try {
                //把類簿晓、方法、參數(shù)等信息寫(xiě)入文件
                javaFile.writeTo(filer);
            } catch (IOException e) {
                loggerInfo("process exception");
                e.printStackTrace();
            }
            return true;
        }
        return false;
    }
}

然后我們?cè)谖覀僡pp模塊引用這個(gè)注解編譯器及注解千埃,如下:

dependencies {
     ...
    annotationProcessor project(':annotationCompiler')
    implementation project(':annotationLib')
    ...
}

我們clean一下項(xiàng)目憔儿,然后rebuild一下,會(huì)發(fā)現(xiàn)如下目錄生成的文件:

app/build/generated/source/apt/debug/com/wzh/annotation/InjectHelper.java

import java.lang.String;
import java.lang.System;

public final class InjectHelper {
  public static void inject(String msg) {
    System.out.println("2個(gè)文件加了@Route注解放可!"+msg);
  }
}

這就是我們生成的文件谒臼,很簡(jiǎn)單一個(gè)InjectHelper類,里面一個(gè)靜態(tài)方法inject耀里,打印出加@Route的文件個(gè)數(shù)蜈缤。

3、使用注解處理器

@Route("main")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectHelper.inject("調(diào)用生成類的方法");
    }
}
@Route("test")
public class TestClass {
     //empty class
}

這里我們演示很簡(jiǎn)單冯挎,在Activity上加了@Route的注解底哥,并調(diào)用生成類的方法,然后在任意類都可以加注解房官,因?yàn)槲覀儧](méi)做任何注解類的限制趾徽,運(yùn)行程序輸出:

System.out: 2個(gè)文件加了@Route注解!調(diào)用生成類的方法

Kotlin使用注解處理器

首先app模塊引入注解處理器的時(shí)候需要引入kapt插件翰守,在app下的gradle配置如下:

apply plugin: 'kotlin-kapt'
....
dependencies {
     ...
     //自定義注解處理器 module
    kapt project(':annotationCompiler')
    //自定義注解 module
    implementation project(':annotationLib')
    ...
}

其他配置基本一樣孵奶,文件生成的目錄變化,apt目錄變?yōu)?code>kapt:

app/build/generated/source/kapt/debug/com/wzh/annotation/InjectHelper.java

給注解處理器傳參數(shù)

在編譯之前可以傳遞需要的參數(shù)給注解處理器蜡峰,我們?cè)赼pp模塊gradle傳遞module的名字給注解處理器:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                 //參數(shù)名 route_module_name了袁,攜帶的數(shù)據(jù)就是當(dāng)前module的名字
                arguments = [route_module_name: project.getName()]
            }
        }
    }
}

在注解處理器init方法里接受參數(shù):

private String moduleName = null;

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
    ...
    moduleName = processingEnvironment.getOptions().get("route_module_name");
    loggerInfo("moduleName = " + moduleName);
}

Rebuild項(xiàng)目的時(shí)候我們會(huì)在build控制臺(tái)看到如下輸出信息:

moduleName = app

APT的相關(guān)知識(shí)學(xué)習(xí)

自定義AbstractProcessor的時(shí)候我們會(huì)重寫(xiě)以下的方法:

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    // 初始化操作
}
@Override
public Set<String> getSupportedAnnotationTypes() {
    // 設(shè)置注解處理器需要處理的注解類型
}
@Override
public SourceVersion getSupportedSourceVersion() {
     //指定java版本
     return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    //注解處理的核心方法
}

下面重點(diǎn)介紹initprocess方法

  • init(ProcessingEnvironment processingEnvironment) 方法
    此方法會(huì)被注解處理工具調(diào)用,參數(shù)ProcessingEnvironment 提供了一些實(shí)用的工具類Elements事示、Types和Filer等早像,如下表所示僻肖。
工具方法 功能
getElementUtils() 返回實(shí)現(xiàn)Elements接口的對(duì)象肖爵,用于操作元素的工具類
getFiler() 返回實(shí)現(xiàn)Filer接口的對(duì)象,用于創(chuàng)建文件臀脏、類和輔助文件
getMessager() 返回實(shí)現(xiàn)Messager接口的對(duì)象劝堪,用于報(bào)告錯(cuò)誤信息冀自、警告提醒
getOptions() 返回指定的參數(shù)選項(xiàng),可在Gradle文件配置
getTypeUtils() 返回實(shí)現(xiàn)Types接口的對(duì)象秒啦,用于操作類型的工具類
  • process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)方法
    此方法里面是我們進(jìn)行注解處理邏輯的地方熬粗。
    參數(shù)1 Set<? extends TypeElement> set:返回所有當(dāng)前注解處理器需要處理的Annotation.
    參數(shù)2 RoundEnvironment roundEnvironment:表示當(dāng)前或是之前的運(yùn)行環(huán)境,可以通過(guò)該對(duì)象查找到注解余境。

從roundEnvironment我們可以獲取到Element被注解的元素信息驻呐。下面我們寫(xiě)個(gè)實(shí)例來(lái)打印一下看看。

package com.wzh.annotation;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE,ElementType.TYPE_PARAMETER})
public @interface Test {
    String value();
}

這里定義的注解為了方便打印芳来,支持注解到類含末、方法、變量即舌、參數(shù)等佣盒。下面使用注解。

package com.wzh.annotation;
@Test("this is class TestClass")
public class TestClass<T> implements TestInterface{
    @Test("this is local field name")
    private String name = "my name is test";

    @Test("this is local method sayHello")
    private String sayHello(@Test("this is parameter msg") String msg){
        String hello = "my name is hello";
        return hello;
    }
}

然后在注解處理器去打印元素信息顽聂。

private void parseTestAnnotation(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Test.class);
        for (Element element : elements){ //遍歷所有元素
            if(element.getKind().equals(ElementKind.PACKAGE)){
                LoggerInfo("element--------------PACKAGE-------------------------------");
            } else if (element.getKind().equals(ElementKind.CLASS)){
                //被注解的元素是類
                TypeElement typeElement = (TypeElement) element;
                LoggerInfo("element--------------CLASS-------------------------------");
                //實(shí)現(xiàn)接口信息
                LoggerInfo("element:Interfaces = "+typeElement.getInterfaces().toString());
                //泛型參數(shù)
                LoggerInfo("element:TypeParameters = "+typeElement.getTypeParameters().toString());

                //element的父元素是包元素
                PackageElement packageElement = (PackageElement) element.getEnclosingElement();
                LoggerInfo("element:packageElement = "+packageElement.getQualifiedName());

            } else if (element.getKind().equals(ElementKind.FIELD)){
                //被注解的元素是全局變量
                LoggerInfo("element--------------FIELD-------------------------------");
                VariableElement variableElement = (VariableElement) element;
                //獲取變量類型
                LoggerInfo("element:typeSimpleName = "+ types.asElement(variableElement.asType()).getSimpleName());
            } else if (element.getKind().equals(ElementKind.PARAMETER)){
                //被注解的元素是參數(shù)
                LoggerInfo("element--------------PARAMETER-------------------------------");
            } else if (element.getKind().equals(ElementKind.METHOD)){
                //被注解的元素是方法
                LoggerInfo("element--------------METHOD-------------------------------");
                ExecutableElement executableElement = (ExecutableElement) element;
                //獲取方法的參數(shù)名
                LoggerInfo("element:Parameters = "+executableElement.getTypeParameters().toString());
                //獲取方法的返回值類型
                LoggerInfo("element:ReturnType = "+executableElement.getReturnType().toString());
            }
            //打印注解里面的值
            LoggerInfo("element:value = "+ element.getAnnotation(Test.class).value());
            //打印包名信息
            LoggerInfo("element:packageName = "+ elementUtils.getPackageOf(element).getQualifiedName());
            //被注解元素的名稱
            LoggerInfo("element:SimpleName = "+element.getSimpleName());
            //被注解元素的類型(String/int/float...)
            LoggerInfo("element:asType = "+element.asType().toString());
            //被注解元素的種類(PACKAGE肥惭、CLASS、METHOD紊搪、PARAMETER等)
            LoggerInfo("element:KindName = "+element.getKind().name());
            //獲取父元素的種類(局部變量的父元素是方法蜜葱、方法及全局變量的父元素是類、類元素的父元素是包)
            LoggerInfo("element:EnclosingElementKindName = "+element.getEnclosingElement().getKind().name());
            //被注解元素的修飾 如:public static 等
            LoggerInfo("element:Modifiers = "+element.getModifiers().toString());
        }
    }

注: >> element--------------CLASS-------------------------------
注: >> element:Interfaces = com.wzh.annotation.TestInterface
注: >> element:TypeParameters = T
注: >> element:packageElement = com.wzh.annotation
注: >> element:value = this is class TestClass
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = TestClass
注: >> element:asType = com.wzh.annotation.TestClass<T>
注: >> element:KindName = CLASS
注: >> element:EnclosingElementKindName = PACKAGE
注: >> element:Modifiers = [public]
注: >> element--------------FIELD-------------------------------
注: >> element:typeSimpleName = String
注: >> element:value = this is local field name
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = name
注: >> element:asType = java.lang.String
注: >> element:KindName = FIELD
注: >> element:EnclosingElementKindName = CLASS
注: >> element:Modifiers = [private]
注: >> element--------------METHOD-------------------------------
注: >> element:Parameters =
注: >> element:ReturnType = java.lang.String
注: >> element:value = this is local method sayHello
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = sayHello
注: >> element:asType = (java.lang.String)java.lang.String
注: >> element:KindName = METHOD
注: >> element:EnclosingElementKindName = CLASS
注: >> element:Modifiers = [private]
注: >> element--------------PARAMETER-------------------------------
注: >> element:value = this is parameter msg
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = msg
注: >> element:asType = java.lang.String
注: >> element:KindName = PARAMETER
注: >> element:EnclosingElementKindName = METHOD
注: >> element:Modifiers = []

由打印結(jié)果可以看到所有被注解的元素信息都被打印出來(lái)耀石。

Element

Java文檔關(guān)于Element介紹
Element代表一個(gè)程序元素笼沥,如包、類娶牌、方法奔浅、變量、參數(shù)诗良、接口泛型等的接口汹桦,其有多種子類分別代表不同的程序元素,如:ExecutableElement 方法元素, PackageElement 包元素, TypeElement 類元素, TypeParameterElement 形參元素, VariableElement 變量及參數(shù)元素等鉴裹。之前的TestClass<T>可以對(duì)應(yīng)成下圖舞骆。

Element對(duì)應(yīng)圖

TypeMirror

TypeMirror是一個(gè)接口,表示Java編程語(yǔ)言中的類型径荔。這些類型包括基本類型督禽、引用類型、數(shù)組類型总处、類型變量和null類型等等狈惫。Element的 asType()返回TypeMirror類型的值,我們通過(guò)這個(gè)值得getKind()方法獲取元素的類型鹦马,這個(gè)類型有很多枚舉類型如:CHAR胧谈、ARRAY(數(shù)組)忆肾、FLOATEXECUTABLE(方法)等菱肖。

總結(jié)

關(guān)于注解處理器具體使用還有很多東西客冈,就不一一寫(xiě)出來(lái)了,具體可以參考JAVA API稳强,不得不說(shuō)注解處理器很強(qiáng)大场仲,很多熱門框架都使用了APT,如:butterknife退疫、Arouter燎窘、Dagger2、EventBus等蹄咖。所以學(xué)好注解處理器還是比較重要的褐健,接下來(lái)我們實(shí)戰(zhàn)一把,不看butterknife源碼的情況下實(shí)現(xiàn)簡(jiǎn)單的功能澜汤。Android開(kāi)發(fā)— APT之ButterKnife的簡(jiǎn)單功能實(shí)現(xiàn)

參考:
Java API
Java注解處理器
java-apt的實(shí)現(xiàn)之Element詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚜迅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俊抵,更是在濱河造成了極大的恐慌谁不,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徽诲,死亡現(xiàn)場(chǎng)離奇詭異刹帕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谎替,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門偷溺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人钱贯,你說(shuō)我怎么就攤上這事挫掏。” “怎么了秩命?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵尉共,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我弃锐,道長(zhǎng)袄友,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任霹菊,我火速辦了婚禮剧蚣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己券敌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布柳洋。 她就那樣靜靜地躺著待诅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熊镣。 梳的紋絲不亂的頭發(fā)上卑雁,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天,我揣著相機(jī)與錄音绪囱,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛电禀,可吹牛的內(nèi)容都是我干的鸽凶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼齿椅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琉挖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起涣脚,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤示辈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后遣蚀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體矾麻,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年芭梯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了险耀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玖喘,死狀恐怖胰耗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芒涡,我是刑警寧澤柴灯,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站费尽,受9級(jí)特大地震影響赠群,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旱幼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一查描、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦冬三、人聲如沸匀油。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敌蚜。三九已至,卻和暖如春窝爪,著一層夾襖步出監(jiān)牢的瞬間弛车,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工蒲每, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纷跛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓邀杏,卻偏偏與公主長(zhǎng)得像贫奠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子望蜡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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