Dubbo源碼學(xué)習(xí)二--Dubbo配置文件加載的過程

1.Dubbo服務(wù)架構(gòu)圖

圖1--Dubbo原理圖

名詞解釋

Provider:暴露服務(wù)的服務(wù)提供方
Container:服務(wù)運行容器(可以理解為Spring容器)
Registry:服務(wù)注冊與發(fā)現(xiàn)的注冊中心
Consumer:調(diào)用遠程服務(wù)的消費者(一般為Web應(yīng)用)
Monitor:統(tǒng)計服務(wù)的調(diào)用次數(shù)喝調(diào)用時間的監(jiān)控中心

Container詳解

Dubbo的Container是一個獨立的容器毙沾,因為Dubbo服務(wù)通常不需要部署在Web容器(如Tomcat归形、JBoss等)中挺峡,沒有必要用Web容器去加載服務(wù)葵孤,服務(wù)容器只是一個簡單的Main方法,并且在一個簡單的Spring容器用于暴露服務(wù)橱赠。

org.apache.dubbo.container.Container是服務(wù)啟動的主類源碼:

package org.apache.dubbo.container;

import org.apache.dubbo.common.extension.SPI;

/**
* Container. (SPI, Singleton, ThreadSafe)
*/
@SPI("spring")
public interface Container {

  /**
   * start method to load the container.
   */
  void start();

  /**
   * stop method to unload the container.
   */
  void stop();

}

通過以上代碼可以看到尤仍,這個接口有兩個方法,start()方法和stop()方法狭姨,它的實現(xiàn)類有Log4jContainer宰啦、LogBackContainer和SpringContainer,由于該接口上有@SPI(“spring”)注解饼拍,所以默認調(diào)用SpringContainer赡模。
那么接下來看一下SpringContainer的源碼:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.container.spring;

import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.container.Container;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * SpringContainer. (SPI, Singleton, ThreadSafe)
 *
 * The container class implementation for Spring
 */
public class SpringContainer implements Container {
    /**讀取dubbo.properties配置文件中dubbo.spring.config”的參數(shù)*/
    public static final String SPRING_CONFIG = "dubbo.spring.config”;
    /**默認加載的Spring配置文件路徑*/
    public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
    private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
    static ClassPathXmlApplicationContext context;

    public static ClassPathXmlApplicationContext getContext() {
        return context;
    }

    @Override
    public void start() {
        String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
        /**如果參數(shù)為空,則默認加載META-INF/spring路徑下的所有配置文件*/
        if (StringUtils.isEmpty(configPath)) {
            configPath = DEFAULT_SPRING_CONFIG;
        }
        context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
        context.refresh();
        //加載完配置文件之后师抄,Spring容器開始啟動
        context.start();
    }

