Java面試題:SpringMVC過濾器(Filter)與攔截器(Interceptor)的區(qū)別(附源碼)

1 概述

在JavaWeb階段我們學習了Filter過濾器,提出Filter的概念一開始我們?yōu)榱诉^濾字符集亂碼,在Servlet體系中攔截目標請求,而攔截器是在SpringMVC中定義的概念名叫HandlerInteceptor。

在開發(fā)過程中,使用攔截器的配置更為靈活,其API接口更豐富,他們的目的都可以達到對請求的前置和后置處理,其本質(zhì)上區(qū)別不大,但由于攔截器可以被Spring容器管理,從而獲得被容器賦予的能力,而filter功能單一,所以后期大家都習慣使用攔截器完成某項特定的功能。


過濾器和攔截器.jpg

2 過濾器(Filter)

2.1 過濾器定義

一個實現(xiàn)了特殊接口(Filter)的Java類,實現(xiàn)對請求資源(jsp、servlet、html)的過濾功能。過濾器是一個運行在服務器的程序,優(yōu)先于請求資源(jsp、servlet、html)之前執(zhí)行, 過濾器是javaweb技術中最為實用的技術之一。

2.2 過濾器作用

Filter的作用是對目標資源(servlet、jsp)進行過濾,其應用場景有:登錄權限檢查,解決網(wǎng)站亂碼,過濾敏感字符等等。

Filter 接口中定義了三個方法:

  • init() :該方法在容器啟動初始化過濾器時被調(diào)用,它在 Filter 的整個生命周期只會被調(diào)用一次,這個方法必須執(zhí)行成功,否則過濾器會不起作用。
  • doFilter() :容器中的每一次請求都會調(diào)用該方法, FilterChain 用來調(diào)用下一個過濾器 Filter。
  • destroy(): 當容器銷毀 過濾器實例時調(diào)用該方法,一般在方法中銷毀或關閉資源,在過濾器 Filter 的整個生命周期也只會被調(diào)用一次。

2.3 過濾器實現(xiàn)

@Component
public class MyFilter implements Filter {
     
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
        System.out.println("Filter 前置");
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 
        System.out.println("Filter 處理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }
 
    @Override
    public void destroy() {
 
        System.out.println("Filter 后置");
    }
}

3 攔截器 (Interceptor)

3.1 攔截器定義

攔截器它是鏈式調(diào)用,一個應用中可以同時存在多個攔截器Interceptor, 一個請求也可以觸發(fā)多個攔截器 ,而每個攔截器的調(diào)用會依據(jù)它的聲明順序依次執(zhí)行。

3.2 攔截器的核心API

SpringMVC攔截器提供三個方法分別是preHandle、postHandle、afterCompletion,我們就是通過這幾個方法來對用戶的請求進行攔截處理的。

