spring-beans深入源碼之Bean Definition(Bean的裝載過程)

我們在使用一個bean得時候尊沸,在不用任何框架的情況下都是需要自己new的俊犯,spring框架既然為我們提供bean容器使得我們把bean得管理權(quán)交給它妇多,那么我們看看spring的bean是怎么裝載的,先說xml燕侠,注解的方式其實是一樣的者祖。
   接著上篇的test例子<b>CollectionMergingTests</b>其中最開始有個<b>setUp</b>方法,這是junit方法運行前的裝配方法

public class CollectionMergingTests extends TestCase {

    private DefaultListableBeanFactory beanFactory;
        @Override
    protected void setUp() throws Exception {
        this.beanFactory = new DefaultListableBeanFactory();
        BeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("collectionMerging.xml", getClass()));
    }
………………
}

這個方法就是在裝配bean到容器中绢彤。
  <b>DefaultListableBeanFactory </b>是spring 真正可以獨立使用IOC容器的<b>BeanFactory</b>咸包。繼承和實現(xiàn)的傳遞性可知<b>DefaultListableBeanFactory </b>默認實現(xiàn)了<b>BeanFactory</b>和<b>BeanDefinitionRegistry</b>,<b>DefaultListableBeanFactory </b>類繼承圖(引用自software-architecture-design

DefaultListableBeanFactory繼承關(guān)系

直接debug看下其初始化過程便基本知道繼承關(guān)系。

DefaultListableBeanFactory初始化第一步

可見先是到static初始化塊

static {
        try {
            javaUtilOptionalClass =
                    ClassUtils.forName("java.util.Optional", DefaultListableBeanFactory.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            // Java 8 not available - Optional references simply not supported then.
        }
        try {
            javaxInjectProviderClass =
                    ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - Provider interface simply not supported then.
        }
    }

加載class java.util.Optional(JAVA8新加入的)和interface javax.inject.Provider
<b>java.util.Optional</b>是JAVA8新加入的類杖虾,這個類專門用來解決空引用的問題,同時這個類和lambda表達式和函數(shù)式編程也可以比較好的整合在一起使用
java.util.Optional doc
<b>javax.inject.Provider</b>提供了一個 T的實例媒熊。 通常作為一個依賴注入容器器的父接口. 可以注入任何類型的 T, 當然也可以入 Provider<T>
相對于直接注入 T 奇适,注入 Provider<T> 有如下作用(doc的英文大概翻譯):

  • 檢索多個實例
  • 延遲或者選擇性的檢索一個實例
  • 打破循環(huán)依賴
  • 抽象的scope,可以從一個包含scope的更小的scope中檢索一個實例
    javax.inject.Provider doc

在debug該類初始化的過程中我在類的屬性初始化上打斷點無意間發(fā)現(xiàn)了eclipse的斷點類型

breakpoint

第一處的是<b>Watchpoint</b>這個主要是關(guān)注變量的值得變化過程芦鳍,第二處是<b>Line Breakpoint</b>最簡單的斷點嚷往,即執(zhí)行到此處就斷點,還有柠衅。eclipse 還有條件斷點皮仁,方法斷點 異常斷點 遠程調(diào)試等 這個完了再細化記錄博客吧。
這里想說的是為什么 第一行是<b>Watchpoint</b>二處是<b>Line Breakpoint</b> 同樣是屬性是為什么菲宴? 其實是final關(guān)鍵字的作用贷祈,<b>Watchpoint</b>是觀察值的變化情況 final的變量是不可變的 所以final修飾的參數(shù)斷點都是<b>Line Breakpoint</b> 類似于方法斷點,所以有時候要分清你可以在屬性上設(shè)置的斷點類型喝峦。

接下來到了屬性的初始化
屬性初始化

繼續(xù)往下走的時候可以看到 類初始化的關(guān)系 先是<b>DefaultListableBeanFactory</b>static靜態(tài)初始化塊和static屬性势誊,然后是SimpleAliasRegistry的屬性


SimpleAliasRegistry初始化

再是SimpleAliasRegistry的子類<b>DefaultSingletonBeanRegistry</b>


DefaultSingletonBeanRegistry初始化

接著是<b>DefaultSingletonBeanRegistry</b>的子類<b>FactoryBeanRegistrySupport</b>
Paste_Image.png

接著<b>FactoryBeanRegistrySupport</b>子類<b>AbstractBeanFactory</b>


AbstractBeanFactory初始化

接著是<b>AbstractBeanFactory</b>子類<b>AbstractAutowireCapableBeanFactory</b>

AbstractAutowireCapableBeanFactory初始化

最后到<b>DefaultListableBeanFactory</b>自己

DefaultListableBeanFactory初始化
由此我們基本可以得出類初始化的順序(為什么要說這個 是因為我看到網(wǎng)上好多說法和我運行的得到的結(jié)果不一致):

先找到根類,根類static靜態(tài)塊和static屬性的初始化 接著根類的子類static靜態(tài)塊和static屬性初始化 最后回到類本身的類static靜態(tài)塊和static屬性初始化然后再到根類 其他屬性的初始化谣蠢,接著根類子類的其他屬性初始化 最后回到自己的其他屬性初始化粟耻。
<b>DefaultListableBeanFactory</b>初始化完畢以后接著是<b>BeanDefinitionReader</b>的初始化 選擇初始化的類實例是<b>XmlBeanDefinitionReader</b>初始化完畢后調(diào)用

reader.loadBeanDefinitions(new ClassPathResource("collectionMerging.xml", getClass()));

方法去加載collectionMerging.xml配置文件。
ClassPathResource類是Resource接口實現(xiàn)類的子類眉踱,如果沒有指定相對的類名挤忙,該類將從類的根路徑開始尋找某個resource,如果指定了相對的類名谈喳,則根據(jù)指定類的相對路徑來查找某個resource册烈。上面的還可以寫成:

reader.loadBeanDefinitions(new ClassPathResource(
"org/springframework/beans/factory/xml/collectionMerging.xml"));

<b>XmlBeanDefinitionReader</b>初始化的時候先找到根類<b>AbstractBeanDefinitionReader</b>
其構(gòu)造方法

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

在構(gòu)造方法中有instanceof ResourceLoader 判斷 顯然傳入的registry 是<b>DefaultListableBeanFactory</b>的實例 沒有實現(xiàn)ResourceLoader接口 所以走else 邏輯創(chuàng)建<b>PathMatchingResourcePatternResolver</b>類,在<b>PathMatchingResourcePatternResolver</b>初始化的過程中

public PathMatchingResourcePatternResolver() {
        this.resourceLoader = new DefaultResourceLoader();
    }

其創(chuàng)建的是<b>DefaultResourceLoader</b>類 該類實現(xiàn)了<b>ResourceLoader</b>接口婿禽。所以最后還是創(chuàng)建的是<b>ResourceLoader</b>的實例

public DefaultResourceLoader() {
        this.classLoader = ClassUtils.getDefaultClassLoader();
    }

這個構(gòu)造方法就是為了初始化private ClassLoader classLoader;屬性用于load xml資源茄厘。
好了<b>Resource</b>參數(shù)構(gòu)造完畢后進入

@Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

方法矮冬,這里將<b>Resource</b>包裝成了<b>EncodedResource</b> 其實EncodedResource是對Resource的包裝 增加了encoding和charset屬性而已 這里都為null 相當于默認的null。接著進入方法

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

這里會有一個比較經(jīng)典的同步緩存的方式

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

this.resourcesCurrentlyBeingLoaded是

private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
            new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");

可以看到采用了<b>ThreadLocal</b>的存貯方式次哈。在JDK1.2的時候就提供了<b>java.lang.ThreadLocal</b><b>ThreadLocal</b>為解決多線程問題提供了非常非常大幫助胎署,當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本窑滞,所以每一個線程都可以獨立地改變自己的副本琼牧,而不會影響其它線程所對應(yīng)的副本。這樣就完美的解決了多線程的問題哀卫,不過對于計數(shù)的情況不適用 建議使用ATMOIC的變量巨坊。
在沒有取到的情況下 將新的<b>EncodedResource</b>加入ThreadLocal變量的緩存中。
接著從<b>EncodedResource</b>獲取輸入流<b>InputStream</b>構(gòu)造 <b>InputSource</b>,<b>InputSource</b>比較簡單的一個類,封裝了一下<b>InputStream</b>最后調(diào)用 doLoadBeanDefinitions方法此改。部分源碼:

try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }

這里的<b>finally</b>方法最后將<b>ThreadLocal</b>變量<b>resourcesCurrentlyBeingLoaded</b> remove掉了趾撵。看了好半天才看懂其中的奇妙之處:<b>這個方法本身存在多線程問題共啃,最簡單的做法就是同步語句塊占调,但是大家知道這會影響性能所以借助<b>ThreadLocal</b>變量做了一個過程同步 在使用完即remove掉</b>。
<b>doLoadBeanDefinitions</b>是java dom加載xml的過程移剪,大家都知道java解析xml的幾種方式究珊,基本的解析方式有兩種,一種叫SAX,另一種叫DOM纵苛,再細一點有

  • DOM生成和解析XML文檔
    為 XML 文檔的已解析版本定義了一組接口剿涮。解析器讀入整個文檔,然后構(gòu)建一個駐留內(nèi)存的樹結(jié)構(gòu)攻人,然后代碼就可以使用 DOM 接口來操作這個樹結(jié)構(gòu)取试。
    優(yōu)點:整個文檔樹在內(nèi)存中,便于操作怀吻;支持刪除想括、修改、重新排列等多種功能烙博;
    缺點:將整個文檔調(diào)入內(nèi)存(包括無用的節(jié)點)瑟蜈,浪費時間和空間;使用場合:一旦解析了文檔還需多次訪問這些數(shù)據(jù)渣窜;硬件資源充足(內(nèi)存铺根、CPU)。
  • SAX生成和解析XML文檔
    為解決DOM的問題乔宿,出現(xiàn)了SAX位迂。SAX ,事件驅(qū)動。當解析器發(fā)現(xiàn)元素開始掂林、元素結(jié)束臣缀、文本、文檔的開始或結(jié)束等時泻帮,發(fā)送事件精置,程序員編寫響應(yīng)這些事件的代碼,保存數(shù)據(jù)锣杂。
    優(yōu)點:不用事先調(diào)入整個文檔脂倦,占用資源少;SAX解析器代碼比DOM解析器代碼小元莫,適于Applet赖阻,下載。
    缺點:不是持久的踱蠢;事件過后火欧,若沒保存數(shù)據(jù),那么數(shù)據(jù)就丟了茎截;無狀態(tài)性苇侵;從事件中只能得到文本,但不知該文本屬于哪個元素稼虎;使用場合:Applet;只需XML文檔的少量內(nèi)容,很少回頭訪問招刨;機器內(nèi)存少霎俩;
  • DOM4J生成和解析XML文檔
    DOM4J 是一個非常非常優(yōu)秀的Java XML API,具有性能優(yōu)異沉眶、功能強大和極端易用使用的特點打却,同時它也是一個開放源代碼的軟件。如今你可以看到越來越多的 Java 軟件都在使用 DOM4J 來讀寫 XML谎倔,特別值得一提的是連 Sun 的 JAXM 也在用 DOM4J柳击。
  • JDOM生成和解析XML
    為減少DOM、SAX的編碼量片习,出現(xiàn)了JDOM捌肴;優(yōu)點:20-80原則,極大減少了代碼量藕咏。使用場合:要實現(xiàn)的功能簡單状知,如解析、創(chuàng)建等孽查,但在底層饥悴,JDOM還是使用SAX(最常用)、DOM、Xanan文檔西设。

這里spring用java dom解析xml 我想原因就是需要加載的xml配置文件都不是大型的xml文件瓣铣,都是比較小的 所以使用java dom反而效果會好一些。
這里定義了<b>DocumentLoader</b>接口贷揽,提供了默認的實現(xiàn)類<b>DefaultDocumentLoader</b>其中的方法:

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

解析過程可以看oracle jom的api文檔
最后返回<b>Document</b>對象棠笑。

Documen return

進入很重要的方法<b>registerBeanDefinitions</b>該方法將讀取的dom轉(zhuǎn)換成bean definition先進入方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

第一行BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 創(chuàng)建了<b>BeanDefinitionDocumentReader</b>對象 該對象是將document中包含的所偶bean定義解析出來。

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
    }

