13磅废、行為型模式之責任鏈模式

在閻宏博士的《JAVA與模式》一書中開頭是這樣描述責任鏈(Chain of Responsibility)模式的:

責任鏈模式是一種對象的行為模式纳像。在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈拯勉。請求在這個鏈上傳遞竟趾,直到鏈上的某一個對象決定處理此請求。發(fā)出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求宫峦,這使得系統(tǒng)可以在不影響客戶端的情況下動態(tài)地重新組織和分配責任岔帽。


從擊鼓傳花談起

擊鼓傳花是一種熱鬧而又緊張的飲酒游戲。在酒宴上賓客依次坐定位置导绷,由一人擊鼓犀勒,擊鼓的地方與傳花的地方是分開的,以示公正。開始擊鼓時贾费,花束就開始依次傳遞钦购,鼓聲一落,如果花束在某人手中褂萧,則該人就得飲酒押桃。

比如說,賈母导犹、賈赦唱凯、賈政、賈寶玉和賈環(huán)是五個參加擊鼓傳花游戲的傳花者谎痢,他們組成一個環(huán)鏈磕昼。擊鼓者將花傳給賈母,開始傳花游戲舶得。花由賈母傳給賈赦爽蝴,由賈赦傳給賈政沐批,由賈政傳給賈寶玉,又賈寶玉傳給賈環(huán)蝎亚,由賈環(huán)傳回給賈母九孩,如此往復,如下圖所示发框。當鼓聲停止時躺彬,手中有花的人就得執(zhí)行酒令。

image.png

擊鼓傳花便是責任鏈模式的應用梅惯。責任鏈可能是一條直線宪拥、一個環(huán)鏈或者一個樹結構的一部分。

責任鏈模式的結構

下面使用了一個責任鏈模式的最簡單的實現(xiàn)铣减。

image.png

責任鏈模式涉及到的角色如下所示:

●  抽象處理者(Handler)角色:定義出一個處理請求的接口她君。如果需要,接口可以定義 出一個方法以設定和返回對下家的引用葫哗。這個角色通常由一個Java抽象類或者Java接口實現(xiàn)缔刹。上圖中Handler類的聚合關系給出了具體子類對下家的引用,抽象方法handleRequest()規(guī)范了子類處理請求的操作劣针。

●  具體處理者(ConcreteHandler)角色:具體處理者接到請求后校镐,可以選擇將請求處理掉,或者將請求傳給下家捺典。由于具體處理者持有對下家的引用鸟廓,因此,如果需要,具體處理者可以訪問下家肝箱。

源代碼

抽象處理者角色

package com.nieshenkuan.constitutive.ChainofResponsibility;

public abstract class Handler {

    /**
     * 當前對象哄褒,持有后繼對象的引用
     * 持有后繼的責任對象
     */
    protected Handler successor;

    /**
     * 示意處理請求的方法,雖然這個示意方法是沒有傳入?yún)?shù)的 但實際是可以傳入?yún)?shù)的煌张,根據(jù)具體需要來選擇是否傳遞參數(shù)
     */
    public abstract void handleRequest();

    /**
     * 取值方法
     */
    public Handler getSuccessor() {
        return successor;
    }

    /**
     * 賦值方法呐赡,設置后繼的責任對象
     */
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

}

具體處理者角色

package com.nieshenkuan.constitutive.ChainofResponsibility;

public class ConcreteHandler extends Handler {
    /**
     * 處理方法,調用此方法處理請求
     */
    @Override
    public void handleRequest() {
        /**
         * 判斷是否有后繼的責任對象 如果有骏融,就轉發(fā)請求給后繼的責任對象 如果沒有链嘀,則處理請求
         */
        if (getSuccessor() != null) {
            System.out.println("放過請求");
            getSuccessor().handleRequest();
        } else {
            System.out.println("處理請求");
        }
    }

}

客戶端類

package com.nieshenkuan.constitutive.ChainofResponsibility;

public class Client {
    public static void main(String[] args) {
        // 組裝責任鏈
        Handler handler1 = new ConcreteHandler();
        Handler handler2 = new ConcreteHandler();
        Handler handler3 = new ConcreteHandler();
        handler1.setSuccessor(handler2);
        handler2.setSuccessor(handler3);
        // 提交請求
        handler1.handleRequest();
    }
}

結果:

放過請求
放過請求
處理請求

可以看出,客戶端創(chuàng)建了三個處理者對象档玻,并指定第一個處理者對象的下家是第二個處理者對象怀泊,而第二個處理者對象是第三個處理對象的下家。然后客戶端將請求傳遞給第一個處理者對象误趴。