  • preHandle() :這個方法將在請求處理之前進行調(diào)用。「注意」:如果該方法的返回值為false ,將視為當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執(zhí)行。
  • postHandle():只有在 preHandle() 方法返回值為true 時才會執(zhí)行。會在Controller 中的方法調(diào)用之后,DispatcherServlet 返回渲染視圖之前被調(diào)用。「有意思的是」:postHandle() 方法被調(diào)用的順序跟 preHandle() 是相反的,先聲明的攔截器 preHandle() 方法先執(zhí)行,而postHandle()方法反而會后執(zhí)行。
  • afterCompletion():只有在 preHandle() 方法返回值為true 時才會執(zhí)行,在整個請求結束之后, DispatcherServlet 渲染了對應的視圖之后執(zhí)行。

3.3 攔截器的實現(xiàn)

SpringMVC攔截器有兩種實現(xiàn)方式:

第一種方式是要定義的Interceptor類要實現(xiàn)了Spring的HandlerInterceptor 接口;

第二種方式是繼承實現(xiàn)了HandlerInterceptor接口的類,比如Spring已經(jīng)提供的實現(xiàn)了HandlerInterceptor接口的抽象類HandlerInterceptorAdapter;

以下是實現(xiàn)一個登錄攔截過程

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class CommonInterceptor  extends HandlerInterceptorAdapter{

        private final Logger log = LoggerFactory.getLogger(CommonInterceptor.class);

        public  static  final  String  LAST_PAGE = "lastPage";
        /** 
         * 在業(yè)務處理器處理請求之前被調(diào)用 
         * 如果返回false 
         *     從當前的攔截器往回執(zhí)行所有攔截器的afterCompletion(),再退出攔截器鏈
         *     
         * 如果返回true 
         *    執(zhí)行下一個攔截器,直到所有的攔截器都執(zhí)行完畢 
         *    再執(zhí)行被攔截的Controller 
         *    然后進入攔截器鏈, 
         *    從最后一個攔截器往回執(zhí)行所有的postHandle() 
         *    接著再從最后一個攔截器往回執(zhí)行所有的afterCompletion() 
         */  
        @Override  
        public boolean preHandle(HttpServletRequest request,  
                HttpServletResponse response, Object handler) throws Exception {            
            if ("GET".equalsIgnoreCase(request.getMethod())) {
                    RequestUtil.saveRequest();
            }
            log.info("==============執(zhí)行順序: 1、preHandle================");  
            String requestUri = request.getRequestURI();
            String contextPath = request.getContextPath();
            String url = requestUri.substring(contextPath.length());         if ("/userController/login".equals(url)) {                  
                    return true;
            }else {               
                    String username =  (String)request.getSession().getAttribute("user"); 
                    if(username == null){
                            log.info("Interceptor:跳轉到login頁面!");
                            request.getRequestDispatcher("/page/index.html").forward(request, response);
                            return false;
                    }else
                            return true;   
           }
            
        }        
        /**
         * 在業(yè)務處理器處理請求執(zhí)行完成后,生成視圖之前執(zhí)行的動作   
         * 可在modelAndView中加入數(shù)據(jù),比如當前時間
         */
        @Override  
        public void postHandle(HttpServletRequest request,  
                HttpServletResponse response, Object handler,  
                ModelAndView modelAndView) throws Exception {   
            log.info("==============執(zhí)行順序: 2、postHandle================");  
            if(modelAndView != null){  //加入當前時間  
                modelAndView.addObject("haha", "測試postHandle");  
            }  
        }        
        /** 
         * 在DispatcherServlet完全處理完請求后被調(diào)用,可用于清理資源等    
         * 當有攔截器拋出異常時,會從當前攔截器往回執(zhí)行所有的攔截器的afterCompletion() 
         */  
        @Override  
        public void afterCompletion(HttpServletRequest request,  
                HttpServletResponse response, Object handler, Exception ex)  
                throws Exception {  
            log.info("==============執(zhí)行順序: 3、afterCompletion================");  
        }  
}

spring-MVC.xml的相關配置

