0x01.IoC容器和Bean

[TOC]

主要内容整理自Spring官方reference

Ioc容器

一个对象定义它的依赖(也就是它所协作的对象),只能通过构造器参数(依赖作为构造器参数传入)、工厂方法的参数,或者在此对象实例被构造或从工厂方法返回后在对象实例上设置属性(set方法参数)来建立依赖关系。Ioc容器在创建Bean时将这些依赖注入进去,也就是不需要显式通过构造器传参,setter等方式传入一个对象,容器帮我们把这件事完成了,这个过程的依赖设置从对象自身而言是被动的,是容器将依赖项“注入”到对象中,所以这翻转了依赖建立的过程,所以才成为依赖反转。

Spring容器是Spring的核心,也是依赖注入的基础。容器容纳了应用所使用的bean对象,并为这些对象根据依赖关系装配属性。容器的概念相当于Spring环境,置入容器中的对象,才会被spring框架所管理和操作,依赖注入也需要基于容器这个环境才能让Spring知道注入什么以及注入到何处甚至如何注入。容器是SPring管辖的地方,一系列对象想要被Spring管理(或者说要使用Spring提供的特性),需要先“主动”地注册到容器中,然后才能借助Spring的力量“被动”的注入依赖关系。

对象主动注册到容器的方式,就涉及到配置,即配置对象由spring管理,配置对象之间的依赖关系,甚至还有配置对象被创建的形式和被销毁的方法。但是只是配置,交给Spring容器,此后对象的声明周期由容器来管理,而非自行编写的代码来管理。

将应用对象和依赖关系配置到容器,就需要使用容器提供的具体工具。借助工具才能将对象放入容器和让容器了解对象之间的依赖。容器提供了两个实现,一个是基本的BeanFactory,另一个是基于BeanFactory的ApplicationContext,即应用上下文。

ApplicationContext和BeanFactory

  • BeanFactory是基础类型的IoC容器,提供完整的IoC服务支持。如果没有特别指定,默认使用的是延迟加载的策略:即为只有当客户端对象需要访问容器中的某个受管对象的时候,容器才会对该受管的对象进行初始化以及依赖注入操作。所以相对来说,容器的初期启动较快,所需要的资源也少。
  • ApplicationContext是在BeanFactory基础上构建的,即ApplicationContext实际上是BeanFactory的子类,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,还提供了其他高级的特性。ApplicationContext所管理的对象,在该类型的容器启动之后,默认全部初始化并绑定完成(也可以配置lazy-init来延迟加载),所以相对于BeanFactory,它需要更多的系统资源,且初期的启动也会慢一些。

简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多的企业特定功能如AOP功能、Web应用、从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者等。ApplicationContext是BeanFactory的一个完整的超集。因为ApplicationContext具备BeanFactory的所有功能,且还有更多BeanFactory不具备的高级功能,一般除非在嵌入式系统中(资源紧张)会使用原始的BeanFactory,一般都是使用ApplicationContext的实现类。

关于BeanFactory:

BeanFactory只是一个接口,且它只定义了如何访问容器内管理的bean的方法,各个BeanFactory的实现类负责具体Bean的注册以及管理工作。

DefaultListableBeanFactory就是一个比较通用的BeanFactory的实现类,它除了间接实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,BeanDefinitionRegistry接口才是在BeanFactory的实现中担当Bean注册管理的角色,它的接口定义抽象了Bean的注册逻辑。

BeanDefinitionRegistry接口依赖于BeanDefinition接口,这三个接口在IoC 的实现中的角色是这样的:

BeanDefinition接口定义了每一个受管对象,BeanDefinitionRegistry负责把每一个受管对象注册到容器中,BeanFactory接口负责提供访问这些受管对象的方法。

BeanDefinition:
每一个受管对象,在容器中都会有一个对应的BeanDefinition实例,该实例负责保存对象的所有必要信息,包括对象的class类型,是否抽象等,当客户端想BeanFactory请求相应对象时,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。

ApplicationContext接口有以下几种常见实现来加载bean的定义到容器中。
常用的应用上下文:

  • AnnotationConfigApplicationContext:从一个或多个Java的配置类中加载Spring应用上下文定义。
  • AnnotationConfigWebApplicationContext:从一个或多个Java的配置类中加载Spring Web应用上下文定义。
  • ClassPathXMLApplicationContext: 从classpath的一个或多个xml文件来加载上下文定义。
  • FileSystemXMLApplicationContext: 从文件系统下的一个或多个xml文件加载上下文定义。
  • XmlWebApplicationContext: 从web应用下的一个或多个xml文件加载上下文定义。