由于本示例的傳遞邏輯非常簡單:只要有下家霹琼,就傳給下家處理;如果沒有下家凉当,就自行處理枣申。因此,第一個處理者對象接到請求后看杭,會將請求傳遞給第二個處理者對象忠藤。由于第二個處理者對象沒有下家,于是自行處理請求楼雹∧:ⅲ活動時序圖如下所示。

image.png

使用場景

來考慮這樣一個功能:申請聚餐費用的管理贮缅。

很多公司都是這樣的福利榨咐,就是項目組或者是部門可以向公司申請一些聚餐費用,用于組織項目組成員或者是部門成員進行聚餐活動谴供。

申請聚餐費用的大致流程一般是:由申請人先填寫申請單祭芦,然后交給領導審批,如果申請批準下來憔鬼,領導會通知申請人審批通過龟劲,然后申請人去財務領取費用,如果沒有批準下來轴或,領導會通知申請人審批未通過昌跌,此事也就此作罷。

不同級別的領導照雁,對于審批的額度是不一樣的蚕愤,比如答恶,項目經理只能審批500元以內的申請;部門經理能審批1000元以內的申請萍诱;而總經理可以審核任意額度的申請悬嗓。

也就是說,當某人提出聚餐費用申請的請求后裕坊,該請求會經由項目經理包竹、部門經理、總經理之中的某一位領導來進行相應的處理籍凝,但是提出申請的人并不知道最終會由誰來處理他的請求周瞎,一般申請人是把自己的申請?zhí)峤唤o項目經理,或許最后是由總經理來處理他的請求饵蒂。

可以使用責任鏈模式來實現(xiàn)上述功能:當某人提出聚餐費用申請的請求后声诸,該請求會在 **項目經理—〉部門經理—〉總經理 **這樣一條領導處理鏈上進行傳遞,發(fā)出請求的人并不知道誰會來處理他的請求退盯,每個領導會根據(jù)自己的職責范圍彼乌,來判斷是處理請求還是把請求交給更高級別的領導,只要有領導處理了渊迁,傳遞就結束了慰照。

需要把每位領導的處理獨立出來,實現(xiàn)成單獨的職責處理對象宫纬,然后為它們提供一個公共的焚挠、抽象的父職責對象膏萧,這樣就可以在客戶端來動態(tài)地組合職責鏈漓骚,實現(xiàn)不同的功能要求了。

image.png

源代碼

抽象處理者角色類

package com.nieshenkuan.constitutive.ChainofResponsibility.demo;

public abstract class Handler {
    /**
     * 持有下一個處理請求的對象
     */
    protected Handler successor = null;

    /**
     * 取值方法
     */
    public Handler getSuccessor() {
        return successor;
    }

    /**
     * 設置下一個處理請求的對象
     */
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    /**
     * 處理聚餐費用的申請
     * 
     * @param user
     *            申請人
     * @param fee
     *            申請的錢數(shù)
     * @return 成功或失敗的具體通知
     */
    public abstract String handleFeeRequest(String user, double fee);
}

具體處理者角色

package com.nieshenkuan.constitutive.ChainofResponsibility.demo;

public class ProjectManager extends Handler {

    @Override
    public String handleFeeRequest(String user, double fee) {

        String str = "";
        // 項目經理權限比較小榛泛,只能在500以內
        if (fee < 500) {
            // 為了測試蝌蹂,簡單點,只同意張三的請求
            if ("張三".equals(user)) {
                str = "成功:項目經理同意【" + user + "】的聚餐費用曹锨,金額為" + fee + "元";
            } else {
                // 其他人一律不同意
                str = "失敼赂觥:項目經理不同意【" + user + "】的聚餐費用,金額為" + fee + "元";
            }
        } else {
            // 超過500沛简,繼續(xù)傳遞給級別更高的人處理
            if (getSuccessor() != null) {
                return getSuccessor().handleFeeRequest(user, fee);
            }
        }
        return str;
    }

}
package com.nieshenkuan.constitutive.ChainofResponsibility.demo;

public class DeptManager extends Handler {

    @Override
    public String handleFeeRequest(String user, double fee) {

        String str = "";
        // 部門經理的權限只能在1000以內
        if (fee < 1000) {
            // 為了測試齐鲤,簡單點,只同意張三的請求
            if ("張三".equals(user)) {
                str = "成功:部門經理同意【" + user + "】的聚餐費用椒楣,金額為" + fee + "元";
            } else {
                // 其他人一律不同意
                str = "失敻肌:部門經理不同意【" + user + "】的聚餐費用,金額為" + fee + "元";
            }
        } else {
            // 超過1000捧灰,繼續(xù)傳遞給級別更高的人處理
            if (getSuccessor() != null) {
                return getSuccessor().handleFeeRequest(user, fee);
            }
        }
        return str;
    }

}
package com.nieshenkuan.constitutive.ChainofResponsibility.demo;

