MyBatis源碼之中心配置文件

首先分析源碼,我們需要知道其使用在入手分析其源碼。

MyBatis的使用:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);
session.close();

從原生的MyBatis使用上看,其使用分為了六部分,分別是:
  1. 文件讀取成流
  2. 獲取一個SqlSessionFactory工廠類,在這一步驟里,完成了解析配置文件,包括mybatis-config.xml和Mapper映射器文件。
  3. 通過SqlSessionFactory獲得一個SqlSession
  4. 根據(jù)SqlSeesion的實例獲取一個Mapper對象
  5. 使用mapper對象調(diào)用接口
  6. 關(guān)閉session

下面就按這些步驟去分析源碼,分析這些步驟中隱藏的操作。
讀取文件成流就不說了,直接進入第二步獲取工廠類時解析配置文件分析。
首先,在分析其源碼之前我們應該關(guān)心的內(nèi)容:其解析過程做了什么,產(chǎn)生了哪些對象,這些對象時放到哪里的。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

首先

我們需要了解SqlSessionFactory 是由SqlSessionFactoryBuilder()的build()方法得到,所以直接進入buil()方法中

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 用于解析 mybatis-config.xml,同時創(chuàng)建了 Configuration 對象 
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 解析XML,最終返回一個 DefaultSqlSessionFactory 
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

從方法里可以看出,這里創(chuàng)建了一個XMLConfigBuilder對象,這個對象時BaseBuilder的一個子類,是用來專門解析全局配置文件的,可以看到BaseBuilder的類圖


image.png
  • XMLMapperBuilder:解析Mapper映射器文件的
  • XMLStatementBuilder:解析增刪改查標簽的
  • XMLScriptBuilder:解析動態(tài)SQL
  • SqlSourceBuilder:將SQL語句中的#{xxx}替換成占位符 ?和將占位符 ?對應的屬性信息一起構(gòu)建成到ParameterMapping中,以便在后續(xù)ParameterHandler真正地參數(shù)賦值
  • ParameterMappingTokenHandler 是SqlSourceBuilder的靜態(tài)內(nèi)部類,是為參數(shù)構(gòu)建 ParameterMapping的。
    這些后面分析源碼的時候都會看到

好了,言歸正傳

我們先來看看創(chuàng)建XMLConfigBuilder對象時做了些什么。

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

XPathParser 類中持有一個Document對象,使我們的XML文件,還有一個XPath對象,是定位信息的方法,是java提供的,XPathParser解析出的元素用一個XNode對象存儲。
XMLConfigBuilder的構(gòu)造方法里可以看出new Configuration()創(chuàng)建了我們重要的對象Configuration,我們都知道它是全局唯一用來存放MyBatis行為信息的。

我們來看看創(chuàng)建Configuration對象時都做了些什么。

public class Configuration {

  protected Environment environment;

  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
...
protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
...

其中一部分源碼就不全部貼上了,自己可以去仔細看看,在這個configuration類里可以看到它有很多的屬性,并且有些屬性是直接賦值了,所以我們就明白了在配置文件中的一些屬性為什么不需要我們?nèi)ピO(shè)置了,因為在其內(nèi)部已經(jīng)幫我們設(shè)置了默認值,如果我們自己設(shè)置了值得話,在后面的配置文件中將會解析出來再進行替換。
還有里面的各種容器跟一些注冊中心可以網(wǎng)上查找更多的資料去了解,因為太多了就不一一解讀了。