从以上的上下文实现类来看,可以反向得出Spring容器的配置方式可以有两种:Java类配置和xml文件配置。实际上Java类来配置可以分为编码配置和注解来配置(注解也可以使用xml文件来开启),实际上不同的配置方式对于容器来说是通用的,并不是独立的配置,而且不同方式直接可以通过import汇总到一个地方。

关闭容器的方法

在非web环境下,可以通过以下方式关闭一个容器:

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

容器扩展点

BeanPostProcessor定制Bean

If you want to implement some custom logic after the Spring container finishes instantiating, configuring, and initializing a bean, you can plug in one or more BeanPostProcessor implementations.

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

An ApplicationContext automatically detects any beans that are defined in the configuration metadata which implement the BeanPostProcessor interface. The ApplicationContext registers these beans as post-processors so that they can be called later upon bean creation. Bean post-processors can be deployed in the container just like any other beans.

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测 (@Component) ,但也可以通过使用addBeanPostProcessor方法以编程方式将它们注册到一个ConfigurableBeanFactory中。当需要在注册之前评估条件逻辑,或者甚至在层次结构的上下文中复制bean post处理器时,这可能是有用的。但是请注意,以编程方式添加的BeanPostProcessor不尊重有序接口。这里的注册顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor总是在通过自动检测注册的处理器之前进行处理,而不考虑任何显式排序。

BeanPostProcessor的实现是作用于所有Bean上的。实现BeanPostProcessor接口的类是特殊的,并被容器区别对待。它们直接引用的所有BeanPostProcessor和bean都在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序的方式注册所有的BeanPostProcessor并应用到容器中的所有进一步的bean。因为AOP自动代理是作为一个bean - postprocessor本身实现的,所以它们直接引用的bean和bean都不适合自动代理,因此也没有将方面编织到它们中。

http://www.cnblogs.com/smile361/p/5556697.html

BeanFactoryPostProcessor自定义配置元数据

Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并在容器实例化除BeanFactoryPostProcessor以外的任何bean之前可能对其进行更改。
可以配置多个BeanFactoryPostProcessor,顺序有Ordered接口控制。

如果要更改实际的bean实例(即,从配置元数据创建的对象),则需要使用BeanPostProcessor(如上面BeanPostProcessor定制bean中所述)。 虽然技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,从而违反标准的容器生命周期。 这可能会导致负面影响,例如绕过bean后处理。

此外,BeanFactoryPostProcessors的范围是每个容器。 这仅在您使用容器层次结构时才相关。 如果在一个容器中定义BeanFactoryPostProcessor,它将仅应用于该容器中的bean定义。 BeanFactoryPostProcessors不会在另一个容器中对一个容器中的Bean定义进行后处理,即使两个容器都是同一层次结构的一部分。

bean工厂后处理器在ApplicationContext中声明时自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。 例如,也可以使用自定义BeanFactoryPostProcessor来注册自定义属性编辑器。

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
                                throws BeansException;
}
  • 示例,占位符的解析
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

或者

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

FactoryBean自定义实例化逻辑

Implement the org.springframework.beans.factory.FactoryBean interface for objects that are themselves factories.

FactoryBean接口是Spring IoC容器实例化逻辑的可插拔点。 如果你有一个复杂的初始化代码,用Java表示,而不是(可能)冗长的XML,你可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

public interface FactoryBean<T> {
    T getObject() throws Exception; 
    Class<?> getObjectType(); 
    boolean isSingleton();
}

当您需要向容器询问实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,使用&符号(&)作为bean的id前缀。 因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品; 而,调用getBean(“&myBean”)会返回FactoryBean实例本身。

Bean

Bean实际上就是对象,被Spring管理的对象统称为Bean。一个Spring IOC容器管理一个或多个Bean。
这些Bean的创建依赖于所提供的配置元数据(如xml、Java类配置、注解配置)。在容器内部,bean的定义包括他们的依赖关系,对象的作用域等等相关的bean的定义,都被抽象为BeanDefinition对象。包括:

  • 包限定的类名
  • bean的行为配置项(scope、生命周期回调等等)
  • 依赖对象的引用
  • 其他设置到新建对象的配置

这些元数据转换成一系列标记每个bean的属性:
class、name、scope、constructor arguments、properties、autowiring mode、lazy-initialization mode、initialization method、destruction method

