逐行閱讀Spring5.X源碼(八)Mybatis是如何利用MapperScan完成掃描的慢叨?

mybaits是通過@MapperScan注解完成掃描的寻定,具體是如何完成的呢?首先看一下MapperScan的源碼:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
    ...注解體省略...
}

@MapperScan上加了一個@Import注解,了解注解知識的讀者都知道侦另,@MapperScan起始就是@Import的子類。在上一篇博客中我們詳細分析了注冊掃描后置處理器ConfigurationClassPostProcessor是如何完成掃描的截酷,其中有一個重要的步驟就是解析@Import注解势篡。mybatis就是利用了這個特性完成的掃描。

回顧以下上一篇博文ConfigurationClassPostProcessor解析@Import注解的過程:
定位到ConfigurationClassParser類的doProcessConfigurationClass方法中的如下代碼:

processImports(configClass, sourceClass, getImports(sourceClass), true);

這個方法就是處理配置類的@Import注解耳幢。起始上一篇博文已經分析過了岸晦,這里我們再分析一遍。
首先睛藻,通過getImports(sourceClass)方法獲取配置類上所有的@Import注解中的類启上。加入我們有一個配置類如下所示:

@Configuration
@ComponentScan(value = "com")
@Import(User.class)
@MapperScan("net")
public class Config extends ConfigSuperClass implements UserInterface {
    @Value("${demo.name}")
    private String name;
    public String getName() {
        return name;
    }
}

getImports方法就是拿到@Import(User.class)上的User.class和@MapperScan("net")父類上的MapperScannerRegistrar.class,然后將這兩個class封裝成SourceClass并返回店印。分析一下getImports源碼:

    private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
        Set<SourceClass> imports = new LinkedHashSet<>();
        Set<SourceClass> visited = new LinkedHashSet<>();
        collectImports(sourceClass, imports, visited);
        return imports;
    }

這個方法中的核心方法是collectImports(sourceClass, imports, visited);冈在,將找到的class加入到imports集合中,然后返回按摘。

    private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
            throws IOException {

        if (visited.add(sourceClass)) {
            for (SourceClass annotation : sourceClass.getAnnotations()) {
                String annName = annotation.getMetadata().getClassName();
                if (!annName.equals(Import.class.getName())) {
                    //如果注解本身不是@Import包券,遞歸查看注解的父注解是否有@Import注解
                    collectImports(annotation, imports, visited);
                }
            }
            //拿到@Import注解上的value值
            imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
        }
    }

總之,就是
1.拿到配置類上所有的注解
2.遞歸拿到所有注解的父注解
3.在所有的注解中找到@Import注解炫贤,拿到value值溅固,放到imports集合中返回。
在本例中兰珍,此時我們拿到了兩個class发魄,一個是配置類本身的@Import注解中的User.class,另一個是@MapperScan的父注解@Import中的MapperScannerRegistrar.class。


拿到import導入的兩個類后,就是真正的進入processImports方法進行處理了励幼,核心代碼如下:

for (SourceClass candidate : importCandidates) {
  if (candidate.isAssignable(ImportSelector.class)) {
        // Candidate class is an ImportSelector -> delegate to it to determine imports
        Class<?> candidateClass = candidate.loadClass();
        // 反射創(chuàng)建這個類的實例對象
        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        //是否有實現(xiàn)相關Aware接口,如果有,這調用相關方法
        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
        // 延遲加載的ImportSelector
        if (selector instanceof DeferredImportSelector) {
            //  延遲加載的ImportSelector先放到List中,延遲加載
            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
        }else {
            // 普通的ImportSelector ,執(zhí)行其selectImports方法,獲取需要導入的類的全限定類名數(shù)組
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            //獲取需要導入類的class
            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
            // 遞歸調用
            processImports(configClass, currentSourceClass, importSourceClasses, false);
              }
        }
    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        // Candidate class is an ImportBeanDefinitionRegistrar ->
        // delegate to it to register additional bean definitions
        Class<?> candidateClass = candidate.loadClass();
        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        ParserStrategyUtils.invokeAwareMethods(registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    }
    else {
        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        // process it as an @Configuration class
        this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass));
     }
}
  • 首先汰寓,if (candidate.isAssignable(ImportSelector.class))判斷導入的類是否實現(xiàn)了ImportSelector接口。該接口源碼如下:
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

