依赖注入框架 - Dagger2

Dagger2是个什么东西呢?依赖注入,这是个啥玩意?
嗯,在学这个东西的时候我们得了解一些知识点:

知识点呀

  • 依赖注入(Dependency Injection)
    通俗点来说,就是目标类中需要用到其他的类,并把其他的类初始化的过程
    举个例子:
public class ClassA {
    ...
    ClassB b;
    ...
    public ClassA() {
        b = new ClassB();
    }
    
    public void do() {
        ...
        b.doSomething();
        ...
    }
}

这个类A就是目标类,而这个类B就是目标类A所依赖的其他类,A中需要用到B的对象或者方法,那就会在A类里面初始化B这么个过程。依赖注入讲的就是这么个事。Dagger2呢?Dagger2就是把A中通过手动编码的方式创建的对象B,替换为了通过技术手段把类B已经初始化好的对象自动注入到类A中,哎,这就叫Dagger2!舒服~

  • 注解
    注解是Dagger2中用到的核心技术,重要性不言而喻
    关于java注解的详细介绍,包括概念,作用,分类,解析及几个常用的安卓库的原理解析等等等等,抽出来可以讲解一天。今天的主角是Dagger2,关于注解可以去下面这个这个链接里学习一下,特别全面!
    Java 注解原理简析

知识点不多,就两个,下面我们再来开始看Dagger2


什么是Dagger2

日常开发中,我们经常会在自己的Activity、Fragment、Dialog...用到各种各样的依赖关系。比如说我想请求一个接口数据,我会我的类中使用Retrofit、Gson...,我想本地存储一个简单的东西,我会使用SharedPreference。无一例外,我们都需要在使用它们之前对它们进行创建对象,并且各种各样的对象之间可能还存在着各种各样的依赖关系。
而Dagger2 就是一个依赖注入框架,自动生成创建依赖关系(就是new对象)的代码。减少很多模板化的代码,更易于测试,降低耦合,创建可复用可互换的模块。


Dagger2的好处

  • 全局对象实例的简单访问方式
    和ButterKnife 定义了view、事件处理以及资源的引用一样,Dagger2 提供全局对象引用的简易访问方式。声明了单例的实例都可以使用@inject进行访问。比如下面的MyTwitterApiClient 和SharedPreferences 的实例:
public class MainActivity extends Activity {
   @Inject 
   MyTwitterApiClient mTwitterApiClient;
   @Inject 
   SharedPreferences sharedPreferences;

   public void onCreate(Bundle savedInstance) {
       InjectorClass.inject(this);
   }
  • 复杂的依赖关系只需要进行简单的代码配置
    Dagger2 会通过依赖关系并且生成易懂易分析的代码。以前通过手写的大量模板代码中的对象引用将会由它给你创建并传递到相应对象中。因此你可以更多的关注模块中构建的内容而不是模块中的对象实例的创建。简而言之,就是之前各种new对象的活让Dagger2给你干完了,你可以把重心放到其他更重要的地方。
  • 让单元测试和集成测试更加方便
    因为依赖关系已经被我们独立出来,所以我们可以轻松的抽取出不同的模块进行测试。依赖的注入和配置独立于组件之外。因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码。依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。简而言之,就是高内聚、低耦合,一个地方的改动不再会影响其他地方的代码。这样模块的独立也使得测试模块功能也变的简单。

如何引入Dagger2

  • 配置apt插件(在build.gradle中添加如下代码)
dependencies {
     // other classpath definitions here
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
 }
  • 添加依赖(在build.gradle(Module:app)中添加如下代码)
apply plugin: 'com.android.application'
//添加如下代码,应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
    ...
    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'
    //java注解
    compile 'org.glassfish:javax.annotation:10.0-b28'
    ...
}

Dagger2使用

Dargger2基本也是最重要的用法就要弄清四个概念:
Inject,Component,Module,Provides

  • Inject是什么?
    先看一段代码
 class A{
       B b = new B(...);
       C c = new C(...);
       D d = new D(new E());
       F f = new F(.....);
 }