除了bean定义中包含关于如何创建特定bean的信息之外,ApplicationContext实现还允许用户对在容器外创建的现有对象进行注册。这是通过通过方法getBeanFactory()访问ApplicationContext的BeanFactory来完成的,它返回BeanFactory实现DefaultListableBeanFactory。DefaultListableBeanFactory通过方法registerSingleton()和registerBeanDefinition()来支持这种注册。

Bean的命名

Every bean has one or more identifiers. These identifiers must be unique within the container that hosts the bean. A bean usually has only one identifier, but if it requires more than one, the extra ones can be considered aliases。
在基于xml的配置中,可以指定一个Id或者name来标识一个bean,id不能重复。如果要向bean引入其他别名,还可以在name属性中指定它们,用逗号(,)、分号(;)分隔。

不强制为bean提供名称或id。如果没有显式提供名称或id,容器将为该bean生成唯一的名称。但是,如果您希望按名称引用该bean,通过使用ref元素或服务定位符样式查找,您必须提供一个名称。使用内部bean和autowire可以不显式提供名称。

  • Bean Naming Conventions(惯例)

使用类名的首字母小写形式。另外使用组件自动扫描功能且没有注明bean的name时,Spring会依据此Convention为bean生成name。

实例化beans

实例化的方式是可以配置的,有以下几种:

  • 默认情况下,是调用无参构造器(所以要注意提供无参构造器)来实例化bean的。
  • factory-method模式,指定bean所在类的内部静态工厂方法来实例化,在xml中指定factory-method即可:
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
  • 使用其他bean的工厂方法
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService" factory-bean="serviceLocator" 
      factory-method="createClientServiceInstance"/>

Bean的生命周期

在传统的java应用中,bean的生命周期很简单,使用Java关键字new进行实例化,然后bean就可以使用了。一旦bean不再被使用,就会被垃圾回收机制自动回收。

在Spring容器中的bean的生命周期,是托付给Spring进行管理的,包括bean对象的实例化到对象死亡,都由容器来控制。

在Spring容器的管理下,一个bean在准备就绪之前,bean工厂会执行若干步骤,如下归纳:


image
  1. Spring对bean进行实例化
  2. Spring将值和bean的引用注入到bean对应的属性中;
  3. 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法;
  4. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将Beanfactory容器实例注入;
  5. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传进来;
  6. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法;
  7. 如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方法也会调用;
  8. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法;
  9. 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直停留在应用上下文中,直到应用上下文被销毁;
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()方法,同样的,如果bean使用destory-method声明了销毁方法,该方法也会被调用;

所以可以通过实现上面提到的接口来介入Bean的生命周期,提供各种回调函数来附加功能。

其他一些aware接口:

Name Injected Dependency
ApplicationContextAware Declaring ApplicationContext
ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext
BeanClassLoaderAware Class loader used to load the bean classes.
BeanFactoryAware Declaring BeanFactory
BeanNameAware Name of the declaring bean
BootstrapContextAware Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts
LoadTimeWeaverAware Defined weaver for processing class definition at load time
MessageSourceAware Configured strategy for resolving messages (with support for parametrization and internationalization)
NotificationPublisherAware Spring JMX notification publisher
PortletConfigAware Current PortletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext
PortletContextAware Current PortletContext the container runs in. Valid only in a web-aware Spring ApplicationContext
ResourceLoaderAware Configured loader for low-level access to resources
ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext
ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext

Bean的scope

Scope Description
singleton (Default)每个容器中的每个bean都是单例的
prototype 对每个对bean的请求创建一个bean.
request Scopes a single bean definition to the lifecycle of a single HTTP request; that is, each HTTP request has its own instance of a bean created off the back of a single bean definition.
session Scopes a single bean definition to the lifecycle of an HTTP Session.
globalSession Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a Portlet context.
application Scopes a single bean definition to the lifecycle of a ServletContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket.

The request, session, globalSession, application, and websocket scopes are only available if you use a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these scopes with regular Spring IoC containers such as the ClassPathXmlApplicationContext, an IllegalStateException will be thrown complaining about an unknown bean scope.

  • singleton


  • prototype


配置scope

  • xml
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
  • annotation
@RequestScope
@Component
public class LoginAction {
    // ...
}

Scoped beans as dependencies

