SpringMVC原理和啟動流程

SpringMVC是一款Web MVC框架聚蝶。 它跟Struts框架類似箩溃,是目前主流的Web MVC框架之一

一、SpringMVC的由來

早期 Java Web 的開發(fā)中,把顯示層堵漱、控制層、數(shù)據(jù)層的操作全部交給 JSP 或者 JavaBean 來進(jìn)行處理遗增,我們稱之為 Model1:

mvc1.png

然而出現(xiàn)了很多的弊端执解,比如

  • JSP 和 Java Bean 之間嚴(yán)重耦合,Java 代碼和 HTML 代碼也耦合在了一起
  • 要求開發(fā)者不僅要掌握 Java 哲身,還要有高超的前端水平
  • 前端和后端相互依賴辩涝,前端需要等待后端完成,后端也依賴前端完成勘天,才能進(jìn)行有效的測試

隨后出現(xiàn)servlet怔揩,就有了早期的MVC模式

mvc2.png

首先用戶請求到servlet捉邢,然后根據(jù)請求調(diào)用響應(yīng)的JavaBean,并把所有的顯示交給Jsp去處理商膊,這樣就稱之為mvc模式:

  • M代表模型(Model):數(shù)據(jù)伏伐、bean
  • V代表視圖(View):網(wǎng)頁,jsp....展示模型中的數(shù)據(jù)
  • C代表控制器(Controller):把不同的數(shù)據(jù)(Model)晕拆,顯示在不同的視圖(View)上藐翎,Servlet 扮演的就是這樣的角色

為解決持久層中一直未處理好的數(shù)據(jù)庫事務(wù)的編程,又為了迎合 NoSQL 的強(qiáng)勢崛起实幕,Spring MVC 給出了方案

mvc3.png

二吝镣、初始化過程

當(dāng)一個web應(yīng)用部署tomcat時,在接收用戶請求之前茬缩,會進(jìn)行以下初始化過程

  • 部署在web.xml文件里由<listener>元素標(biāo)記事件的監(jiān)聽器會被創(chuàng)建和初始化
  • 對于所有事件監(jiān)聽器赤惊,如果實現(xiàn)了ServletContextListener接口,將會執(zhí)行其實現(xiàn)的contextInitialized()方法
  • 部署描述文件由<filter>元素標(biāo)記的過濾器會被創(chuàng)建和初始化凰锡,并調(diào)用其init()方法
  • 部署在描述文件由<servlet>元素標(biāo)記的servlet會根據(jù)<load-on-start>的權(quán)值按順序創(chuàng)建并初始化未舟,并調(diào)用其init()方法
    init

三、啟動流程

以下為一個常見的web.xml配置進(jìn)行SpringMvc啟動流程分析

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

  <display-name>spring-demo</display-name>
<!--全局變量-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

<!--監(jiān)聽器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