上面的代码没啥问题,但是总感觉创建对象都是重复的一些操作,那何尝不想个办法,把这些重复的体力劳动用一种自动化的、更省力的方法解决掉呢?这样就可以让开发的效率提高,可以把精力集中在重要的业务上了。我们可以用注解来标注目标类中所依赖的其他类,在Dagger2中该注解的名字就叫Inject,这样就可以变为如下:

   class A{
        @Inject
        B b;
   }

   class B{
       @Inject
       B(){
       }
   }

可以看到,Dagger2的Inject标注了两个东西。一个是标注目标类A中所依赖的类B,另一个是标注所依赖的类B的构造函数。这样我们就可以让目标类A中所依赖的类B与类B的构造函数之间有了一种无形的联系,但是要想使它们之间产生直接的关系,还得需要一个桥梁来把它们之间连接起来。那这个桥梁就是Component了。

  • Component又是什么鬼?
    Component也是一个注解类,一个类要想是Component,那它必须是接口或抽象类,并且必须用Component注解来标注该类。比如:
@Component(modules = MainModule.class)
public interface MainComponent {
    void inject(MainActivity activity);
}

我们不讨论具体类的代码,先从抽象概念的角度来讨论Component。上文中提到Component在目标类A所依赖的类B与类B的构造函数之间可以起到一个桥梁的作用。那我们看看这桥梁是怎么工作的:
Component需要引用到类A的实例,Component会查找类A中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字:注入器(Injector)

小结一下:目标类想要初始化自己依赖的其他类:

  • 用Inject注解标注目标类中其他类
  • 用Inject注解标注其他类的构造函数
  • 若其他类还依赖于其他的类,则重复进行上面2个步骤
  • 调用Component(注入器)的injectXXX(Object)方法开始注入(injectXXX方法名字是官方推荐的名字,以inject开始)
    Component现在是一个注入器,就像注射器一样,Component会把目标类依赖的实例注入到目标类中,来初始化目标类中的依赖。

OK,Inject和Component大致了解了,下面说下Module:

  • Module
    现在有个新问题:如果你的项目中使用到了第三方jar包中的类库,第三方类库又不能让你进行修改它里面的代码,所以根本不可能把Inject注解加入到这些类中,这时我们的Inject就失效了。解决办法呢?我们可以用自己的代码封装这些用到的第三方的类库,这样我们就可以在我们自己的代码中随意使用Inject注解了。但随之而来的问题就是:封装的代码怎么管理呢,总不能让这些封装的代码散落在项目中的各个地方,总得有个好的管理机制吧。哎,这Module就可以担当此任。我们可以把封装第三方类库的代码放入Module中,像下面的例子:
@Module
public class ModuleClass{
    //A是第三方类库中的一个类
    A provideA(){
       return A();
    }
}

Module其实是一个简单工厂模式,Module里面的方法基本都是创建类实例的方法。接下来问题来了,因为Component是注入器(Injector),我们怎么能让Component与Module有联系呢?

Component,从上面我们知道,他是一个注入器,一端连接目标类(我们叫它目标端),另一端连接目标类所依赖的实例(我们叫它实例端)。而Module的作用就是提供创建各种类的实例,所以Module是属于Component所连接的实例端。所以,现在Component有了个新职责:管理好Module!(Component中的modules属性可以把Module加入Component,modules可以加入多个Module)

只剩下最后一个:Provides

  • Provides
    Provides使用在Module里面
    Module中各种创建类的实例与目标类中的用Inject注解标注的依赖对象产生关联,这就是Provides的活:
    Module中的创建类实例方法用Provides进行标注,Component在搜索到目标类中用Inject注解标注的属性后,Component就会去Module中去查找用Provides标注的对应的创建类实例方法,这样就可以解决第三方类库用dagger2实现依赖注入了。
@Module
public class ModuleClass{
    private final A mA;

    public ModuleClass(A a) {
        mA = a;
    }

    @Provides
    A provideA() {
        return mA;
    }
}

下面从一段代码(MVP模式)中分析一下Dagger2的原理:

public class MainActivity extends AppCompatActivity implements MainContract.View {
    @Inject
    MainPresenter mainPresenter;
    ...
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
         DaggerMainComponent.builder()
                .mainModule(new MainModule(this))
                .build()
                .inject(this);
        //调用Presenter方法加载数据
         mainPresenter.loadData();
         