    @Override
    public void stop() {
        try {
            if (context != null) {
                context.stop();
                context.close();
                context = null;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

}

容器在啟動時漓柑,先去讀取dubbo.properties文件,如果dubbo.properties文件中沒有配置dubbo.spring.config 參數(shù)叨吮,則默認加載META-INF/spring下的全部Spring配置文件辆布。


圖2--Dubbo--provider示例代碼結(jié)構(gòu).png

SpringContainer加載完配置文件之后,Spring容器開始啟動茶鉴,此時Dubbo服務(wù)的整個啟動過程結(jié)束锋玲。

2. Dubbo-provider服務(wù)的配置文件:

由圖2我們可以看出Dubbo服務(wù)的項目結(jié)構(gòu)圖,在resources目錄下涵叮,重點需要兩個配置文件:
1惭蹂、dubbo-consumer.xml
2、dubbo.properties
在項目啟動時围肥,Spring容器會加載這兩個配置文件剿干,Dubbo框架中的dubbo-config模塊會解析這些配置文件,并且解析成對應(yīng)的Bean定義穆刻,并注冊到Spring上下文中置尔。那么接下來我們分析,這些配置文件到底是怎么被解析成對應(yīng)的Bean氢伟,并且注冊到Spring的上下文當中的榜轿。

3.Dubbo服務(wù)的啟動類:

想要了解配置文件被加載的過程幽歼,那么我們首先從項目啟動的入口著手進行分析。接下來谬盐,我們看下Dubbo服務(wù)啟動類的代碼:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.demo.provider;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {
    public static void main(String[] args) throws Exception {
        /**創(chuàng)建Spring IOC容器*/
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
        context.start();
        System.in.read();
    }
}

啟動類的關(guān)鍵代碼甸私,加載Sring的IOC容器,并且指定dubbo的配置文件飞傀,那么當Spring容器啟動的時候皇型,會加載dubb-provider.xml文件。

關(guān)于ClassPathXmlApplicationContext的加載過程有篇博客介紹的非常詳細砸烦,博客地址: ClassPathXmlApplicationContext加載過程 那么當Spring容器加載完dubbo.provider.xml文件之后弃鸦,又怎么將配置文件解析并且獲取到里面的一些參數(shù)呢,接下來我們看一下dubbo-config模塊的代碼幢痘,看下具體解析唬格。

4.Dubbu服務(wù)配置文件的加載過程

首先我們來看一下dubbo-config模塊的整體架構(gòu):


圖3--Dubbo-config模塊結(jié)構(gòu)

如圖3所示,dubbo-config模塊分為兩塊颜说,一個是dubbon-config-api和dubbo-config-spring购岗,其中dubbo-config-api提供了一些接口和封裝了一些實體類,dubbo-config-spring重點實現(xiàn)的是在spring容器啟動時门粪,加載并且解析dubbo項目的配置文件喊积。

基于schema設(shè)計解析:

dubbo-config-spring的META-INF下有三個配置文件: dubbo.xsd、spring.handlers玄妈、spring.schemas文件注服。那么我們分別看下這三個配置文件的作用:
dubbo.xsd文件:規(guī)范了在編寫xml文件時需要有哪些元素,以及元素的節(jié)點是什么措近,也就是對我們的provider服務(wù)中的dubbo-provider.xml文件做了一個約束溶弟。
spring.schemas文件:

http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd

文件中指定了dubbo的約束文件,Spring框架通過spring.handlers中的配置來解析用戶的自定義配置瞭郑,而dubbo.xsd文件正是用戶自定義的一種約束規(guī)范辜御,所以在此指定dubbo.xsd文件 Spring就可以解析xsd文件中配置的節(jié)點。
spring.handlers文件:

http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

指定了解析配置文件中標簽的解析類屈张。
Spring解析項目中dubbo-provider.xml的過程可以這么理解:
1擒权、加載dubbo-provider.xml
2、解析到自定義的namespace(如 <dubbo:service>標簽)時查找對應(yīng)的spring.schemas和spring.handlers文件
3阁谆、spring.schemas文件指定了約束文件碳抄,spring.handlers指定了解析標簽的類及DubboNamespaceHandler來進行初始化和解析。
dubbo.xsd文件中定義了很多模塊场绿,這些模塊基本可以滿足大多數(shù)使用場景剖效。

基于XML配置解析原理:

通過以上分析我們可以得知,最終解析xml文件的是DubboNamespaceHandler這個類,接下來我們看下DubboNamespaceHandler的實現(xiàn):

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.config.spring.schema;

import org.apache.dubbo.common.Version;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.MetadataReportConfig;
import org.apache.dubbo.config.ModuleConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.MonitorConfig;
import org.apache.dubbo.config.MetricsConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.spring.ConfigCenterBean;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.config.spring.ServiceBean;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * DubboNamespaceHandler
 *
 * @export
 */
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}

DubboNamespaceHandler的繼承圖:


圖4--DubboNamespaceHandler繼承結(jié)構(gòu)

DubboNamespaceHandler集成了NamespaceHandlerSupport璧尸,因此不需要實現(xiàn)全部的解析工作咒林,只需要將自定義schema中的元素解析器注冊進來就可以。
DubboBeanDefinitionParser類實現(xiàn)了BeanDefinitionParser這個接口爷光,負責(zé)將標簽轉(zhuǎn)換成bean定義對象BeanDefinition垫竞。
我們接下來看一下DubboBeanDefinitionParser的parser方法解析步驟:

private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
    /**初始化RootBeanDefinition,生成Spring的bean定義蛀序,指定beanClass交給Spring反射創(chuàng)建實例*/
    RootBeanDefinition beanDefinition = new RootBeanDefinition();
    beanDefinition.setBeanClass(beanClass);
    beanDefinition.setLazyInit(false);
    String id = element.getAttribute("id”);
    /**確保Spring容器中沒有重復(fù)的Bean定義*/
    if (StringUtils.isEmpty(id) && required) {
        /**依次嘗試獲取XML文件配置標簽的name和interface屬性做為Bean的唯一Id*/
        String generatedBeanName = element.getAttribute("name");
        if (StringUtils.isEmpty(generatedBeanName)) {
                /**如果協(xié)議中沒有指定名稱欢瞪,則默認為Dubbo*/
            if (ProtocolConfig.class.equals(beanClass)) {
                generatedBeanName = "dubbo";
            } else {
                generatedBeanName = element.getAttribute("interface");
            }
        }
        if (StringUtils.isEmpty(generatedBeanName)) {
            generatedBeanName = beanClass.getName();
        }
        id = generatedBeanName;
        int counter = 2;
        while (parserContext.getRegistry().containsBeanDefinition(id)) {
            id = generatedBeanName + (counter++);
        }
    }
    if (StringUtils.isNotEmpty(id)) {
        if (parserContext.getRegistry().containsBeanDefinition(id)) {
            throw new IllegalStateException("Duplicate spring bean id " + id);
        }
        /**每次解析回想Sring注冊心的BeanDefinition,后續(xù)會追加屬性*/
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        beanDefinition.getPropertyValues().addPropertyValue("id", id);
    }
    /**<dubbo:protocol>標簽解析*/
    if (ProtocolConfig.class.equals(beanClass)) {
        for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
            BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
            PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
            if (property != null) {
                Object value = property.getValue();
                if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                    definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
                }
            }
        }
    } 
    /**<dubbo:service>標簽解析*/
    else if (ServiceBean.class.equals(beanClass)) {
        String className = element.getAttribute("class");
        if (className != null && className.length() > 0) {
            RootBeanDefinition classDefinition = new RootBeanDefinition();
            classDefinition.setBeanClass(ReflectUtils.forName(className));
            classDefinition.setLazyInit(false);
            parseProperties(element.getChildNodes(), classDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
        }
    } 
    /**<dubbo:provider>標簽解析*/
    else if (ProviderConfig.class.equals(beanClass)) {
        parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
    } 
    /**<dubbo:consumer>標簽解析*/
    else if (ConsumerConfig.class.equals(beanClass)) {
        parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
    }
    Set<String> props = new HashSet<>();
    ManagedMap parameters = null;
    for (Method setter : beanClass.getMethods()) {
        String name = setter.getName();
        if (name.length() > 3 && name.startsWith("set")
                && Modifier.isPublic(setter.getModifiers())
                && setter.getParameterTypes().length == 1) {
            Class<?> type = setter.getParameterTypes()[0];
            String beanProperty = name.substring(3, 4).toLowerCase() + name.substring(4);
            String property = StringUtils.camelToSplitName(beanProperty, "-");
            props.add(property);
            // check the setter/getter whether match
            Method getter = null;
            try {
                getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
            } catch (NoSuchMethodException e) {
                try {
                    getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
                } catch (NoSuchMethodException e2) {
                    // ignore, there is no need any log here since some class implement the interface: EnvironmentAware,
                    // ApplicationAware, etc. They only have setter method, otherwise will cause the error log during application start up.
                }
            }
            if (getter == null
                    || !Modifier.isPublic(getter.getModifiers())
                    || !type.equals(getter.getReturnType())) {
                continue;
            }
            if ("parameters".equals(property)) {
                parameters = parseParameters(element.getChildNodes(), beanDefinition);
            } else if ("methods".equals(property)) {
                parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
            } else if ("arguments".equals(property)) {
                parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
            } else {
                String value = element.getAttribute(property);
                if (value != null) {
                    value = value.trim();
                    if (value.length() > 0) {
                        if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
                            RegistryConfig registryConfig = new RegistryConfig();
                            registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
                            beanDefinition.getPropertyValues().addPropertyValue(beanProperty, registryConfig);
                        } else if ("provider".equals(property) || "registry".equals(property) || ("protocol".equals(property) && ServiceBean.class.equals(beanClass))) {
                            /**
                             * For 'provider' 'protocol' 'registry', keep literal value (should be id/name) and set the value to 'registryIds' 'providerIds' protocolIds'
                             * The following process should make sure each id refers to the corresponding instance, here's how to find the instance for different use cases:
                             * 1. Spring, check existing bean by id, see{@link ServiceBean#afterPropertiesSet()}; then try to use id to find configs defined in remote Config Center
                             * 2. API, directly use id to find configs defined in remote Config Center; if all config instances are defined locally, please use {@link org.apache.dubbo.config.ServiceConfig#setRegistries(List)}
                             */
                            beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids", value);
                        } else {
                            Object reference;
                            if (isPrimitive(type)) {
                                if ("async".equals(property) && "false".equals(value)
                                        || "timeout".equals(property) && "0".equals(value)
                                        || "delay".equals(property) && "0".equals(value)
                                        || "version".equals(property) && "0.0.0".equals(value)
                                        || "stat".equals(property) && "-1".equals(value)
                                        || "reliable".equals(property) && "false".equals(value)) {
                                    // backward compatibility for the default value in old version's xsd
                                    value = null;
                                }
                                reference = value;
                            } else if(ONRETURN.equals(property) || ONTHROW.equals(property) || ONINVOKE.equals(property)) {
                                int index = value.lastIndexOf(".");
                                String ref = value.substring(0, index);
                                String method = value.substring(index + 1);
                                reference = new RuntimeBeanReference(ref);
                                beanDefinition.getPropertyValues().addPropertyValue(property + METHOD, method);
                            } else {
                                if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
                                    BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
                                    if (!refBean.isSingleton()) {
                                        throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
                                    }
                                }
                                reference = new RuntimeBeanReference(value);
                            }
                            beanDefinition.getPropertyValues().addPropertyValue(beanProperty, reference);
                        }
                    }
                }
            }
        }
    }
    NamedNodeMap attributes = element.getAttributes();
    int len = attributes.getLength();
    for (int i = 0; i < len; i++) {
        Node node = attributes.item(i);
        String name = node.getLocalName();
        if (!props.contains(name)) {
            if (parameters == null) {
                parameters = new ManagedMap();
            }
            String value = node.getNodeValue();
            parameters.put(name, new TypedStringValue(value, String.class));
        }
    }
    if (parameters != null) {
        beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
    }
    return beanDefinition;
}