public class GeneralManager extends Handler {

    @Override
    public String handleFeeRequest(String user, double fee) {

        String str = "";
        // 總經理的權限很大淆九,只要請求到了這里,他都可以處理
        if (fee >= 1000) {
            // 為了測試,簡單點炭庙,只同意張三的請求
            if ("張三".equals(user)) {
                str = "成功:總經理同意【" + user + "】的聚餐費用饲窿,金額為" + fee + "元";
            } else {
                // 其他人一律不同意
                str = "失敗:總經理不同意【" + user + "】的聚餐費用焕蹄,金額為" + fee + "元";
            }
        } else {
            // 如果還有后繼的處理對象逾雄,繼續(xù)傳遞
            if (getSuccessor() != null) {
                return getSuccessor().handleFeeRequest(user, fee);
            }
        }
        return str;
    }

}

客戶端類

package com.nieshenkuan.constitutive.ChainofResponsibility.demo;

public class Client {
    public static void main(String[] args) {
        // 先要組裝責任鏈
        Handler h1 = new GeneralManager();
        Handler h2 = new DeptManager();
        Handler h3 = new ProjectManager();
        h3.setSuccessor(h2);
        h2.setSuccessor(h1);

        // 開始測試
        String test1 = h3.handleFeeRequest("張三", 300);
        System.out.println("test1 = " + test1);
        String test2 = h3.handleFeeRequest("李四", 300);
        System.out.println("test2 = " + test2);
        System.out.println("---------------------------------------");

        String test3 = h3.handleFeeRequest("張三", 700);
        System.out.println("test3 = " + test3);
        String test4 = h3.handleFeeRequest("李四", 700);
        System.out.println("test4 = " + test4);
        System.out.println("---------------------------------------");

        String test5 = h3.handleFeeRequest("張三", 1500);
        System.out.println("test5 = " + test5);
        String test6 = h3.handleFeeRequest("李四", 1500);
        System.out.println("test6 = " + test6);
    }

}

運行結果如下:

test1 = 成功:項目經理同意【張三】的聚餐費用,金額為300.0元
test2 = 失敳炼堋:項目經理不同意【李四】的聚餐費用嘲驾,金額為300.0元
---------------------------------------
test3 = 成功:部門經理同意【張三】的聚餐費用,金額為700.0元
test4 = 失敿B:部門經理不同意【李四】的聚餐費用辽故,金額為700.0元
---------------------------------------
test5 = 成功:總經理同意【張三】的聚餐費用,金額為1500.0元
test6 = 失敗:總經理不同意【李四】的聚餐費用,金額為1500.0元

純的與不純的責任鏈模式

一個純的責任鏈模式要求一個具體的處理者對象只能在兩個行為中選擇一個:一是承擔責任鬓椭,而是把責任推給下家条辟。不允許出現(xiàn)某一個具體處理者對象在承擔了一部分責任后又 把責任向下傳的情況。

在一個純的責任鏈模式里面蜕该,一個請求必須被某一個處理者對象所接收;在一個不純的責任鏈模式里面,一個請求可以最終不被任何接收端對象所接收芋肠。

純的責任鏈模式的實際例子很難找到,一般看到的例子均是不純的責任鏈模式的實現(xiàn)遵蚜。有些人認為不純的責任鏈根本不是責任鏈模式帖池,這也許是有道理的。但是在實際的系統(tǒng)里吭净,純的責任鏈很難找到睡汹。如果堅持責任鏈不純便不是責任鏈模式,那么責任鏈模式便不會有太大意義了寂殉。

責任鏈模式在Tomcat中的應用

眾所周知Tomcat中的Filter就是使用了責任鏈模式囚巴,創(chuàng)建一個Filter除了要在web.xml文件中做相應配置外,還需要實現(xiàn)javax.servlet.Filter接口友扰。

public class TestFilter implements Filter{

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        
        chain.doFilter(request, response);
    }

    public void destroy() {
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

}

使用DEBUG模式所看到的結果如下


image.png

其實在真正執(zhí)行到TestFilter類之前彤叉,會經過很多Tomcat內部的類。順帶提一下其實Tomcat的容器設置也是責任鏈模式村怪,注意被紅色方框所圈中的類秽浇,從Engine到Host再到Context一直到Wrapper都是通過一個鏈傳遞請求。被綠色方框所圈中的地方有一個名為ApplicationFilterChain的類实愚,ApplicationFilterChain類所扮演的就是抽象處理者角色兼呵,而具體處理者角色由各個Filter扮演兔辅。