         ...
    }

}

public class MainPresenter {
    //MainContract是个接口,View是他的内部接口,这里看做View接口即可
    private MainContract.View mView;
    
    @Inject
    MainPresenter(MainContract.View view) {
        mView = view;
    }    
    public void loadData() {
        //调用model层方法,加载数据
        ...
        //回调方法成功时
        mView.updateUI();
    }
}

@Module
public class MainModule {
    private final MainContract.View mView;

    public MainModule(MainContract.View view) {
        mView = view;
    }

    @Provides
    MainView provideMainView() {
        return mView;
    }
}

@Component(modules = MainModule.class)
public interface MainComponent {
    void inject(MainActivity activity);
}

Dagger2与其他依赖注入框架不同,它是通过apt插件在编译阶段生成相应的注入代码,下面我们就具体看看Dagger2生成了哪些注入代码

MainPresenter类,在这个类中我们对构造方法用了@Inject标注,然后我们对项目进行下Rebuild Project,如图:


Rebuild Project.png

Dagger2会在/app/build/generated/apt/debug/目录下生成一个对应的工厂类MainPresenter_Factory(目录路径如图)


Rebuild Project.png

我们看下MainPresenter_Factory的具体代码:

public class MainPresenter {
    //MainContract是个接口,View是他的内部接口,这里看做View接口即可
    private MainContract.View mView;
    
    @Inject
    MainPresenter(MainContract.View view) {
        mView = view;
    }    
    public void loadData() {
        //调用model层方法,加载数据
        ...
        //回调方法成功时
        mView.updateUI();
    }
}

public final class MainPresenter_Factory implements Factory<MainPresenter> {
  private final Provider<MainContract.View> viewProvider;

  public MainPresenter_Factory(Provider<MainContract.View> viewProvider) {
    assert viewProvider != null;
    this.viewProvider = viewProvider;
  }

  @Override
  public MainPresenter get() {
    return new MainPresenter(viewProvider.get());
  }

  public static Factory<MainPresenter> create(Provider<MainContract.View> viewProvider) {
    return new MainPresenter_Factory(viewProvider);
  }
}

对比MainPresenter,我们发现在MainPre_Factory里也生成了对应的代码。首先是viewProvide,这是一个Provider类型,泛型参数就是MainPresenter类里面需要的MainContract.View,接着通过构造方法,对viewProvider进行实例化。其实这里有个不大好理解的地方,viewProvider为什么不直接是MainContract.View类型,而是转了个弯成为Provider类型?看到Provider我们应该想到上文中的Provides注解,Provides的作用是:Module中各种创建类的实例与目标类中的用Inject注解标注的依赖对象产生关联,那么这就好理解了。这个MainContract.View是一个依赖,而依赖的提供者是Module,因此 这个viewProvider一定是由MainModul提供的。我们接着看下面的get()方法,看到这个方法,知道了get()是实例化MainPresenter的。
好了,总计一下这个MainPresenter_Factory类,首先有一个create()方法用来创建这个MainPresenter_Factory这个类,这个方法有一个参数是viewProvider。这个参数viewProvider的初始化是在MainPresenter_Factory的构造方法中,MainPresenter_Factory的构造方法的参数就是我们的依赖MainPresenter类中的MainContract.View。而通过get()的代码我们得知,这个MainContract.View是由viewProvider通过get()提供。
接下来我们验证上面中我用黑体字标粗的内容:
这个viewProvider一定是由MainModul提供的。我们接着看MainModule所对应的注入类

@Module
public class MainModule {
    private final MainContract.View mView;

    public MainModule(MainContract.View view) {
        mView = view;
    }

    @Provides
    MainContract.View provideMainView() {
        return mView;
   }   
}


public final class MainModule_ProvideMainViewFactory implements Factory<MainContract.View> {
  private final MainModule module;

