3

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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="parent" class="com.example.spring.bean.collection.CollectionMerge">
        <property name="list">
            <list >
                <value>1</value>
                <value>2</value>
            </list>
        </property>
    </bean>
    
    <bean id="child" parent="parent" >
        <property name="list">
            <list merge="true">
                <value>1</value>
                <value>2</value>
            </list>
        </property>
    </bean>
</beans>

测试代码

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class MapMergeTests {
    
    @Autowired
    @Qualifier("child")
    private CollectionMerge service;
    
    @Test
    public void testSimpleProperties() throws Exception {
        assertEquals(4, service.getList().size());
    }
}

经过动手实验,证明Spring合并对于List类型,并无覆盖。接下来,我们看看其源码实现

MutablePropertyValues.mergeIfRequired()方法

private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) {
    Object value = newPv.getValue();
    //属性值是否可合并类
    if (value instanceof Mergeable) {
        Mergeable mergeable = (Mergeable) value;
        //属性值是否设置了merge=true
        if (mergeable.isMergeEnabled()) {
            //合并当前属性
            Object merged = mergeable.merge(currentPv.getValue());
            return new PropertyValue(newPv.getName(), merged);
        }
    }
    return newPv;
}

上面代码中Mergeable接口共有5个实现类ManagedList,ManagedArray,ManagedMap,ManagedSet,ManagedProperties

ManagedList.merge(Object parent)方法

public List<E> merge(Object parent) {
        //防御性抛异常
        if (!this.mergeEnabled) {
            throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'");
        }
        if (parent == null) {
            return this;
        }
        //不能合并非List类型集合
        if (!(parent instanceof List)) {
            throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]");
        }
        List<E> merged = new ManagedList<E>();
        //注意顺序,先增加父bean中的value值,所以文档中说父集合元素索引位置靠前
        merged.addAll((List) parent);
        merged.addAll(this);
        return merged;
    }

译注:我勒个去,为了找这段代码,洒家差点累吐血。由此可见,译者是非常用心的用生命去翻译文档。

合并限制
不能合并不同类型的集合,比如合并MapList(译注:上面的源码中有这样的代码,不知聪明的小读者是否注意到了)。如果你非得这么干,那么就会抛出个异常。merge属性必须指定给父-子继承结构bean中的子bean,如果指定给了父集合则无效,不会产生预期的合并结果。

强类型集合
Java5中出现了范型,所以可以给集合使用强类型限制。比如说,声明一个只含有String类型的Collection。若使用Spring 注入一个强类型Collection给一个bean,那么就可以利用Spring的类型转换特性 ,该特性能将给定的值转换成合适的类型值,然后赋值给你的强类型Collection

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}

看,飞碟

<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

foobean的accounts属性注入前,强类型集合Map<String,Float>的泛型信息通过反射获取。因此Spring的类型转换机制识别出元素的value类型将会转换为Float9.99,2.75,3.99将会转换成Float类型。

<h5 id=beans-null-element>Null值和空字串</h5>
Spring treats empty arguments for properties and the like as empty Strings. The following XML-based configuration metadata snippet sets the email property to the empty String value ("").
Spring对于属性的空参数转换为空字串。下面的XML片段,设置值email属性为空格字串("")

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上面的xml配置相当于这样的java代码

exampleBean.setEmail("")

<null/>元素处理null值:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面配置相当于

exampleBean.setEmail(null)

<h5 id='beans-p-namespace'>XML简写p命名空间</h5>
p-命名空间能让你使用bean元素属性替代内嵌property/>元素,用来描述属性值或者协作类。

Spirng支持命名空间扩展配置,命名空间基于XML Schema定义。本章讨论的beans配置格式定义在XML Schema文档中。然而,p命名空间并不是在XSD文件中,而是存在于Spring核心中。

下面XML片段解释了:1使用了标准XML,第2个使用p-命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="foo@bar.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>

上例中解释了,在bean定义中使用p-namespace设置email 属性。它告诉Spring这里有一个property声明。前面提到过,p-namespace 并不存在schema定义,所以p可以修改为其他名字。
译注,干活都是译者自己撰写用于验证,而非参考手册原版中的内容,之所以验证,是因为原版E文有看不懂的地方、或者翻译拿不准、或者就是闲来无事、或者就是为了凑篇幅,这些事儿得通过写代码验证搞定了
up fuck goods上干货

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ppp="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        
    <bean name="p-namespace" class="com.example.spring.bean.p.PNamespaceBean"
        ppp:email="foo@email.com"/>