第一個疑問是ApplicationFilterChain將所有的Filter存放在哪里?

答案是保存在ApplicationFilterChain類中的一個ApplicationFilterConfig對象的數(shù)組中击喂。

/**
     * Filters.
     */
    private ApplicationFilterConfig[] filters = 
        new ApplicationFilterConfig[0];

那ApplicationFilterConfig對象又是什么呢维苔?

ApplicationFilterConfig是一個Filter容器。以下是ApplicationFilterConfig類的聲明:

/**
 * Implementation of a <code>javax.servlet.FilterConfig</code> useful in
 * managing the filter instances instantiated when a web application
 * is first started.
 *
 * @author Craig R. McClanahan
 * @version $Id: ApplicationFilterConfig.java 1201569 2011-11-14 01:36:07Z kkolinko $
 */

當一個web應用首次啟動時ApplicationFilterConfig會自動實例化懂昂,它會從該web應用的web.xml文件中讀取配置的Filter的信息介时,然后裝進該容器。

剛剛看到在ApplicationFilterChain類中所創(chuàng)建的ApplicationFilterConfig數(shù)組長度為零凌彬,那它是在什么時候被重新賦值的呢沸柔?

 private ApplicationFilterConfig[] filters = 
        new ApplicationFilterConfig[0];

是在調用ApplicationFilterChain類的addFilter()方法時。

 /**
     * The int which gives the current number of filters in the chain.
     */
    private int n = 0;
 public static final int INCREMENT = 10;
 void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters)
            if(filter==filterConfig)
                return;

        if (n == filters.length) {
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }

變量n用來記錄當前過濾器鏈里面擁有的過濾器數(shù)目铲敛,默認情況下n等于0褐澎,ApplicationFilterConfig對象數(shù)組的長度也等于0,所以當?shù)谝淮握{用addFilter()方法時伐蒋,if (n == filters.length)的條件成立工三,ApplicationFilterConfig數(shù)組長度被改變。之后filters[n++] = filterConfig;將變量filterConfig放入ApplicationFilterConfig數(shù)組中并將當前過濾器鏈里面擁有的過濾器數(shù)目+1先鱼。

那ApplicationFilterChain的addFilter()方法又是在什么地方被調用的呢俭正?

是在ApplicationFilterFactory類的createFilterChain()方法中。

public ApplicationFilterChain createFilterChain
        (ServletRequest request, Wrapper wrapper, Servlet servlet) {

        // get the dispatcher type
        DispatcherType dispatcher = null; 
        if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
            dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
        }
        String requestPath = null;
        Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
        
        if (attribute != null){
            requestPath = attribute.toString();
        }
        
        // If there is no servlet to execute, return null
        if (servlet == null)
            return (null);

        boolean comet = false;
        
        // Create and initialize a filter chain object
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            Request req = (Request) request;
            comet = req.isComet();
            if (Globals.IS_SECURITY_ENABLED) {
                // Security: Do not recycle
                filterChain = new ApplicationFilterChain();
                if (comet) {
                    req.setFilterChain(filterChain);
                }
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);

        filterChain.setSupport
            (((StandardWrapper)wrapper).getInstanceSupport());

        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();//第51行
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0))
            return (filterChain);

        // Acquire the information we will need to match filter mappings
        String servletName = wrapper.getName();

        // Add the relevant path-mapped filters to this filter chain
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            boolean isCometFilter = false;
            if (comet) {
                try {
                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
                } catch (Exception e) {
                    // Note: The try catch is there because getFilter has a lot of 
                    // declared exceptions. However, the filter is allocated much
                    // earlier
                    Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
                    ExceptionUtils.handleThrowable(t);
                }
                if (isCometFilter) {
                    filterChain.addFilter(filterConfig);
                }
            } else {
                filterChain.addFilter(filterConfig);
            }
        }

        // Add filters that match on servlet name second
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            boolean isCometFilter = false;
            if (comet) {
                try {
                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
                } catch (Exception e) {
                    // Note: The try catch is there because getFilter has a lot of 
                    // declared exceptions. However, the filter is allocated much
                    // earlier
                }
                if (isCometFilter) {
                    filterChain.addFilter(filterConfig);
                }
            } else {
                filterChain.addFilter(filterConfig);
            }
        }

        // Return the completed filter chain
        return (filterChain);

    }

可以將如上代碼分為兩段焙畔,51行之前為第一段掸读,51行之后為第二段。

第一段的主要目的是創(chuàng)建ApplicationFilterChain對象以及一些參數(shù)設置宏多。