  public MainModule_ProvideMainViewFactory(MainModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public MainContract.View get() {
    return Preconditions.checkNotNull(
        module.provideMainView(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<MainContract.View> create(MainModule module) {
    return new MainModule_ProvideMainViewFactory(module);
  }
}

上面的代码,我们会发现在MainModule中用@Provides修饰的方法会对应的生成一个工厂类,那就是MainModule_ProvideMainViewFactory。我们看到这个类里有一个get()方法,其中调用了MainModule里的provideMainView()方法来返回我们所需要的依赖MainContract.View。好!到这里,还记得在MainPresenter_Factory里的get()方法中,get()在实例化MainPresenter对象的时候,它的参数viewProvider.get()吗?到这里我们就可以大致理解到,原来get()方法参数中的viewProvider就是生成的MainModule_ProvideMainViewFactory,然后调用了其get()方法,将我们需要的MainContract.View注入到MainPresenter里。

好,总结一下MainPresenter的实例化过程。
MainPresenter RebuildProject后会对应的产生一个工厂类,在这个类中有一个get()方法来进行MainPresenter创建,而get()创建MainPresenter所需要的View参数,就是由MainModule里定义的以provide开头的方法对应的工厂类提供的。到此,我们也证明了上文黑体加粗的文字。

虽然大体明白了实例化的创建过程,但是还存在几点疑惑:
1.MainPresenter_Factory的创建是由create()完成的,那么crate是在哪调用的?
2.创建的MainPresenter实例是怎么跟@Inject注解的MainPresenter关联起来的?

咱们只剩下Component概念了!前面说过Component是一个注入器和桥梁。它连接着@Module和@Inject,所以上面的疑惑就要到编译后Component所对应的类中寻找答案。

@Component(modules = MainModule.class)
public interface MainComponent {
    void inject(MainActivity activity);
}

public final class DaggerMainComponent implements MainComponent {
  private Provider<MainContract.View> provideMainViewProvider;

  private Provider<MainPresenter> mainPresenterProvider;

  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerMainComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.provideMainViewProvider = MainModule_ProvideMainViewFactory.create(builder.mainModule);

    this.mainPresenterProvider = MainPresenter_Factory.create(provideMainViewProvider);

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(mainPresenterProvider);
  }

  @Override
  public void inject(MainActivity activity) {
    mainActivityMembersInjector.injectMembers(activity);
  }

  public static final class Builder {
    private MainModule mainModule;

    private Builder() {}

    public MainComponent build() {
      if (mainModule == null) {
        throw new IllegalStateException(MainModule.class.getCanonicalName() + " must be set");
      }
      return new DaggerMainComponent(this);
    }

    public Builder mainModule(MainModule mainModule) {
      this.mainModule = Preconditions.checkNotNull(mainModule);
      return this;
    }
  }
}

从上面代码看到定义的MainComponent会生成一个对应的DaggerMainComponent,并且实现了MainComponent里的方法。我们看到代码中又出现了Provider类型的成员属性,前面说过这个Provider就是提供所需依赖的实例化。我们再来看Provider类型的成员属性是在哪实例化的。看没看到有个initialize()方法,咱把它单独择出来

@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
    this.provideMainViewProvider = MainModule_ProvideMainViewFactory.create(builder.mainModule);
    
    this.mainPresenterProvider = MainPresenter_Factory.create(provideMainViewProvider);
    
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(mainPresenterProvider);
  }

ok,首先创建了MainModule_ProvideMainViewFactory实例,把它赋值给provideMainViewProvider(泛型:Provider<MainContract.View>),用来提供MainContract.View实例。这里可能有个小疑惑,create()方法返回的是Factory类型,而provideMainViewProvider是个Provider类型,其实看源码就明白了,Factory继承自Provider,如下:

public interface Factory<T> extends Provider<T> {
}

然后将provideMainViewProvider当参数(就是前面讲到的viewProvider)传递到MainPresenter_Factory的create()中,返回了mainPresenterProvider(泛型为:Provider<MainPresenter>)。紧接着将这个mainPresenterProvider又传递到MainActivity_MembersInjector的create()中,返回了mainActivityMembersInjector(泛型为:MembersInjector<MainActivity>),我们可以看到mainActivityMembersInjector的泛型是MainActivity,因此可以猜想一下:MainActivity_MembersInjector类是MainActivity对应的注入类(后面再分析这个类)。

接着是我们在MainComponent里定义的Inject方法的实现,这里对应的是mainActivityMembersInjector.injectMembers(activity)方法,将我们的MainActivity注入到该类中。
再然后就是Builder内部类,用来创建我们的MainModule以及自身实例Builder 。所以在DaggerMainComponent类中Builder主要用来初始化各种实例。而真正的将这些实例与Inject关联起来的就是刚才的MainActivity_MembersInjector类,我们看看这个类里做了什么。

public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<MainPresenter> mainPresenterProvider;

  public MainActivity_MembersInjector(Provider<MainPresenter> mainPresenterProvider) {
    assert mainPresenterProvider != null;
    this.mainPresenterProvider = mainPresenterProvider;
  }

  public static MembersInjector<MainActivity> create(Provider<MainPresenter> mainPresenterProvider) {
    return new MainActivity_MembersInjector(mainPresenterProvider);
  }

  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.mainPresenter = mainPresenterProvider.get();
  }