</beans>

注意p命名空间的用法xmlns:ppp="http://www.springframework.org/schema/p"ppp:email="foo@email.com"

go on fuck goods继续干货

public class PNamespaceBean {
    private String email;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    
    
}

下面样例中,2个bean都引用了同一个bean

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--传统-->
    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <!--时髦-->
    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <!---->
    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

样例中2个p-namespace设置属性值,出现了一种新的格式声明引用。第一个bean中,使用了<property name="spouse" ref="jane"/>应用bean jane.第二个bean中,使用了p:spouse-ref="jane"做了相同的好事儿,此时,spouse是属性名,然而-ref表示这不是直接量而是引用另一个bean。

译注 好事儿,我小时候虽然做好事儿不留名,但是总能被发现,令我非常苦恼。我的妈妈常常揪着我的耳朵问:这又是你干的好事儿吧。

<h5 id="beans-c-namespace">c-namespace命名空间</h5>

p-namespace相似,c-namespace,是Spring3.1中新出的,允许行内配置构造参数,而不需使用内嵌的constructor-arg元素

c:namespace重构构造注入

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

c:namespace和p:使用了相同机制(ref后缀表示引用),通过names设置构造参数。因为它未定义在XSD schema中(但是存在于Spring内核中),所以需要先声明。

有一些情况比较特殊,不能识别或者看到构造参数(比如无源码且编译时无调试信息),此时可以求助于参数索引:

<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
注意

由于XML语法,索引标记需要_开头作为XML属性名称,而不能使用数字开头(尽管某些ID支持)

在实际中,构造注入(name匹配/类型匹配/索引匹配)是非常高效的,一般情况下,推荐使用 name匹配方式配置。

<h5 id='beans-compound-property-names'>复合属性</h5>
在设置bean属性时,可以使用复合或者内嵌属性,组件路径可以有多长写多长,除了最后一个属性,其他属性都不能为null。看下面的bean定义

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

bean foo有属性fred,fred有属性bob,bob有属性sammy,最后的sammy属性赋值"123"。在beanfoo构造后,fred属性和bob属性都不能为null否则抛异常NullPointerException

<h4 id='beans-factory-dependson'>使用depends-on</h4>

若bean是另个bean的依赖,通常是指该bean是另个bean的属性。在XML中通过<ref/>元素配置实现。然而,bean之间并不全是直接依赖。举个栗子,类中有个静态初始化需要出发,像注册数据库驱动这样的。depends-on属性能强制这些先决条件首先完成执行初始化,然后再去使用它(比如用于注入)。
下面的样例中,展示了使用depends-on来表达bean之间的依赖关系:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

也可以依赖多个bean,为depends-on属性值提供一个bean name列表,用逗号,空白,分号分隔。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
注意

单例bean中,depends-on属性既可以设定依赖的初始化时机,也可以相应的设定依赖的销毁时机。在bean被销毁之前,bean使用depdnds-on属性定义的依赖bean会首先被销毁。因此depends-on也能控制销毁顺序。

<h4 id='beans-factory-lazy-init'>延迟初始化</h4>
ApplicationContext的各种实现默认的初始化处理过程,都是尽早的创建、配置所有的单例bean。通常,这种预先实例化是非常好的,因为在配置的错误或者环境问题立刻就能暴露出来,而不是数小时甚至数天后才发现。若不需要此行为,可以通过设置lazy-initialized延迟加载来阻止预先初始化。lazy-initializedbean告诉Ioc容器,只有在第一次请求的时候采取初始化,而不是在启动容器时初始化。

在XML中,属性lazy-init控制<bean/>元素的初始化。

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

ApplicationContext解析上面的配置,在启动时,不会预先初始化这个标记为lazy的bean,为标记lazy的bean则会立刻初始化。

如果一个非延迟的单例bean依赖了lazy延迟bean,ApplicationContext会在启动时就创建lazy延迟bean,因为它必须满足单例bean依赖。延迟bean注入给单例bean,就意味着,它不会延迟加载的。

通过设置<beans/>元素的default-lazy-init属性,可以设置容器级别的延迟加载。看样例:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