接口只定義了一個selectImports(AnnotationMetadata importingClassMetadata)方法苹粟,用于指定需要注冊到容器中的Class名稱有滑。當配置類的@Import注解引入了一個ImportSelector接口實現(xiàn)類后,會根據(jù)selectImports方法返回的Class名稱生成BeanDefinition加載到spring容器中嵌削。

來看一個簡單的示例:

public class User2 {
}

@Component
public class User implements ImportSelector ,BeanFactoryAware {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {User2.class.getName()};
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("實現(xiàn)了BeanFactoryAware接口");
    }
}

上文中毛好,我們在配置類上@Import(User.class)導入了User,此時拿到User的selectImports方法返回的類名苛秕,根據(jù)類名生成對應的BeanDefinition加載到容器中肌访。在這里,把User2定義成了BeanDefinition艇劫,加載到容器中了吼驶。看源碼如何實現(xiàn)的:

//如果實現(xiàn)了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
// 獲取User的class對象
    Class<?> candidateClass = candidate.loadClass();
// 反射創(chuàng)建這個類的實例對象
    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有實現(xiàn)相關Aware接口,如果有,這調用相關方法
    ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
// 延遲加載的ImportSelector
    if (selector instanceof DeferredImportSelector) {
//  延遲加載的ImportSelector先放到List中,延遲加載
    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
    }else {
// 普通的ImportSelector ,執(zhí)行其selectImports方法,獲取需要導入的類的全限定類名數(shù)組
    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 遞歸調用
    processImports(configClass, currentSourceClass, importSourceClasses, false);
    }
}

