Spring源碼解析(六)-解析bean標簽

Spring版本

5.2.5.RELEASE

參考

學習Spring源碼(二)自定義標簽屬性

源碼解讀

在Spring源碼中奖年,bean標簽的解析交由processBeanDefinition處理:

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 解析bean,如果解析成功植捎,獲得持有name和alias的BeanDefinitionHolder狡蝶,即BeanDefinitionHolder對象咸这,解析失敗返回null
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            // bean支持定義屬性和內(nèi)部嵌套自定義標簽,進行這一部分的處理
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // 注冊bean
                // 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);
            }
            // 通知監(jiān)聽器注冊事件
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

該方法主要做了以下三點:

  • 解析bean標簽
  • 解析bean標簽的自定義屬性和bean標簽下嵌套的自定義標簽
  • 注冊bean拱雏,并且在注冊成功之后通知監(jiān)聽器注冊事件

1. parseBeanDefinitionElement

    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
        return parseBeanDefinitionElement(ele, null);
    }

繼續(xù)交由重載方法:

    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        // 將name屬性值進行切割棉安,塞進別名集合
        List<String> aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        // 如果沒有聲明id屬性,將上面解析出來的第一個別名當成beanName
        String beanName = id;
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isTraceEnabled()) {
                logger.trace("No XML 'id' specified - using '" + beanName +
                        "' as bean name and " + aliases + " as aliases");
            }
        }

        if (containingBean == null) {
            // 檢查beanName和aliases中的元素是否唯一
            checkNameUniqueness(beanName, aliases, ele);
        }

        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        // 生成唯一beanName
                        beanName = BeanDefinitionReaderUtils.generateBeanName(
                                beanDefinition, this.readerContext.getRegistry(), true);
                    }
                    else {
                        // 生成唯一beanName
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // Register an alias for the plain bean class name, if still possible,
                        // if the generator returned the class name plus a suffix.
                        // This is expected for Spring 1.2/2.0 backwards compatibility.
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (logger.isTraceEnabled()) {
                        logger.trace("Neither XML 'id' nor 'name' specified - " +
                                "using generated bean name [" + beanName + "]");
                    }
                }
                catch (Exception ex) {
                    error(ex.getMessage(), ele);
                    return null;
                }
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            // 構(gòu)建BeanDefinitionHolder對象铸抑,返回
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

邏輯在于構(gòu)建一個包含beanDefinition贡耽、beanName和aliasesArray的BeanDefinitionHolder對象,因此鹊汛,整個過程也就可以拆分成三個部分來查看:

  • 生成aliasesArray
  • 解析獲取beanDefinition對象
  • 生成beanName

1.1 生成aliasesArray

        // 將name屬性值進行切割蒲赂,塞進別名集合
        List<String> aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

獲取到name屬性的值,之后切割并組裝成List類型的aliases刁憋,在構(gòu)建BeanDefinitionHolder的時候滥嘴,將List類型的aliases轉(zhuǎn)化為數(shù)組類型并作為構(gòu)造參數(shù):

String[] aliasesArray = StringUtils.toStringArray(aliases);

1.2 解析獲取beanDefinition對象

核心方法在于:

AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

查看parseBeanDefinitionElement方法:

    @Nullable
    public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, @Nullable BeanDefinition containingBean) {

        // ParseState的定義生命信息如下:
        // Simple {@link LinkedList}-based structure for tracking the logical position during a parsing process.
        // 看起來,parseState是用于追蹤bean解析過程中的邏輯位置
        // 追蹤parseState的使用方式至耻,應該是解析過程報錯的情況下可以輕易找到當前正在被解析的bean吧
        this.parseState.push(new BeanEntry(beanName));

        String className = null;
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }

        try {
            // createBeanDefinition用于創(chuàng)建一個AbstractBeanDefinition對象若皱,且將className進行加載,設置進該對象的beanClass屬性尘颓,同時將parent設置進去parentName屬性
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);

            // 解析bean的各種屬性走触,比如autowire,lazyinit疤苹,設置到bd中
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            // 解析description子元素內(nèi)容互广,getChildElementValueByTagName可以從ele中找到符合DESCRIPTION_ELEMENT的第一個子元素的內(nèi)容文本值
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            // 解析meta子元素
            parseMetaElements(ele, bd);
            // 解析lookup-method子元素
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            // 解析replace-method子元素
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

            // 解析construct-arg子元素
            parseConstructorArgElements(ele, bd);
            // 解析property子元素
            parsePropertyElements(ele, bd);
            // 解析qualifier子元素
            parseQualifierElements(ele, bd);

            bd.setResource(this.readerContext.getResource());
            bd.setSource(extractSource(ele));

            return bd;
        }
        catch (ClassNotFoundException ex) {
            error("Bean class [" + className + "] not found", ele, ex);
        }
        catch (NoClassDefFoundError err) {
            error("Class that bean class [" + className + "] depends on not found", ele, err);
        }
        catch (Throwable ex) {
            error("Unexpected failure during bean definition parsing", ele, ex);
        }
        finally {
            this.parseState.pop();
        }

        return null;
    }