 <!--配置攔截器, 多個攔截器,順序執(zhí)行 -->
    <mvc:interceptors> 
           <mvc:interceptor>
                   <!--  
                       /**的意思是所有文件夾及里面的子文件夾 
                       /*是所有文件夾,不含子文件夾 
                       /是web項目的根目錄
                     --> 
                   <mvc:mapping path="/**" /> 
                   <!-- 需排除攔截的地址 -->  
                   <!--  <mvc:exclude-mapping path="/userController/login"/>  -->
                   <bean id="commonInterceptor" class="org.atguigu.interceptor.CommonInterceptor"></bean> <!--這個類就是我們自定義的Interceptor -->
          </mvc:interceptor> 
          <!-- 當設置多個攔截器時,先按順序調(diào)用preHandle方法,然后逆序調(diào)用每個攔截器的postHandle和afterCompletion方法  -->
    </mvc:interceptors>

web.xml

 <!-- 不攔截靜態(tài)文件 -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/js/*</url-pattern>
        <url-pattern>/css/*</url-pattern>
        <url-pattern>/images/*</url-pattern>
        <url-pattern>/fonts/*</url-pattern>
    </servlet-mapping>

以上代碼也可以在springmvc.xml 這樣寫

<!-- 對靜態(tài)資源文件的訪問-->
<mvc:resources mapping="/images/**"  location="/images/"/> 
<mvc:resources mapping="/css/**"  location="/css/" />
<mvc:resources mapping="/js/**"  location="/js/" /> 
<mvc:resources mapping="/favicon.ico"  location="favicon.ico" />

通過以上兩個對Filter和Interceptor的配置,我們大致也能上手寫項目,下面就針對他們的主要區(qū)別展開敘述

4 過濾器和攔截器的區(qū)別

過濾器和攔截器均體現(xiàn)了AOP的編程思想,都可以實現(xiàn)諸如日志,登錄鑒權等功能,但二者的不同點也是比較多的

  • 攔截器是基于Java的反射機制,而過濾器是基于函數(shù)回調(diào)
  • 攔截器不依賴與Servlet容器,而過濾器依賴Servlet容器
  • 攔截器只能對Controller請求起作用,而過濾器可以對幾乎所有請求起作用
  • 攔截器可以訪問Controller上下文,值棧里的對象,而過濾器不能
  • 在Spring容器的生命周期中,攔截器可以多次調(diào)用,而過濾器只能在容器初始化時被調(diào)用一次

4.1 實現(xiàn)原理不同

過濾器和攔截器底層實現(xiàn)方式大不相同,過濾器是基于函數(shù)回調(diào)的,攔截器則是基于Java的反射機制(動態(tài)代理)實現(xiàn)的

在我們自定義的過濾器中都會實現(xiàn)一個 doFilter()方法,這個方法有一個FilterChain 參數(shù),而實際上它是一個回調(diào)接口。ApplicationFilterChain是它的實現(xiàn)類, 這個實現(xiàn)類內(nèi)部也有一個 doFilter() 方法就是回調(diào)方法。

public interface FilterChain {
 
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
1634109498474.png

ApplicationFilterChain里面能拿到我們自定義的xxxFilter類,在其內(nèi)部回調(diào)方法doFilter()里調(diào)用各個自定義xxxFilter過濾器,并執(zhí)行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
  
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //獲取第pos個filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
  
}

而每個xxxFilter 會先執(zhí)行自身的 doFilter() 過濾邏輯,最后在執(zhí)行結束前會執(zhí),filterChain.doFilter(servletRequest,servletResponse),也就是回調(diào)ApplicationFilterChain的doFilter() 方法,以此循環(huán)執(zhí)行實現(xiàn)函數(shù)回調(diào)。

@Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 
       filterChain.doFilter(servletRequest, servletResponse);
   }

4.2 使用范圍不同

我們看到過濾器 實現(xiàn)的是 javax.servlet.Filter 接口,而這個接口是在Servlet規(guī)范中定義的,也就是說過濾器Filter 的使用要依賴于Tomcat等容器,導致它只能在web程序中使用。


而攔截器(Interceptor) 它是一個Spring組件,并由Spring容器管理,并不依賴Tomcat等容器,是可以單獨使用的。不僅能應用在web程序中,也可以用于Application、Swing等程序中。
3.png

4.3 觸發(fā)時機不同

過濾器 和 攔截器的觸發(fā)時機也不同,我們看下邊這張圖。


4.png

過濾器Filter是在請求進入容器后,但在進入servlet之前進行預處理,請求結束是在servlet處理完以后。

攔截器 Interceptor 是在請求進入servlet后,在進入Controller之前進行預處理的,Controller 中渲染了對應的視圖之后請求結束。

4.4 攔截的請求范圍不同

在上邊我們已經(jīng)同時配置了過濾器和攔截器,再建一個Controller接收請求測試一下。

@Controller
@RequestMapping()
public class Test {
 
    @RequestMapping("/test1")
    @ResponseBody
    public String test1(String a) {
        System.out.println("我是controller");
        return null;
    }
}

過濾器

@Autowired
private TestService testService;
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter 處理中");
        filterChain.doFilter(servletRequest, servletResponse);
 }

攔截器

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor 處理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor 后置");
    }
}

項目啟動過程中發(fā)現(xiàn),過濾器的init()方法,隨著容器的啟動進行了初始化

此時瀏覽器發(fā)送請求,F(xiàn)12 看到居然有兩個請求,一個是我們自定義的 Controller 請求,另一個是訪問靜態(tài)圖標資源的請求。


6.png

看到控制臺的打印日志如下

執(zhí)行順序 :Filter 處理中 -> Interceptor 前置 -> 我是controller -> Interceptor 處理中 -> Interceptor 處理后

Filter 處理中
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor 后置
Filter 處理中

過濾器Filter執(zhí)行了兩次,攔截器Interceptor只執(zhí)行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對Controller中請求或訪問static目錄下的資源請求起作用。

4.5 注入bean情況不同

在實際的業(yè)務場景中,應用到過濾器或攔截器,為處理業(yè)務邏輯難免會引入一些service服務。

下邊我們分別在過濾器和攔截器中都注入service,看看有什么不同?

@Component
public class TestServiceImpl implements TestService {
 
    @Override
    public void a() {
        System.out.println("我是方法A");
    }
}

過濾器中注入service,發(fā)起請求測試一下 ,日志正常打印出“我是方法A”。

@Autowired
private TestService testService;
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 
        System.out.println("Filter 處理中");
        testService.a();   // 調(diào)用service方法  
        filterChain.doFilter(servletRequest, servletResponse);
 }

攔截器中

@Component
public class MyInterceptor implements HandlerInterceptor {
  //  @Autowired
  //  private TestService testService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       // testService.a();
        System.out.println("Interceptor 處理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor 后置");
    }
}

結果:

Filter 處理中
我是方法A
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor 后置

但是在攔截器中注入service ,發(fā)起請求測試,竟然會報錯,別急,原因是加載順序?qū)е碌膯栴}

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Autowired
    private TestService testService;
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        testService.a();
        System.out.println("Interceptor 處理中");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor 后置");
    }
}

7.png

攔截器加載的時間點是SpringContext之前,而Bean又是由Spring容器管理的, 所以在當然不能獲取到bean對象

解決辦法:

我們在注冊攔截器之前,手動將Interceptor進行注入,注意在registry.addInterceptor()注冊的是getMyInterceptor()實例

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
 
    @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
     
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
 
        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

這樣就可以在MyInterceptor攔截器類中使用service的bean

4.6 控制執(zhí)行順序不同

實際開發(fā)過程中,會出現(xiàn)多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優(yōu)先執(zhí)行,就涉及到它們的執(zhí)行順序。

過濾器用@Order注解控制執(zhí)行順序,通過@Order控制過濾器的級別,值越小級別越高越先執(zhí)行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

攔截器默認的執(zhí)行順序,就是它的注冊順序,也可以通過Order手動設置控制,值越小越先執(zhí)行。

@Override
public void addInterceptors(InterceptorRegistry registry) {
 
       registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
       registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
       registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
        
}

看到輸出結果發(fā)現(xiàn),先聲明的攔截器 preHandle() 方法先執(zhí)行,而postHandle()方法反而會后執(zhí)行。

postHandle() 方法被調(diào)用的順序跟 preHandle() 居然是相反的!如果實際開發(fā)中嚴格要求執(zhí)行順序,那就需要特別注意這一點。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor2 處理中
Interceptor1 處理中
Interceptor 后置
Interceptor2 處理后
Interceptor1 處理后

看源碼,我們要知道controller 中所有的請求都要經(jīng)過核心組件DispatcherServlet路由,都會執(zhí)行它的 doDispatch() 方法,而攔截器postHandle()、preHandle()方法便是在其中調(diào)用的。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
     
        try {
         ...........
            try {
            
                // 獲取可以執(zhí)行當前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;
                    }
                }
                // 注意: 執(zhí)行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
 
                // 注意:執(zhí)行Handle【包括我們的業(yè)務邏輯,當拋出異常時會被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
 
                // 注意:執(zhí)行Interceptor中PostHandle 方法【拋出異常時無法執(zhí)行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

看看兩個方法applyPreHandle()、applyPostHandle()具體是如何被調(diào)用的,就明白為什么postHandle()、preHandle() 執(zhí)行順序是相反的了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }
 
        return true;
    }
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

發(fā)現(xiàn)兩個方法中在調(diào)用攔截器數(shù)組 HandlerInterceptor[] 時,循環(huán)的順序竟然是相反的,導致postHandle()、preHandle() 方法執(zhí)行的順序相反。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

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