<!--亂碼filter-->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/**</url-pattern>
  </filter-mapping>

<!--dispatcherServlet-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

1掂为、Listener初始化過程

首先定義了一個<context-param>, 用于配置一個全局的變量裕膀,<context-param>標(biāo)簽內(nèi)容讀取后會放進(jìn)application中,作為web應(yīng)用的全局變量使用勇哗,接下來創(chuàng)建listener時會使用這個變量昼扛,因此,web應(yīng)用在啟動時欲诺,會先讀取這個變量抄谐,之后才進(jìn)行下一步,接著定義了一個ContextLoaderListenerlistener扰法,這個listener的源碼為

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

ContextLoaderListener類繼承了ContextLoader類并實現(xiàn)了ServletContextListener接口蛹含,首先看一下前面講述的ServletContextListener接口源碼:

public interface ServletContextListener extends EventListener {
    void contextInitialized(ServletContextEvent var1);

    void contextDestroyed(ServletContextEvent var1);
}

該接口只有兩個方法contextInitializedcontextDestroyed,這里采用的是觀察者模式塞颁,也稱為為訂閱-發(fā)布模式浦箱,實現(xiàn)了該接口的listener會向發(fā)布者進(jìn)行訂閱,當(dāng)Web應(yīng)用初始化或銷毀時會分別調(diào)用上述兩個方法, ContextLoaderListener的contextInitialized()方法直接調(diào)用了initWebApplicationContext()方法祠锣,這個方法是繼承自ContextLoader類酷窥,通過函數(shù)名可以知道,該方法是用于初始化Web應(yīng)用上下文伴网,即IOC容器蓬推,這里使用的是代理模式,繼續(xù)查看ContextLoader類的initWebApplicationContext()方法的源碼如下:

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
            servletContext.log("Initializing Spring root WebApplicationContext");
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }

            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }

                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }

                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }

                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }

                if (logger.isInfoEnabled()) {
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
                }

                return this.context;
            } catch (Error | RuntimeException var8) {
                logger.error("Context initialization failed", var8);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
                throw var8;
            }
        }
    }

initWebApplicationContext()方法如上講述澡腾,主要目的就是創(chuàng)建root WebApplicationContext對象即IOC容器拳氢,其中比較重要的就是募逞,整個Web應(yīng)用如果存在IOC容器則有且只能有一個,根IoC容器作為全局變量存儲在ServletContextapplication對象中馋评。將根IOC容器放入到application對象之前進(jìn)行了IOC容器的配置和刷新操作,調(diào)用了configureAndRefreshWebApplicationContext()方法刺啦,該方法源碼如下:

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
        wac.refresh();
    }

比較重要的就是獲取到了web.xml中的<context-param>標(biāo)簽配置的全局變量contextConfigLocation留特,并最后一行調(diào)用了refresh()方法,ConfigurableWebApplicationContext是一個接口玛瘸,通過對常用實現(xiàn)類ClassPathXmlApplicationContext逐層查找后可以找到一個抽象類AbstractApplicationContext實現(xiàn)了refresh()方法蜕青,其源碼如下

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

該方法主要用于創(chuàng)建并初始化contextConfigLocation類配置的xml文件中的Bean,因此糊渊,如果我們在配置Bean時出錯右核,在Web應(yīng)用啟動時就會拋出異常,而不是等到運行時才拋出異常
整個ContextLoaderListener類的啟動過程到此就結(jié)束了渺绒,可以發(fā)現(xiàn)贺喝,創(chuàng)建ContextLoaderListener是比較核心的一個步驟,主要工作就是為了創(chuàng)建根IoC容器并使用特定的key將其放入到application對象中宗兼,供整個Web應(yīng)用使用躏鱼,由于在ContextLoaderListener類中構(gòu)造的IOC容器配置的Bean是全局共享的,因此殷绍,在<context-param>標(biāo)識的contextConfigLocation的xml配置文件一般包括: 數(shù)據(jù)庫DataSource染苛、DAO層、Service層主到、事務(wù)等相關(guān)Bean

2茶行、Filter的初始化

在監(jiān)聽器listener初始化完成后,接下來會進(jìn)行filter的初始化操作登钥,GenericFilterBean是任何類型的過濾器的一個比較方便的超類畔师,這個類主要實現(xiàn)的就是從web.xml文件中取得init-param中設(shè)定的值,然后對Filter進(jìn)行初始化(當(dāng)然怔鳖,其子類可以覆蓋init()方法)茉唉。
Filter的生命周期如下

  • void init(FilterConfig config): 用于完成Filter 的初始化
  • void destroy(): 用于Filter 銷毀前结执,完成某些資源的回收
  • void doFilter(ServletRequest request, ServletResponse response,FilterChain chain): 實現(xiàn)過濾功能度陆,該方法就是對每個請求及響應(yīng)增加的額外處理。 過濾器Filter也具有生命周期:init()->doFilter()->destroy()献幔,由部署文件中的filter元素驅(qū)動

3懂傀、Servlet的初始化

Web應(yīng)用啟動的最后一個步驟就是創(chuàng)建和初始化相關(guān)Servlet,在開發(fā)中常用的Servlet就是DispatcherServlet類前端控制器蜡感,前端控制器作為中央控制器是整個Web應(yīng)用的核心蹬蚁,用于獲取分發(fā)用戶請求并返回響應(yīng)

servlet

DispatcherServlet類的間接父類實現(xiàn)了Servlet接口恃泪,因此其本質(zhì)上依舊是一個ServletDispatcherServlet設(shè)計很巧妙犀斋,上層父類不同程度的實現(xiàn)了相關(guān)接口的部分方法贝乎,并留出了相關(guān)方法用于子類覆蓋,將不變的部分統(tǒng)一實現(xiàn)叽粹,將變化的部分預(yù)留方法用于子類實現(xiàn)
具體順序圖
shixu.png

DispatcherServelt類的本質(zhì)是Servlet览效,通過文章開始的講解可知,在Web應(yīng)用部署到容器后進(jìn)行Servlet初始化時會調(diào)用相關(guān)的init(ServletConfig)方法虫几,因此锤灿,DispatchServlet類的初始化過程也由該方法開始。上述調(diào)用邏輯中比較重要的就是FrameworkServlet抽象類中的initServletBean()方法辆脸、initWebApplicationContext()方法以及DispatcherServlet類中的onRefresh()方法

a但校、initServletBean()
    protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
        }

        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }

        if (this.logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
            this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
        }

        if (this.logger.isInfoEnabled()) {
            this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }

    }

該方法是重寫了父類HttpServletBean抽象類的initServletBean()方法,HttpServletBean抽象類在執(zhí)行init()方法時會調(diào)用initServletBean()方法啡氢,由于多態(tài)的特性状囱,最終會調(diào)用其子類FrameworkServlet抽象類的initServletBean()方法。該方法由final標(biāo)識空执,子類就不可再次重寫了浪箭。該方法中比較重要的就是initWebApplicationContext()方法的調(diào)用,該方法仍由FrameworkServlet抽象類實現(xiàn)

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }

        if (wac == null) {
            wac = this.findWebApplicationContext();
        }

        if (wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized(this.onRefreshMonitor) {
                this.onRefresh(wac);
            }
        }

        if (this.publishContext) {
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

該方法的主要作用同樣是創(chuàng)建一個WebApplicationContext對象辨绊,即IOC容器奶栖,不過前文講過每個Web應(yīng)用最多只能存在一個IOC容器,這里創(chuàng)建的則是特定Servlet擁有的子IOC容器门坷。

父子IOC容器的訪問特性

父子容器類似于類的繼承關(guān)系宣鄙,子類可以訪問父類中的成員變量,而父類不可訪問子類的成員變量默蚌,同樣的冻晤,子容器可以訪問父容器中定義的Bean,但父容器無法訪問子容器定義的Bean绸吸,根IOC容器做為全局共享的IOC容器放入Web應(yīng)用需要共享的Bean鼻弧,而子IOC容器根據(jù)需求的不同,放入不同的Bean锦茁,這樣能夠做到隔離攘轩,保證系統(tǒng)的安全性

DispatcherServlet類的子IOC容器創(chuàng)建過程,如果當(dāng)前Servlet存在一個IOC容器則為其設(shè)置根IOC容器作為其父類码俩,并配置刷新該容器度帮,用于構(gòu)造其定義的Bean,這里的方法與前文講述的根IOC容器類似稿存,同樣會讀取用戶在web.xml中配置的<servlet>中的<init-param>值笨篷,用于查找相關(guān)的xml配置文件用于構(gòu)造定義的Bean瞳秽。如果當(dāng)前Servlet不存在一個子IoC容器就去查找一個,如果仍然沒有查找到則調(diào)用
createWebApplicationContext()方法去創(chuàng)建

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if (configLocation != null) {
                wac.setConfigLocation(configLocation);
            }

            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }

該方法用于創(chuàng)建一個子IOC容器并將根IOC容器做為其父容器率翅,接著進(jìn)行配置和刷新操作用于構(gòu)造相關(guān)的Bean练俐。至此,根IOC容器以及相關(guān)Servlet子IoC容器已經(jīng)配置完成安聘,子容器中管理的Bean一般只被該Servlet使用痰洒,因此,其中管理的Bean一般是局部的浴韭,如SpringMVC中需要的各種重要組件,包括Controller脯宿、Interceptor念颈、Converter、ExceptionResolver等

dis.jpeg

當(dāng)IOC子容器構(gòu)造完成后調(diào)用了onRefresh()方法连霉,該方法的調(diào)用與initServletBean()方法的調(diào)用相同榴芳,由父類調(diào)用但具體實現(xiàn)由子類覆蓋,調(diào)用onRefresh()方法時將前文創(chuàng)建的IOC子容器作為參數(shù)傳入

    protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

onRefresh()方法直接調(diào)用了initStrategies()方法跺撼,源碼如上窟感,通過函數(shù)名可以判斷,該方法用于初始化創(chuàng)建multipartResovle來支持圖片等文件的上傳歉井、本地化解析器柿祈、主題解析器、HandlerMapping處理器映射器哩至、HandlerAdapter處理器適配器躏嚎、異常解析器、視圖解析器菩貌、flashMap管理器等卢佣,這些組件都是SpringMVC開發(fā)中的重要組件,相關(guān)組件的初始化創(chuàng)建過程均在此完成箭阶。
到此虚茶,初始化就全部結(jié)束了

四、總結(jié)

在Spring的web容器啟動時會去讀取web.xml文件仇参,相關(guān)啟動順序為:<context-param> --> <listener> --> <filter> --> <servlet>嘹叫,具體為:

  • 1、解析<context-param>鍵值對
  • 2冈敛、創(chuàng)建一個application對象即ServletContext待笑,servlet上下文,用于全局共享
  • 3抓谴、將<context-param>鍵值對放入ServletContext中暮蹂,web應(yīng)用全局共享
  • 4寞缝、讀取<listener>標(biāo)簽,創(chuàng)建監(jiān)聽器仰泻,一般使用ContextLoaderListener荆陆,如果使用了ContextLoaderListener,Spring就會創(chuàng)建一個WebApplicationContext對象集侯,這個就是IOC容器, ContextLoaderListener創(chuàng)建的IOC容器是全局共享的被啼,并將其放在ServletContext中, 鍵名為WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 讀取web.xml文件里的contextConfigLocation配置中的xml文件來創(chuàng)建bean
  • 5、listener創(chuàng)建完畢后如果有Filter會去創(chuàng)建Filter
  • 6棠枉、初始化Servlet浓体,一般使用DispatchServlet類
  • 7、DispatchServlet的父類FrameworkServlet會重寫其父類的initServletBean方法辈讶,并調(diào)用initWebApplicationContext()以及onRefresh()方法
  • 8命浴、initWebApplicationContext()方法會創(chuàng)建一個當(dāng)前servlet的一個IOC子容器,如果存在上述的全局WebApplicationContext則將其設(shè)置為父容器贱除,如果不存在上述全局的則父容器為null生闲。
  • 9、讀取<servlet>標(biāo)簽的<init-param>配置的xml文件并加載相關(guān)Bean
  • 10月幌、onRefresh()方法創(chuàng)建Web應(yīng)用相關(guān)組件

好了碍讯,本文到此就結(jié)束了,篇幅過長扯躺,如果其中有誤捉兴,可以在下方評論留言,也希望可以關(guān)注我哦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缅帘,一起剝皮案震驚了整個濱河市轴术,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钦无,老刑警劉巖逗栽,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異失暂,居然都是意外死亡彼宠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門弟塞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凭峡,“玉大人,你說我怎么就攤上這事决记〈菁剑” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長索昂。 經(jīng)常有香客問我建车,道長,這世上最難降的妖魔是什么椒惨? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任缤至,我火速辦了婚禮,結(jié)果婚禮上康谆,老公的妹妹穿的比我還像新娘领斥。我一直安慰自己,他們只是感情好沃暗,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布月洛。 她就那樣靜靜地躺著,像睡著了一般孽锥。 火紅的嫁衣襯著肌膚如雪膊存。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天忱叭,我揣著相機(jī)與錄音,去河邊找鬼今艺。 笑死韵丑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虚缎。 我是一名探鬼主播撵彻,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼实牡!你這毒婦竟也來了陌僵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤创坞,失蹤者是張志新(化名)和其女友劉穎碗短,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體题涨,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡偎谁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纲堵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巡雨。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖席函,靈堂內(nèi)的尸體忽然破棺而出铐望,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布正蛙,位于F島的核電站督弓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跟畅。R本人自食惡果不足惜咽筋,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徊件。 院中可真熱鬧奸攻,春花似錦、人聲如沸虱痕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽部翘。三九已至硝训,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間新思,已是汗流浹背窖梁。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留夹囚,地道東北人纵刘。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像荸哟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鞍历,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

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