邏輯可以歸納為以下幾個步驟:

  • 將當前解析的beanName壓入parseState棧中,方便解析過程報錯的時候定位到當前被解析元素
  • 獲取class屬性和parent屬性痰催,調(diào)用createBeanDefinition方法構(gòu)建AbstractBeanDefinition對象兜辞,在調(diào)用createBeanDefinition方法的時候,對className進行了加載
  • 解析bean標簽的屬性
  • 解析bean標簽下的子標簽
  • 解析完畢夸溶,從parseState中退棧

1.2.1 createBeanDefinition

    public static AbstractBeanDefinition createBeanDefinition(
            @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

        GenericBeanDefinition bd = new GenericBeanDefinition();
        bd.setParentName(parentName);
        if (className != null) {
            if (classLoader != null) {
                bd.setBeanClass(ClassUtils.forName(className, classLoader));
            }
            else {
                bd.setBeanClassName(className);
            }
        }
        return bd;
    }

可以看到逸吵,該方法構(gòu)建一個GenericBeanDefinition對象,之后如果className和classLoader不為空缝裁,則加載類扫皱,并設置為beanClass屬性足绅,否則將className設置進beanClassName屬性

1.2.2 parseBeanDefinitionAttributes

解析bean標簽的眾多屬性,包含:

  • scope
  • abstract
  • lazy-init
  • autowire
  • depends-on
  • autowire-candidate
  • primary
  • init-method
  • destroy-method
  • factory-method
  • factory-bean
    代碼邏輯較為簡單韩脑,不贅述

1.2.3 生成beanName

首先獲取id屬性作為beanName:

String beanName = id;

如果沒有生命id屬性氢妈,則使用別名的第一個元素作為beanName:

        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isTraceEnabled()) {
                logger.trace("No XML 'id' specified - using '" + beanName +
                        "' as bean name and " + aliases + " as aliases");
            }
        }

在上述beanName的基礎上生成一個唯一的beanName:

                    if (containingBean != null) {
                        // 生成唯一beanName
                        beanName = BeanDefinitionReaderUtils.generateBeanName(
                                beanDefinition, this.readerContext.getRegistry(), true);
                    }
                    else {
                        // 生成唯一beanName
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // Register an alias for the plain bean class name, if still possible,
                        // if the generator returned the class name plus a suffix.
                        // This is expected for Spring 1.2/2.0 backwards compatibility.
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }

2. decorateBeanDefinitionIfRequired

bean標簽支持自定義屬性,我們簡單使用一個demo演示該部分內(nèi)容:

2.1 demo

2.1.1 Student

public class Student {

    private String id;

    private String name;

    private String desc;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

2.1.2 student.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.customAttribute.com/schema/student-desc" targetNamespace="http://www.customAttribute.com/schema/student-desc" elementFormDefault="qualified">

    <xsd:attribute name="student-desc" type="xsd:string"/>

</xsd:schema>

2.1.3 StudentNamespaceHandler

public class StudentNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionDecoratorForAttribute("student-desc", new StudentBeanDefinitionDecorator());
    }
}

2.1.4 StudentBeanDefinitionDecorator

public class StudentBeanDefinitionDecorator implements BeanDefinitionDecorator {
    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
        definition.getBeanDefinition().getPropertyValues().add("desc", ((Attr)node).getValue());
        return definition;
    }
}

