SpringMVC的實現(xiàn)與Web環(huán)境

  • IOC容器在web環(huán)境初始化過程

SpringMVC是建立在IOC容器的基礎(chǔ)上的,SpringIOC是一個獨立的模塊,需要在web環(huán)境中引入SpringIOC并啟動空另。通常我們會在web.xml配置蜗巧。

<servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
</servlet>

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

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
</context-param>

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

DispatcherServlet:Springmvc的前端控制器:初始化mvc組件與分發(fā)請求。
ContextLoaderListener:負責監(jiān)聽IOC容器在web環(huán)境初始化的整個過程。
context-param:SpringIOC讀取bean的路徑笨奠。

ContextLoaderListener創(chuàng)建的上下文為根上下文,同時還有一個輸入MVC的子上下文棍苹。

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,這個監(jiān)聽器是啟動根IOC容器并把它載入web容器的主要模塊此熬,首先從servlet事件中得到servletContext,然后可以讀取配置在web.xml中的各個相關(guān)值滑进,接著Contextloader會實例化WebapplicationContext,并完成載入跟初始化過程犀忱,這個被初始化的上下文為根上下文。獲取根上下文的方法:

 WebApplicationContext w = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);

ServletContextListener是ServlettListener的監(jiān)聽者扶关,當服務(wù)啟動或結(jié)束時阴汇,會調(diào)用相對應(yīng)的回調(diào)方法

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());
    }
}

在initWebApplicationContext中,根上下文創(chuàng)建成功時,會存在web容器的ServletContext中供需要時使用节槐。而當根上下文創(chuàng)建好時搀庶,Web容器開始初始化DispatcherServlet拐纱,DispatcherServlet會建立自己當上下文來持有SpringMVC的bean對象。

  • DispatcherServlet的初始化過程

DispatcherServlet的繼承關(guān)系(引用spring技術(shù)內(nèi)幕).png
DispatcherServlet的處理過程(引用spring技術(shù)內(nèi)幕).png

通過時序圖可看到DispatcherServlet初始化是在httpServletBean完成的

public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        //從初始化參數(shù)哥倔,配置bean屬性  
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        //調(diào)用子類來初始化bean
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

@Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
                //初始化web上下文
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

protected WebApplicationContext initWebApplicationContext() {
        //獲取根上下文
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        //使用根上下文為mvc上下文的雙親上下文
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // 把當前上下文放到servletContext中
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

springMVC的上下文則是在initFrameworkServlet()初始化的,主要是將根上下文傳遞給它秸架,然后利用反射來實例化上下文對象。最終調(diào)用IOC容器的refresh()方法來完成容器的初始化咆蒿。

  • DispatcherServlet具體做了什么事东抹?

由上面的時序圖中可看到在DispatcherServlet中,主要做了兩件事:
1.initStrategies():初始化MVC。
2.doDispatch():對http請求進行分發(fā)沃测。

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

主要對initHandlerMapping初始化進行分析,HandlerMapping主要是為http請求找到相對應(yīng)的Controller控制器缭黔,在initHandlerMappings中把bean配置文件中配置好的HandlerMapping載入到IOC容器中。

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // 從所有的上下文中獲取HandlerMapping
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                      //保存HandlerMapping到list中
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                //對HandlerMapping并排序
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                      //從上下文信息里根據(jù)handler名稱來獲取HandlerMapping
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        
        //通過注冊確保我們至少有一個HandlerMapping
       //如果未找到其他映射蒂破,則為默認的HandlerMapping馏谨。
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

HandlerMapping接口主要維護著handler跟url的映射關(guān)系,我們來分析下HandlerMapping的主要實現(xiàn)附迷。

public interface HandlerMapping {
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

HandlerMapping里只有一個方法getHandler(),這個方法返回了一個HandlerExecutionChain惧互,這個HandlerExecutionChain持有一個攔截器鏈跟handler,通過攔截器鏈來對handler進行增強功能喇伯。HandlerExecutionChain里定義對handler跟interceptors需要在定義HandlerMapping時配置好壹哺,那么我們來分析一下這個注冊的過程。

@Override
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                              //調(diào)用基類來完成注冊
                registerHandler(url, handler);
            }
        }
    }