Dubbo通過parse方法解析配置文件徐裸,并將配置文件的各個參數(shù)映射到對應(yīng)的JavaBean引有。

我是割草的小豬頭,不斷學(xué)習(xí)倦逐,不斷進步,后續(xù)陸續(xù)更新Dubbo系列的文章宫补,如您有興趣一起了解檬姥,歡迎關(guān)注,如文章中有不妥之處粉怕,歡迎指正健民!

Dubbo系列文章一----Dubbo重點掌握模塊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贫贝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稚晚,老刑警劉巖崇堵,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異客燕,居然都是意外死亡鸳劳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門也搓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赏廓,“玉大人,你說我怎么就攤上這事傍妒♂C” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵颤练,是天一觀的道長既忆。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么尿贫? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任电媳,我火速辦了婚禮,結(jié)果婚禮上庆亡,老公的妹妹穿的比我還像新娘匾乓。我一直安慰自己,他們只是感情好又谋,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布拼缝。 她就那樣靜靜地躺著,像睡著了一般彰亥。 火紅的嫁衣襯著肌膚如雪咧七。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天任斋,我揣著相機與錄音继阻,去河邊找鬼。 笑死废酷,一個胖子當著我的面吹牛瘟檩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澈蟆,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼墨辛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了趴俘?” 一聲冷哼從身側(cè)響起睹簇,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寥闪,沒想到半個月后太惠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡疲憋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年垛叨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柜某。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗽元,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喂击,到底是詐尸還是另有隱情剂癌,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布翰绊,位于F島的核電站佩谷,受9級特大地震影響旁壮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谐檀,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一抡谐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桐猬,春花似錦麦撵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惫撰,卻和暖如春羔沙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厨钻。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工扼雏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夯膀。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓诗充,卻偏偏與公主長得像,于是被迫代替她去往敵國和親棍郎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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