2.1.5 spring.handlers

http\://www.customAttribute.com/schema/student-desc=com.kungyu.custom.element.StudentNamespaceHandler

2.1.6 spring.schemas

http\://www.customAttribute.com/schema/student.xsd=META-INF/student.xsd

2.1.7 spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:student="http://www.customAttribute.com/schema/student-desc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.kungyu.custom.element.Student" student:student-desc="student desc">
        <property name="id" value="id"/>
        <property name="name" value="name"/>
    </bean>
</beans>

2.1.8 測試

public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.getId() + "----" + student.getName() + "---" + student.getDesc());
    }
}

2.1.9 結(jié)果

bean自定義屬性結(jié)果

spring.xml中段多,我們并沒有為idstudentbean指定desc屬性的值首量,而是通過自定義屬性student-desc进苍,之后交由在StudentNamespaceHandler中注冊的StudentBeanDefinitionDecorator進行處理加缘,在StudentBeanDefinitionDecoratordecorate中給BeanDefinition定義studentdesc屬性的值。

2.2 源碼解讀

    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

        BeanDefinitionHolder finalDefinition = originalDef;

        // 解析bean標簽里面的自定義屬性
        // Decorate based on custom attributes first.
        NamedNodeMap attributes = ele.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }

        // 解析嵌套在bean標簽里面的自定義標簽
        // Decorate based on custom nested elements.
        NodeList children = ele.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
            }
        }
        return finalDefinition;
    }

倆個步驟:

  • 解析bean標簽的自定義屬性
  • 解析bean標簽內(nèi)部嵌套的自定義標簽
    以上倆個步驟都通過decorateIfRequired方法實現(xiàn):
    public BeanDefinitionHolder decorateIfRequired(
            Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

        String namespaceUri = getNamespaceURI(node);
        if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
            // 同解析自定義標簽一樣觉啊,獲取自定義的handler
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler != null) {
                // 調(diào)用自定義handler的decorate方法
                BeanDefinitionHolder decorated =
                        handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
                if (decorated != null) {
                    return decorated;
                }
            }
            else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
                error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
            }
            else {
                // A custom namespace, not to be handled by Spring - maybe "xml:...".
                if (logger.isDebugEnabled()) {
                    logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
                }
            }
        }
        return originalDef;
    }

該方法很明顯邏輯在于:

  • 查找到自定義的handler
  • 通過handler拣宏,調(diào)用自定義的decorate方法

3. registerBeanDefinition

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        // 注冊,將beanName和beanDefinition寫入緩存map中
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // 別名注冊
        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

邏輯都比較簡單杠人,就直接貼代碼了

3.1 registerBeanDefinition

        @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                // 對methodOverrides進行校驗
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        // 獲取緩存
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        // 如果緩存存在
        if (existingDefinition != null) {
            // 如果不允許BeanDefinition覆蓋勋乾,拋出異常
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            else if (existingDefinition.getRole() < beanDefinition.getRole()) {
                // 如果緩存的BeanDefinition角色比注冊的BeanDefinition的角色d低
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            existingDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else if (!beanDefinition.equals(existingDefinition)) {
                // 如果倆個BeanDefinition不同
                if (logger.isDebugEnabled()) {
                    logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            // 進行覆蓋
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            // 如果不存在緩存
            // 判斷是否已經(jīng)開始Bean創(chuàng)建的進程
            if (hasBeanCreationStarted()) {
                // 如果已開始,進行同步控制
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    // 寫入緩存
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    // 將beanName加入beanDefinitionNames
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    removeManualSingletonName(beanName);
                }
            }
            else {
                // Still in startup registration phase
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                removeManualSingletonName(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (existingDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }

核心其實就是將BeanDefinition寫入緩存中:

this.beanDefinitionMap.put(beanName, beanDefinition);

3.2 registerAlias

該方法在《Spring源碼解析(五)-解析alias標簽》已經(jīng)講述過了嗡善,感興趣可以戳

最后編輯于
?著作權歸作者所有,轉(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
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伪嫁,“玉大人领炫,你說我怎么就攤上這事≌趴龋” “怎么了帝洪?”我有些...
    開封第一講書人閱讀 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)自己被綠了颜启。 大學時的朋友給我發(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