第二行int countBefore = getRegistry().getBeanDefinitionCount(); 獲取到解析前的bean的數(shù)量
第三行documentReader.registerBeanDefinitions(doc, createReaderContext(resource));這里先根據(jù)傳入的<b>Resource</b>參數(shù)創(chuàng)建<b>XmlReaderContext</b>對象,官方給出的解釋是

Extension of {@link org.springframework.beans.factory.parsing.ReaderContext},
 * specific to use with an {@link XmlBeanDefinitionReader}. Provides access to the
 * {@link NamespaceHandlerResolver} configured in the {@link XmlBeanDefinitionReader}.

大概意思就是配合XmlBeanDefinitionReader使用的類擒滑。繼續(xù)往下走到:

@Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

到方法<b>doRegisterBeanDefinitions(root)</b> 進入:

protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    return;
                }
            }
        }

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

先創(chuàng)建<b>BeanDefinitionParserDelegate</b>

BeanDefinitionParserDelegate create

開始解析的三部曲

解析三部曲
  • 先看<b>preProcessXml</b>
    哈哈這真是個好方法 空實現(xiàn) 我喜歡腐晾,不過這算是解析的可擴展性 三部曲的步驟必需保證全 以免擴展需要
  • 在看<b>parseBeanDefinitions</b>
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
    
    這個方法就是最重要的啦,可以看到在遍歷節(jié)點丐一,其中方法<b>parseDefaultElement</b>

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
```
分了好幾種情況去解析 看開頭是import藻糖、alias、bean库车、beans這四種開頭的 標簽去解析 巨柒,我們就找個bean開頭的跟蹤下情況 其它的標簽也是一樣的道理。


bean開頭標簽的解析

進入方法

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

其中<b>parseBeanDefinitionElement</b>解析后獲得<b>BeanDefinitionHolder</b>其中過程中比較中要的一個類<b>GenericBeanDefinition</b>每個bean標簽都是解析成這樣的beandefinition了柠衍。獲得這個bean后對應(yīng)解析各種

GenericBeanDefinition 各個部分的解析

對應(yīng)的bean部件解析parseMetaElements洋满、parseLookupOverrideSubElements、parseReplacedMethodSubElements珍坊、parseConstructorArgElements牺勾、parsePropertyElements、parseQualifierElements 見名知意的方法沒有多大難度就是對應(yīng)的解析xml即可阵漏,解析完成 bean definition也就構(gòu)造完成了驻民。

  • 最后看<b>postProcessXml</b>
    哈哈這也真是個好方法 空實現(xiàn) 我喜歡,不過還是那句話這算是解析的可擴展性 三部曲的步驟必需保證全 以免擴展需要履怯。

BeanDefinition裝配過程基本完成 最后是裝配到了最先初始化的<b>DefaultListableBeanFactory</b>的各個map屬性中了回还。 好多東西很粗率的過了 后續(xù)補進。
不早了 go home!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叹洲,一起剝皮案震驚了整個濱河市柠硕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌运提,老刑警劉巖蝗柔,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異民泵,居然都是意外死亡诫咱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門洪灯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坎缭,“玉大人竟痰,你說我怎么就攤上這事√秃簦” “怎么了坏快?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長憎夷。 經(jīng)常有香客問我莽鸿,道長,這世上最難降的妖魔是什么拾给? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任祥得,我火速辦了婚禮,結(jié)果婚禮上蒋得,老公的妹妹穿的比我還像新娘级及。我一直安慰自己,他們只是感情好额衙,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布饮焦。 她就那樣靜靜地躺著,像睡著了一般窍侧。 火紅的嫁衣襯著肌膚如雪县踢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天伟件,我揣著相機與錄音硼啤,去河邊找鬼。 笑死斧账,一個胖子當著我的面吹牛谴返,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播其骄,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼亏镰,長吁一口氣:“原來是場噩夢啊……” “哼扯旷!你這毒婦竟也來了拯爽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钧忽,失蹤者是張志新(化名)和其女友劉穎毯炮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耸黑,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡桃煎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了大刊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片为迈。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葫辐,到底是詐尸還是另有隱情搜锰,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布耿战,位于F島的核電站蛋叼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剂陡。R本人自食惡果不足惜狈涮,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸭栖。 院中可真熱鬧歌馍,春花似錦、人聲如沸纤泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捏题。三九已至玻褪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間公荧,已是汗流浹背带射。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留循狰,地道東北人窟社。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像绪钥,于是被迫代替她去往敵國和親灿里。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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