代碼注釋講的很清楚店煞,相信大家能看懂蟹演,但是有這么行代碼```

ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);

判斷是否有實現(xiàn)相關Aware接口,如果有,這調用相關方法。Aware顷蟀,是感應和感知的意思酒请。當User實現(xiàn)了對應的Aware接口時,此處就會User實現(xiàn)該接口的實現(xiàn)方法鸣个,此例中就是User的setBeanFactory(BeanFactory beanFactory)方法羞反。該方法傳入了一個BeanFactory類型的參數(shù),這個類型的有以下這些方法:


這樣我們就能利用這個Bean工廠做很多很多擴展囤萤。這就是spring插件或者擴展開發(fā)的一個簡單案例苟弛。

Aware是頂級接口,它有很多子接口:


但是在此處不是所有有Aware接口都可以阁将,invokeAwareMethods源碼:

public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
            ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {

        if (parserStrategyBean instanceof Aware) {
            if (parserStrategyBean instanceof BeanClassLoaderAware) {
                ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
                        ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
                if (classLoader != null) {
                    ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
                }
            }
            if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
                ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
            }
            if (parserStrategyBean instanceof EnvironmentAware) {
                ((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
            }
            if (parserStrategyBean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
            }
        }
    }

只有BeanClassLoaderAware膏秫、BeanFactoryAware 、EnvironmentAware做盅、ResourceLoaderAware接口才能被調用缤削。

Aware接口處理完后,就是判斷User是否是延遲加載:

if (selector instanceof DeferredImportSelector)

顯然吹榴,我們的User實現(xiàn)的ImportSelector接口亭敢,沒有實現(xiàn)DeferredImportSelector接口,所以就不是延遲加載图筹,DeferredImportSelector是ImportSelector的子接口帅刀。如果我們要實現(xiàn)延遲加載功能让腹,讓User實現(xiàn)DeferredImportSelector接口即可:

public class User  implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("延遲加載User2");
        return new String[] {User2.class.getName()};
    }
}

啟動spring,就會進入上面的if判斷里的this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector)方法扣溺,handle方法內會將selector再封裝成DeferredImportSelectorHolder類型的對象骇窍,然后保存在數(shù)組中以后調用,調用時機在以后的實例化過程中再講锥余,先記住這點腹纳。
如果不是延遲加載的話,if條件不執(zhí)行驱犹,執(zhí)行else中的語句嘲恍。String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());就是調用下面的方法,獲取你的返回的類名數(shù)組雄驹。


else中的最后兩行代碼就是根據(jù)上一步返回的類名數(shù)組找到對應的class佃牛,然后遞歸。為什么要遞歸調用医舆,因為你導入的類可能還有Import注解啊俘侠,那也要處理的。

  • 其次彬向,else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))判斷是否實現(xiàn)ImportBeanDefinitionRegistrar接口兼贡。該接口允許我們手動封裝BeanDefinition并注冊到容器中攻冷。我們先修改下User類:
@Component
public class User implements  ImportBeanDefinitionRegistrar {


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("實現(xiàn)了ImportBeanDefinitionRegistrar接口");
    }
}

其實娃胆,很小兒科,不講了等曼,在BeanDefinition博文中講爛了里烦。注意此時并沒有立刻調用registerBeanDefinitions這個方法,而是先通過
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
這行代碼放到配置類對應的解析器的一個集合中禁谦,

private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
            new LinkedHashMap<>();
......
    public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
        this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
    }

當配置類都處理完了后胁黑,遍歷所有配置類解析器,調用這個集合中所有類的addImportBeanDefinitionRegistrar方法州泊。

  • 最后丧蘸,如果以上接口都沒實現(xiàn)。按普通配置類處理遥皂,遞歸調用力喷,跟Config 配置類的處理方式一樣,再來一遍上述過程演训。

也就是說弟孟,如果Import進來的類實現(xiàn)了ImportBeanDefinitionRegistrar 或 ImportSelector 接口,則繼續(xù)遞歸處理他們生成的類样悟,直到找到沒有實現(xiàn)這兩個接口的類拂募,找到后就當作配置類按照Config的方式進行處理庭猩。

MapperScannerRegistrar

上面講了User,其實MapperScannerRegistrar 也一樣

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware 

spring會調用MapperScannerRegistrarregisterBeanDefinitions方法陈症。
上面講了一堆蔼水,就是為了說明spring是如何調用MapperScannerRegistrarregisterBeanDefinitions方法的。下一步就是分析registerBeanDefinitions這個方法即可爬凑。

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //解析MapperScan具體信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

先解析MapperScan類的基礎配置信息徙缴,起始就是拿到MapperScan所有方法的默認返回值。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  //掃描路徑數(shù)組
  String[] value() default {};
  //掃描路徑數(shù)組嘁信,另一種表達方式于样,跟上面一樣
  String[] basePackages() default {};
//類數(shù)組,根據(jù)類獲取所在的包路徑潘靖,作為基礎掃描類型
  Class<?>[] basePackageClasses() default {};
//名字生成器穿剖,配置路徑下的類名根據(jù)此名字生成器進行命名
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
 //待掃描的注解,注冊基本包中具有指定注解的所有接口卦溢。注意這可以與markerInterface結合使用
  Class<? extends Annotation> annotationClass() default Annotation.class;
//掃描指定的類
  Class<?> markerInterface() default Class.class;
//如果spring中有多個sqlSessionTemplate糊余,指定你要用哪一個,通常只有當您有多個數(shù)據(jù)源時才需要這樣做
  String sqlSessionTemplateRef() default "";
//如果spring中有多個SqlSessionFactory单寂,指定你要用哪一個贬芥,通常只有當您有多個數(shù)據(jù)源時才需要這樣做
  String sqlSessionFactoryRef() default "";
//Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean  mapper的創(chuàng)建工廠
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
//對于mapper是否懶加載
  String lazyInitialization() default "";

}

然后調用registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)方法,

第一步
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
將MapperScannerConfigurer生成一個GenericBeanDefinition類型的BeanDefinition再返回來備用宣决,看源碼:

    public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
//構建一個GenericBeanDefinition
        BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
//GenericBeanDefinition與MapperScannerConfigurer對應
        builder.beanDefinition.setBeanClass(beanClass);
        return builder;
    }

第二步設置屬性processPropertyPlaceHolderstrue蘸劈,下文講為什么

builder.addPropertyValue("processPropertyPlaceHolders", true);

第三步找到掃描的包路徑,放到basePackages集合中尊沸,如果沒有配置掃描路徑威沫,則將@MapperScan所在的配置類的包路徑作為基礎包路徑:

if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

然后就將將MapperScannerConfigurer對應的GenericBeanDefinition注冊到容器中

 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

好像,沒有掃描啊洼专,只是做了一些配置工作棒掠,說實話,如果讀者沒有讀過我之前關于后置處理器的文章屁商,很難搞懂接下來我要講的內容——自定義后置處理器何時調用烟很? 建議先閱讀《逐行閱讀Spring5.X源碼(五) 初探BeanFactoryPostProcessor后置處理器,難蜡镶,特別難雾袱。》

MapperScannerConfigurer

我們首先要搞清楚帽哑,上文注冊到容器中的MapperScannerConfigurer到底是個什么鬼谜酒?自動掃描 將Mapper接口生成代理注入到Spring。

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

顧名思義妻枕,mapper掃描配置類僻族,這個類實現(xiàn)了BeanDefinitionRegistryPostProcessor接口粘驰! 變成了一個后置處理器。該接口只有一個postProcessBeanDefinitionRegistry方法述么,spring啟動過程中會主動調用MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法蝌数。所以接下來就要分析這個方法

這個方法第一步

if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

processPropertyPlaceHolders,上文講過這個值確實是true度秘,所以這條判斷成立顶伞,執(zhí)行processPropertyPlaceHolders();方法。此函數(shù)的官方注解如下:

 /*
   * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
   * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
   * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
   * definition. Then update the values.
   */

大概意思就是:BeanDefinitionRegistries會在應用啟動的時候調用剑梳,并且會早于BeanFactoryPostProcessors的調用(在之前后置處理器博文中講過唆貌,確實是
),這就意味著PropertiesResourceConfigurers還沒有被加載垢乙,所有對于屬性文件的引用將會失效锨咙,為避免此種情況發(fā)生,此方法手動地找出定義的PropertyResourceConfigurers并進行調用以以保證對于屬性的引用可以正常工作追逮。這個函數(shù)主要做以下兩件事情:

  1. 找到所有已經注冊的PropertyResourceConfigurer類型的bean酪刀。
  2. 模擬Spring的環(huán)境來用處理器。這里通過使用呢new DefaultlistableBeanFactory()來模擬Spring中的環(huán)境(完成處理器的調用后便失效)钮孵,將映射的bean骂倘,也就是MapperScannerConfigurer類型bean注冊到環(huán)境中來進行后處理器的調用。

可以先不用關注這點巴席,這個會在mybatis源碼專題中詳解历涝,下一步我們看mybatis如何掃描。

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

ClassPathMapperScanner類繼承了spring的ClassPathBeanDefinitionScanner類情妖,專門用來掃描注冊

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

如果讀者閱讀過本專題之前的文章睬关,肯定對這個類非常了解诱担,如果沒讀過毡证,可以 參考《逐行閱讀Spring5.X源碼(六) ClassPathBeanDefinitionScanner掃描器》
逐行閱讀Spring5.X源碼(番外篇)自定義掃描器, Mybatis是如何利用spring完成Mapper掃描的

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蔫仙,一起剝皮案震驚了整個濱河市料睛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摇邦,老刑警劉巖恤煞,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異施籍,居然都是意外死亡居扒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門丑慎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喜喂,“玉大人瓤摧,你說我怎么就攤上這事∮裼酰” “怎么了照弥?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長进副。 經常有香客問我这揣,道長,這世上最難降的妖魔是什么影斑? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任给赞,我火速辦了婚禮,結果婚禮上矫户,老公的妹妹穿的比我還像新娘塞俱。我一直安慰自己,他們只是感情好吏垮,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布障涯。 她就那樣靜靜地躺著,像睡著了一般膳汪。 火紅的嫁衣襯著肌膚如雪唯蝶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天遗嗽,我揣著相機與錄音粘我,去河邊找鬼。 笑死痹换,一個胖子當著我的面吹牛征字,可吹牛的內容都是我干的。 我是一名探鬼主播娇豫,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼匙姜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冯痢?” 一聲冷哼從身側響起氮昧,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浦楣,沒想到半個月后袖肥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡振劳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年椎组,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片历恐。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡寸癌,死狀恐怖选调,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情灵份,我是刑警寧澤仁堪,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站填渠,受9級特大地震影響弦聂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜氛什,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一莺葫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枪眉,春花似錦捺檬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒿秦,卻和暖如春烤镐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棍鳖。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工炮叶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渡处。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓镜悉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親医瘫。 傳聞我的和親對象是個殘疾皇子侣肄,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359