具體的注冊是在基類AbstractUrlHandlerMapping里完成的

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;

        // 如果是用bean名稱定義艘刚,那么直接從容器中獲取
        if (!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String) handler;
            if (getApplicationContext().isSingleton(handlerName)) {
                resolvedHandler = getApplicationContext().getBean(handlerName);
            }
        }

        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                        "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
            }
        }
        else {
            if (urlPath.equals("/")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Root mapping to " + getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            }
            else if (urlPath.equals("/*")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Default mapping to " + getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            }
            else {
                          //將url跟controller設(shè)置到handlerMap中
                this.handlerMap.put(urlPath, resolvedHandler);
                if (logger.isInfoEnabled()) {
                    logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
                }
            }
        }
    }
  //存放url跟controller
private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();

這個時候已經(jīng)為SpringMVC分發(fā)http請求準備好了基礎(chǔ)數(shù)據(jù),根據(jù)handlerMap中的url映射到具體的handler,我們來看一下HandlerMapping是如何完成請求映射的截珍。是如何找到handler的攀甚。

Override
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }
//具體實現(xiàn)在AbstractUrlHandlerMapping類的getHandlerInternal()里面,
@Override
    protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
          //從request中獲取url
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
          //將url跟handler進行匹配
        Object handler = lookupHandler(lookupPath, request);
        if (handler == null) {
            // We need to care for the default handler directly, since we need to
            // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
            Object rawHandler = null;
            if ("/".equals(lookupPath)) {
                rawHandler = getRootHandler();
            }
            if (rawHandler == null) {
                rawHandler = getDefaultHandler();
            }
            if (rawHandler != null) {
                // Bean name or resolved handler?
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    rawHandler = getApplicationContext().getBean(handlerName);
                }
                validateHandler(rawHandler, request);
                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
            }
        }
        if (handler != null && logger.isDebugEnabled()) {
            logger.debug("Mapping [" + lookupPath + "] to " + handler);
        }
        else if (handler == null && logger.isTraceEnabled()) {
            logger.trace("No handler mapping found for [" + lookupPath + "]");
        }
        return handler;
    }

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        // Direct match?
        Object handler = this.handlerMap.get(urlPath);
        if (handler != null) {
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            return buildPathExposingHandler(handler, urlPath, urlPath, null);
        }
        // Pattern match?
        List<String> matchingPatterns = new ArrayList<String>();
        for (String registeredPattern : this.handlerMap.keySet()) {
            if (getPathMatcher().match(registeredPattern, urlPath)) {
                matchingPatterns.add(registeredPattern);
            }
            else if (useTrailingSlashMatch()) {
                if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                    matchingPatterns.add(registeredPattern +"/");
                }
            }
        }
        String bestPatternMatch = null;
        Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            Collections.sort(matchingPatterns, patternComparator);
            if (logger.isDebugEnabled()) {
                logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
            }
            bestPatternMatch = matchingPatterns.get(0);
        }
        if (bestPatternMatch != null) {
            handler = this.handlerMap.get(bestPatternMatch);
            if (handler == null) {
                Assert.isTrue(bestPatternMatch.endsWith("/"));
                handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
            }
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(handler, request);
            String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);

            // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
            // for all of them
            Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
            for (String matchingPattern : matchingPatterns) {
                if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
                    Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                    Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
                    uriTemplateVariables.putAll(decodedVars);
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
            }
            return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
        }
        // No handler found...
        return null;
    }