  public static void injectMainPresenter(
      MainActivity instance, Provider<MainPresenter> mainPresenterProvider) {
        instance.mainPresenter = mainPresenterProvider.get();
  }
}

这个类的关键就是injectMembers()方法,这个方法在哪调用的?大家可以回去看下刚才提到的DaggerMainComponent类中的inject()方法:

@Override
public void inject(MainActivity activity){
    mainActivityMembersInjector.injectMembers(activity);
}

所以这里的MainActivity实例参数是由DaggerMainComponent类提供的,然后我们看到了最关键的一句代码:

instance.mainPresenter = mainPresenterProvider.get();

ok,这里,mainPresenterProvider(就是上面DaggerMainComponent类的那个泛型为:Provider<MainPresenter>的对象)中创建好的MainPresenter实例赋值给instance(就是MainActivity!!)的成员mainPresenter,这样我们MainActivity 中用@Inject标注的mainPresenter就得到了实例化,接着就可以在代码中使用了。


补充一点:

public class MainActivity extends AppCompatActivity implements MainContract.View {
    @Inject
    MainPresenter mainPresenter;
    ...
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
         DaggerMainComponent.builder()
                .mainModule(new MainModule(this))
                .build()
                .inject(this);
        //调用Presenter方法加载数据
         mainPresenter.loadData();
         
         ...
    }

}

上面的有一行代码忘记说了

DaggerMainComponent.builder()
                .mainModule(new MainModule(this))
                .build()
                .inject(this);

DaggerMainComponent是由MainComponent自动生成的,在上面所说的目录下就可以找到该类,它调用了mainModule()把MainModule实例化传进去了,依次推理,像我们公司的代码中的Component:

@Component(modules = {ServiceMoudle.class, AppModule.class})
public interface AppComponent {
  ...
}

AppComponent 通过Component注解引入了两个类:ServiceMoudle,AppModule
所以,我们的代码就应该是:

DaggerAppComponent.builder()
                .serviceMoudle(new ServiceMoudle())
                .appModule(new AppModule(mContext.getApplicationContext()))
                .build()
                .inject(this);

实例化了两个要引入的类。

这就是Dagger2的注入过程,虽然咋一看过程巨繁琐,但是你细细推敲过来,每一步都挺简单,挺好理解的,一环扣一环。我开始研究Dagger2的时候上网找些文章,文章中说的这个那个的关系理解起来全乱套了,然后就写在了一张纸上,慢慢理,慢慢看,用着用着也就大致能了解里面的道道了,附上当时的写的一些小东西。


写写画画.jpg

这篇文章就是对网上几篇文章的小总结,我补充了一些我自己的见解,还有有些我认为讲的不尽详细的小地方做了一些补充,顺便自己也学习一遍Dagger2。附上原地址,有兴趣的可以看下,感谢下面的作者:

Google官方MVP+Dagger2架构详解【从零开始搭建android框架系列(6)】
Android:dagger2让你爱不释手-基础依赖注入框架篇
Dagger2从入门到放弃再到恍然大悟

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

推荐阅读更多精彩内容