<h4 id='beans-factory-autowire'>自动装配</h4>
Spring容器提供自动装配,用于组织bean之间的依赖。可以让Spring,通过检查ApplicationContext内容完成自动注入。自动装配有以下优点:

  • 自动装配能明显减少属性和构造参数的配置。(在这方面,还有其他的机制也能达到此目的,比如bean 模板,在后面的章节里有详细的讲解
  • 自动装配可扩展性非常好。比如,给类增加了依赖,无需修改配置,依赖类就能自动注入。因此,自动装配在开发期非常有用,在代码不稳定时,无需修改编码即可完成切换。

在XML中,设置<bean/>元素的autowire属性来设定bean的自动装配模式。自动装配有5种模式。可以选择任意一种,来设置bean的装配模式。(译注,这不是废话么,有5中模式,每种都能随便用,假设有一种不能用,那就是4种模式了么

Table 5.2. 自动装配模式

模式 解释
no (默认的)非自动装配。必须使用ref元素定义bean引用。对于大规模系统,推荐使用,明确的指定依赖易于控制,清楚明了。它在某种程度上说明了系统的结构。
byName 通过属性名称property name自动装配。Spring会查找与需要自动装配的属性同名bean。举个栗子,若在bean定义中设置了by name方式的自动装配,该bean有属性master(当然了,还得有个setMaster(..)写属性方法),Spring会查找一个名叫master的bean,并将它注入给master熟悉。
byType 若在容器中存在一个bean,且bean类型与设置自动装配bean的属性相同,那么将bean注入给属性。若与属性同类型的bean多于1个,则会抛出我们期待已久的致命异常,也就意味着这个bean也许不适合自动注入。若不存在匹配的bean,啥都不会发生;属性也不会设置,然后就没有然后了。
constructor Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.和byType模式类似,但是应用于构造参数。若在容器中不存在与构造参数类型相同的bean,那么接下来呢,抛异常呗,还能干啥?

byType或者constructor自动装配模式,可以装配arrays数组和范型集合。 这种个情况
下,容器内匹配类型的bean才会注入给依赖。若key类型是String,你也能自动装配强类型Maps。一个自动装配的Map,所有 匹配value类型的bean都会注入Map.value,此时,Map的key就是相应的bean的name。

可以结合自动装配和依赖检查,依赖检查会在装配完成后执行。

<h5 id='beans-autowired-exceptions'>自动装配的局限和缺点</h5>
自动装配最好是贯穿整个项目。若不是全部或大部分使用自动装配,而仅仅自动装配一两个bean定义,可能会把开发者搞晕。(译注,最可能的原因是,开发者对自动装配机制不熟悉,或者想不到项目中居然还使用了自动装配模式,当发生问题时,擦的,找都没地方找去,调试信息里只能看到经过横切织入事务代理的proxy)

总结局限和缺点:

  • 属性中和构造参数明确的依赖设置会覆盖自动装配。不能自动装配所谓的简单属性,比如原始类型,StringsClasses(简单属性数组)。这是源于Spring的设计。
  • 和明确装配相比,自动装配是不确切的。正如上面的列表中提到的,Spring谨慎避免匹配模糊,若真的匹配不正确,则导致错误发生,Spring 管理的对象之间的关系记录也变的不明确了。
  • 对于根据Spring容器生成文档的工具,装配信息将变的无用。
  • 容器内多个bean定义可能会匹配设置为自动装配的setter方法或者构造参数的类型。对于arrays,collections,或者maps,这不是个问题。然而对于期望单一值的依赖,这种歧义将不能随意的解决。如果发现多个类型匹配,将会抛出异常 .

在后面的场景中,给你几条建议:

  • 放弃自动装配,使用明确装配
  • 避免通过在bean定义中设置autowire-candidate属性为false的方式来设置自动装配,下一章节会讲
  • 通过设置<bean/>袁术的primary属性为true来指定单个bean定义作为主候选bean。
  • 使用基于注解的配置实现更细粒度的控制,参看Section 5.9, “Annotation-based container configuration”.

<h5 id='beans-factory-autowire-candidate'>排除自动装配bean</h5>
在每个bean的设置中,你可以排除bean用于自动装配。XML配置中,设置<bean/>元素的autowire-candidate属性为false;容器将不使用该bean自动装配。(包括注解配置,像@Autowired

  • 使用对bean名字进行模式匹配来对自动装配进行限制。其做法是在<beans/>元素的'default-autowire-candidates'属性中进行设置。比如,将自动装配限制在名字以'Repository'结尾的bean,那么可以设置为"Repository“。对于多个匹配模式则可以使用逗号进行分隔。注意,如果在bean定义中的'autowire-candidate'属性显式的设置为'true' 或 'false',那么该容器在自动装配的时候优先采用该属性的设置,而模式匹配将不起作用。译注这一段翻译是从网上copy过来的,我勒个擦,得赶紧睡觉去了*

这些设置非常有用。但是这些被排除出自动注入的bean是不会自动注入到其他bean,但是它本身是可以被自动注入的。

<h4 id='beans-factory-method-injection'>方法注入</h4>
一般情况,容器中的大部分的bean都是单例的。当单例bean依赖另一个单例bean,或者一个非单例bean依赖另个非单例bean是,通常是将另一个bean定义成其他bean的属性。当bean的生命周期不同时,那么问题来了。假设单例bean A依赖非单例bean(prototype) B,也许会在每个方法里都需要B。容器之创建了一个单例bean A,因此只有一次将B注入的机会。A调用B,需要很多B的实例 ,但是容器不会这么干。

解决办法是放弃一些IoC控制反转。令A实现接口ApplicationContextAware,此时A能够感知容器,即获取ApplicationContext,每次当A调用B时,调用容器的getBean("B")方法用以创建B的实例。看样例:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

并不推荐上面的做法,因为业务代码耦合了Spring 框架。方法注入,是SpringIoc容器的高级特性,能够简洁的满足此场景。

想要了解更多的方法注入,参看此博客

<h5 id='beans-factory-lookup-method-injection'>查找式方法注入</h5>
查找式是指,容器为了覆盖它所管理的bean的方法,在容器范围内查找一个bean作为返回结果。通常是查找一个原型(prototype)bean,就像是上面章节中提到过的场景。Srping框架,使用CGLIB类库生成动态子类的字节码技术,覆盖方法。

注意

为了能让动态子类能运行,其父类不能是final类,被覆盖的方法也不能是final。还有,你得自己测试父类是否含有abstract方法,如果有,需要你提供默认实现。最后,被方法注入的对象不能序列化。Spring 3.2以后,不需要CGLIB的类路径了,因为CGLIB被打包入了org.springframework 包,和Spring-core 这个jar包在一起了。既是为了方便也是为了避免CGLIB包与应用中用到的CGLIB包冲突。

来看看前面提到的CommandManager类的代码片段,Spring容器会动态的覆盖createCommand()方法的实现。这样CommandManager类就不会依赖任何Spring API了。下面是修改过后的

package fiona.apple;

// 不再有 Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        //
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    //okay....但是方法实现在哪里?
    protected abstract Command createCommand();
}

在含有被注入方法的类中(像CmmandManager类),被注入方法需要使用以下签名

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

动态生成子类会实现抽象方法。若该方法不是抽象的,动态生成自来则会重写在源类中的方法。配置如下:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

commandManager类调用createCommand方法时,动态代理类将会被识别为commandManager返回一个command bean的实例。将commandbean设置成prototype,一定要小心处理。若被设置成了singleton,每次调用将返回同一个commandbean。

注意

感兴趣的小读者也找找ServiceLocatorFactoryBean类(在org.springframework.beans.factor.config包)来玩玩。使用ServiceLocatorFactoryBean类的处理手法和另一个类ObjectFactoryCreatingFactoryBean类似,但是ServiceLocatorFactoryBean类与虚拟指定你自己的lookup接口(查找接口),这与Spring指定lookup接口略有不同。详情参看这些类的javadocs

译注,今天加班晚了点,到家时23:30了,可是今天还没翻。进了家门,打开电脑,翻一小节再说,要不估计睡不着觉了。对于我自己的毅力,我还是有相当的认识的,比如:无论咳的多么严重,都能坚持抽烟,由此可见一斑。以上是玩笑。我的意志力并不强,但是意志薄弱也有意志薄弱的积极的正面的意义,比如我养成了每天翻点东西的习惯,哪怕就是再困、再饿、再累,也得翻译一下,因为要是不翻译的话,我就得跟自己的习惯作斗争了,准确的说是和自己斗争,而我却又没有与自己斗争的念想,我根本打不过我自己,就这样,我又翻了一小节

<h5 id="beans-factory-arbitrary-method-replacement">任意方法替换</h5>
还有一种方法注入方式,不如lookup method注入方式好用,可以用其他bean方法实现替换受管理的bean的任意方法。你可以跳过本节,当真的需要时再回来也是可以的。

在xml配置中,设置replaced-method元素,就可用其他实现来替换已经部署的bean中存在的方法实现。考虑下面的类,有一个我们要重写的方法computeValue

public class MyValueCalculator {

    public String computeValue(String input) {
        // balbalba
    }

    // 其他方法...

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容