具體的http分發(fā)是由dispatcherServlet的doDispatch()來做的,我們直接看代碼

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       HttpServletRequest processedRequest = request;
       HandlerExecutionChain mappedHandler = null;
       boolean multipartRequestParsed = false;

       WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

       try {
             //先設(shè)置視圖模型
           ModelAndView mv = null;
           Exception dispatchException = null;

           try {
               processedRequest = checkMultipart(request);
               multipartRequestParsed = (processedRequest != request);

               // 根據(jù)請求獲取Handler.
               mappedHandler = getHandler(processedRequest);
               if (mappedHandler == null || mappedHandler.getHandler() == null) {
                   noHandlerFound(processedRequest, response);
                   return;
               }

               // 利用HandlerAdapter檢查handler的合法性
               HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

               // Process last-modified header, if supported by the handler.
               String method = request.getMethod();
               boolean isGet = "GET".equals(method);
               if (isGet || "HEAD".equals(method)) {
                   long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                   if (logger.isDebugEnabled()) {
                       logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                   }
                   if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                       return;
                   }
               }

               if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                   return;
               }

               // 真正完成handler的調(diào)用
               mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

               if (asyncManager.isConcurrentHandlingStarted()) {
                   return;
               }

               applyDefaultViewName(processedRequest, mv);
               mappedHandler.applyPostHandle(processedRequest, response, mv);
           }
           catch (Exception ex) {
               dispatchException = ex;
           }
           catch (Throwable err) {
               // As of 4.3, we're processing Errors thrown from handler methods as well,
               // making them available for @ExceptionHandler methods and other scenarios.
               dispatchException = new NestedServletException("Handler dispatch failed", err);
           }
           processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
       }
       catch (Exception ex) {
           triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
       }
       catch (Throwable err) {
           triggerAfterCompletion(processedRequest, response, mappedHandler,
                   new NestedServletException("Handler processing failed", err));
       }
       finally {
           if (asyncManager.isConcurrentHandlingStarted()) {
               // Instead of postHandle and afterCompletion
               if (mappedHandler != null) {
                   mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
               }
           }
           else {
               // Clean up any resources used by a multipart request.
               if (multipartRequestParsed) {
                   cleanupMultipart(processedRequest);
               }
           }
       }
   }
springMVC的流程(圖片引用spring技術(shù)內(nèi)幕)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市岗喉,隨后出現(xiàn)的幾起案子秋度,更是在濱河造成了極大的恐慌,老刑警劉巖钱床,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荚斯,死亡現(xiàn)場離奇詭異,居然都是意外死亡查牌,警方通過查閱死者的電腦和手機事期,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纸颜,“玉大人兽泣,你說我怎么就攤上這事⌒菜铮” “怎么了唠倦?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵称鳞,是天一觀的道長。 經(jīng)常有香客問我稠鼻,道長冈止,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任候齿,我火速辦了婚禮熙暴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毛肋。我一直安慰自己怨咪,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布润匙。 她就那樣靜靜地躺著诗眨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孕讳。 梳的紋絲不亂的頭發(fā)上匠楚,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音厂财,去河邊找鬼芋簿。 笑死,一個胖子當著我的面吹牛璃饱,可吹牛的內(nèi)容都是我干的与斤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼荚恶,長吁一口氣:“原來是場噩夢啊……” “哼撩穿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谒撼,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤食寡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后廓潜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抵皱,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年辩蛋,在試婚紗的時候發(fā)現(xiàn)自己被綠了呻畸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡悼院,死狀恐怖擂错,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情樱蛤,我是刑警寧澤钮呀,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布剑鞍,位于F島的核電站,受9級特大地震影響爽醋,放射性物質(zhì)發(fā)生泄漏蚁署。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一蚂四、第九天 我趴在偏房一處隱蔽的房頂上張望光戈。 院中可真熱鬧,春花似錦遂赠、人聲如沸久妆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至术唬,卻和暖如春抑诸,著一層夾襖步出監(jiān)牢的瞬間烂琴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工蜕乡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奸绷,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓层玲,卻偏偏與公主長得像号醉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辛块,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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