如果您想将一个HTTP请求作用域bean注入一个更长的作用域的另一个bean,您可以选择将一个AOP代理注入到作用域bean的位置。也就是说,您需要注入一个代理对象,它公开与作用域对象相同的公共接口,但是也可以从相关的范围(比如HTTP请求)中检索真正的目标对象,并将方法调用委托给真正的对象。
例如:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

一个singleton scope的userManager依赖一个session scope的userPreferences,userManager之后实例化一次,其依赖项也只会注入一次,而userPerference是每次seession创建一个新的,但是userManager使用的userPerference一直只能是最初注入的那个,显然这个是违反需求的。

因此就需要容器能创建这样一个对象,该对象公开与UserPreferences类(理想情况下是UserPreferences实例)完全相同的公共接口,该对象可以从scope机制(HTTP请求、会话等)获取真正的UserPreferences对象。 也就是每次要使用短生命周期的bean依赖时,能借助这个表明(代理)bean获取真正的bean。

容器将这个代理对象注入到userManager bean中,后者不知道这个UserPreferences引用是一个代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是调用代理上的方法。然后代理从HTTP会话(在本例中)获取真正的UserPreferences对象,并将方法调用委托给检索到的真正的UserPreferences对象。

这个代理的生成,需要借助AOP的功能,配置上只需要加一个<aop:scoped-proxy/>(xml):

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

依赖注入

注入的数据类型

  • bean(ref)
  • 字面量(value)

注入的形式

  • 构造器DI(constructor-arg)
<beans>
    <bean id="foo" class="x.y.Foo">
    <!--按参数顺序-->
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>
    <!--指定参数类型-->
    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg type="int" value="7500000"/>
        <constructor-arg type="java.lang.String" value="42"/>
    </bean>
    <!--指定参数位置index-->
    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>
    <!--指定参数名字-->
    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/>
    </bean>
</beans>

The ApplicationContext supports constructor-based and setter-based DI for the beans it manages. It also supports setter-based DI after some dependencies have already been injected through the constructor approach. You configure the dependencies in the form of a BeanDefinition, which you use in conjunction with PropertyEditor instances to convert properties from one format to another.
  • setter DI(property)

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class.

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

Circular dependencies

如果出现A依赖B,B又依赖 A的环形依赖,容器会检测到并会抛出BeanCurrentlyInCreationException。

自动化装配

即基于注解扫描的自动化方案。

  • Autowiring modes
Mode Explanation
no (Default) No autowiring. Bean references must be defined via a ref element. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system.
byName Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name, and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master, and uses it to set the property.
byType Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set.
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.
  • autowiring的优点
  1. 自动装配可以显著减少指定属性或构造函数参数的需要。
  2. 自动化更新依赖关系,无需维护xml文件
  • autowiring的缺点
  1. propertyconstructor-arg设置中显式的依赖总是覆盖自动连接。您不能自动连接所谓的简单属性,如原语、字符串和类(以及此类简单属性的数组)。
  2. 依赖关系的表达没有显示配置那么明确,尽管Spring谨慎地避免猜测可能会产生意想不到的结果,但Spring管理对象之间的关系不再明确地记录下来。
  3. 可能从Spring容器生成文档的工具可能无法获得连接信息。
  4. 容器内的多个bean定义可能与setter方法或构造器参数指定的类型相匹配,以便自动连接。需要明确唯一性,否则会抛出异常。

Method injection

在大多数应用程序场景中,容器中的大多数bean都是单例的。当单例bean需要与另一个单例bean协作时,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A上的每个方法调用上,容器只创建一次单例bean A,因此只有一次机会设置属性。容器不能在每次需要bean B时为bean A提供一个新的实例。

解决办法是放弃一些控制反转(还有Bean Scope中协调不同生命周期的注入使用AOP代理的方式)。您可以通过实现applicationcontextAware接口来让bean知道容器,并且通过在每次bean A需要的时候,让一个getBean(“B”)调用容器请求(一个典型的新)bean实例。下面是这种方法的一个例子:

// 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;
    }
}

参考资料

[1] Spring实战

[2] Spring Framework Reference Documentation

[3] Spring 框架简介Spring AOP 和 IOC 容器入门

[4] Spring之FactoryBean

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

推荐阅读更多精彩内容

  • 以下操作基于MySQL 5.7.8 更改root密码 service mysqld stop停用mysql服务后,...
    johnnyOnRoad阅读 514评论 0 1
  • 第二幅,喜欢就画,不管好坏,刚开始觉得很糟糕,所以没过程图,勉强完成了。先这样吧!慢慢练习。
    zero最好的开始阅读 545评论 1 6