我們再看其構(gòu)造函數(shù),里面都是為我們常用的類注冊別名的。

 public Configuration() {
    // 為類型注冊別名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
···

XMLConfigBuilder的創(chuàng)建會新建一個Configuration對象,我們接著往下看

 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 解析XML,最終返回一個 DefaultSqlSessionFactory >>
      return build(parser.parse());

在得到XMLConfigBuilder 對象后,可以看到調(diào)用了其內(nèi)部的parse()方法

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // XPathParser,dom 和 SAX 都有用到 >>
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 對于全局配置文件各種標簽的解析
      propertiesElement(root.evalNode("properties"));
      // 解析 settings 標簽
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 類型別名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 插件
      pluginElement(root.evalNode("plugins"));
      // 用于創(chuàng)建對象
      objectFactoryElement(root.evalNode("objectFactory"));
      // 用于對對象進行加工
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工具箱
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // settings 子標簽賦值,默認值就是在這里提供的 >>
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 創(chuàng)建了數(shù)據(jù)源 >>
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析引用的Mapper映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

parse()方法中首先會parser.evalNode("/configuration")獲取到XNode對象,XNode是MyBatis對Node類的封裝,它會自動解析出元素的元素名,元素的屬性,反之起就是解析XML文件的,重點不在此。

parseConfiguration()方法中是對中心配置文件的解析

見名知意,可以看出它是一步一步的解析器配置中心的標簽,如果設(shè)置有就將其解析并放入configuration中。

Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 類型別名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 插件
      pluginElement(root.evalNode("plugins"));
      // 用于創(chuàng)建對象
      objectFactoryElement(root.evalNode("objectFactory"));
      // 用于對對象進行加工
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工具箱
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
  • 第一是解析<properties>標簽,讀取我們引入的外部配置文件,例如db.properties,這里有兩種類型,一種是放在resorce目錄下的,是相對路徑,一種是寫絕對路徑(url),解析的最終結(jié)果就是我們配置信息放到 XPathparser的variables中和Configuration的variables中
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      // 創(chuàng)建了一個 Properties 對象,后面可以用到
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //為什么放進parser里面
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }
  • 第二是解析<setting>標簽解析成一個Properties對象,將放到后面的setting方法中處理。
  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }
  • 第三步loadCustomVfs(settings) 是獲取Vitual File System的自定義實現(xiàn)類,比如要讀取本地文件,或者FTP遠程文件的時候,就可以用到自定義的VFS類,是根據(jù)在<settings>標簽里的<vfsImpl>標簽設(shè)置生成一個抽象類VFS的子類,最后賦值到Configuration中
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
      String[] clazzes = value.split(",");
      for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
          @SuppressWarnings("unchecked")
          Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
          configuration.setVfsImpl(vfsImpl);
        }
      }
    }
  }
  • 第四步 loadCustomLogImpl(settings)根據(jù)<logImpl>標簽獲取日志的實現(xiàn)類,包括可以有LOG4J,LOG4J2,SLF4J等待,在這里生成一個Log接口的實現(xiàn)類并賦值到Configuration中
private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
  }
  • 第五步typeAliasesElement(root.evalNode("typeAliases"))這一步是解析類型別名,類的別名和類的關(guān)系,放在TypeAliasRegistry對象里面。
 private void typeAliasesElement(XNode parent) {
    // 放入 TypeAliasRegistry
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
  • 第六步pluginElement(root.evalNode("plugins"))插件的解析,這一步就是把插件解析換成Interceptor類,設(shè)置屬性,首先它會先判斷是否為空,就是判斷配置中心是否配置有這一標簽。有則動態(tài)代理實現(xiàn)的插件,放入configuration對象的interceptorChain里。
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        //動態(tài)代理 我們實現(xiàn)的插件
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  • 對于objectFactoryElement、objectWrapperFactoryElement、 reflectorFactoryElement的解析跟插件類似;
  • ObjectFactory 是用來創(chuàng)建返回的對象
  • ObjectWrapperFactory 是用來對對象做特殊處理的,
  • ReflectorFactory 是反射工具箱,對反射進行了封裝
    這些對象都是用resolveClass創(chuàng)建的
 settingsElement(settings);

看看這個方法做了些什么

 private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
···

可以看到里面就是將中心配置文件的<setting>標簽里我們設(shè)置的屬性進行重新賦值,settings二級標簽中一共有26個配置,比如二級緩存、延遲加載、默認執(zhí)行器類型等。一些默認值也是在這里賦值的。

我們再來看看中心配置文件我們設(shè)置的數(shù)據(jù)庫相關(guān)的配置它是怎么解析的

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          // 事務工廠
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 數(shù)據(jù)源工廠(例如 DruidDataSourceFactory )
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          // 數(shù)據(jù)源
          DataSource dataSource = dsFactory.getDataSource();
          // 包含了 事務工廠和數(shù)據(jù)源的 Environment
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 放入 Configuration
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }
  • typeHandlerElement 跟TypeAlias一樣,TypeHandler有兩種配置方式,一種是單獨配置一個類,一種是指定一個package,最后我們得到的是javaType和JdbcType,以及用來做相互映射的TypeHandler之間的映射關(guān)系,存放在TypeHandlerRegistry對象里。
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

映射關(guān)系

private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();

第二大步驟,就是解析我們在配置文件中配置的Mapper映射器文件了

      // 解析引用的Mapper映射器
      mapperElement(root.evalNode("mappers"));

這一步是解析Mapper映射器文件了,到了這里對于配置中心文件的解析就剩下映射器了,下一篇會詳細的介紹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372