第二段的主要目的是從上下文中獲取所有Filter信息儿惫,之后使用for循環(huán)遍歷并調用filterChain.addFilter(filterConfig);將filterConfig放入ApplicationFilterChain對象的ApplicationFilterConfig數(shù)組中。

那ApplicationFilterFactory類的createFilterChain()方法又是在什么地方被調用的呢绷落?

是在StandardWrapperValue類的invoke()方法中被調用的姥闪。

image.png

由于invoke()方法較長始苇,所以將很多地方省略砌烁。

public final void invoke(Request request, Response response)
        throws IOException, ServletException {
   ...省略中間代碼
     // Create the filter chain for this request
        ApplicationFilterFactory factory =
            ApplicationFilterFactory.getInstance();
        ApplicationFilterChain filterChain =
            factory.createFilterChain(request, wrapper, servlet);
  ...省略中間代碼
         filterChain.doFilter(request.getRequest(), response.getResponse());
  ...省略中間代碼
    }

那正常的流程應該是這樣的:

在StandardWrapperValue類的invoke()方法中調用ApplicationFilterChai類的createFilterChain()方法———>在ApplicationFilterChai類的createFilterChain()方法中調用ApplicationFilterChain類的addFilter()方法———>在ApplicationFilterChain類的addFilter()方法中給ApplicationFilterConfig數(shù)組賦值。

image.png

根據(jù)上面的代碼可以看出StandardWrapperValue類的invoke()方法在執(zhí)行完createFilterChain()方法后催式,會繼續(xù)執(zhí)行ApplicationFilterChain類的doFilter()方法函喉,然后在doFilter()方法中會調用internalDoFilter()方法。

以下是internalDoFilter()方法的部分代碼

 // Call the next filter if there is one
        if (pos < n) {
       //拿到下一個Filter荣月,將指針向下移動一位
            //pos它來標識當前ApplicationFilterChain(當前過濾器鏈)執(zhí)行到哪個過濾器
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
          //獲取當前指向的Filter的實例
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);
                
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                            Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = 
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege
                        ("doFilter", filter, classType, args, principal);
                    
                } else {
            //調用Filter的doFilter()方法  
                    filter.doFilter(request, response, this);
                }

這里的filter.doFilter(request, response, this);就是調用我們前面創(chuàng)建的TestFilter中的doFilter()方法管呵。而TestFilter中的doFilter()方法會繼續(xù)調用chain.doFilter(request, response);方法,而這個chain其實就是ApplicationFilterChain,所以調用過程又回到了上面調用dofilter和調用internalDoFilter方法哺窄,這樣執(zhí)行直到里面的過濾器全部執(zhí)行捐下。

如果定義兩個過濾器账锹,則Debug結果如下:

image.png

轉自:http://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坷襟,隨后出現(xiàn)的幾起案子奸柬,更是在濱河造成了極大的恐慌,老刑警劉巖婴程,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廓奕,死亡現(xiàn)場離奇詭異,居然都是意外死亡档叔,警方通過查閱死者的電腦和手機桌粉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衙四,“玉大人铃肯,你說我怎么就攤上這事〈福” “怎么了缘薛?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卡睦。 經常有香客問我宴胧,道長,這世上最難降的妖魔是什么表锻? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任恕齐,我火速辦了婚禮,結果婚禮上瞬逊,老公的妹妹穿的比我還像新娘显歧。我一直安慰自己,他們只是感情好确镊,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布士骤。 她就那樣靜靜地躺著,像睡著了一般蕾域。 火紅的嫁衣襯著肌膚如雪拷肌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天旨巷,我揣著相機與錄音巨缘,去河邊找鬼。 笑死采呐,一個胖子當著我的面吹牛若锁,可吹牛的內容都是我干的。 我是一名探鬼主播斧吐,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼又固,長吁一口氣:“原來是場噩夢啊……” “哼仲器!你這毒婦竟也來了?” 一聲冷哼從身側響起仰冠,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤娄周,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沪停,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體煤辨,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年木张,在試婚紗的時候發(fā)現(xiàn)自己被綠了众辨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡舷礼,死狀恐怖鹃彻,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情妻献,我是刑警寧澤蛛株,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站育拨,受9級特大地震影響谨履,放射性物質發(fā)生泄漏。R本人自食惡果不足惜熬丧,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一笋粟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧析蝴,春花似錦害捕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佑菩,卻和暖如春盾沫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倘待。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工疮跑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留组贺,地道東北人凸舵。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像失尖,于是被迫代替她去往敵國和親啊奄。 傳聞我的和親對象是個殘疾皇子渐苏,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容