SpringBoot 攔截器、過濾器、監聽器

來醉一場發表於2019-01-21

  在工作中使用Web框架,總是避免不了與這些概念打交道,做一下總結,一口氣說完攔截器、過濾器、監聽器。

GitHub原始碼地址

1. 攔截器、過濾器、監聽器區別

  • 攔截器(interceptor):依賴於web框架,基於Java的反射機制,屬於AOP的一種應用。一個攔截器例項在一個controller生命週期內可以多次呼叫。只能攔截Controller的請求。
  • 過濾器(Filter):依賴於Servlet容器,基於函式回掉,可以對幾乎所有請求過濾,一個過濾器例項只能在容器初使化呼叫一次。
  • 監聽器(Listener):web監聽器是Servlet中的特殊的類,用於監聽web的特定事件,隨web應用啟動而啟動,只初始化一次。

2. 有什麼用

  • 攔截器(interceptor):在一個請求進行中的時候,你想幹預它的進展,甚至控制是否終止。這是攔截器做的事。
  • 過濾器(Filter):當有一堆東西,只希望選擇符合的東西。定義這些要求的工具,就是過濾器。
  • 監聽器(Listener):一個事件發生後,只希望獲取這些事個事件發生的細節,而不去幹預這個事件的執行過程,這就用到監聽器

3. 啟動順序

監聽器 >  過濾器 > 攔截器
複製程式碼

4.SpringBoot中的具體實現

(1) 攔截器

  1. 攔截器常用有兩種方式實現
    • 實現HandlerInterceptor介面
    • 繼承HandlerInterceptorAdapter 抽象類
  2. 區別和聯絡
  • HandlerInterceptorAdapter 實現AsyncHandlerInterceptor介面,AsyncHandlerInterceptor介面 繼承HandlerInterceptor介面.
  • AsyncHandlerInterceptor介面多了一個afterConcurrentHandlingStarted方法
  1. 具體方法
  • preHandle //請求過來之後首先走的方法 return true 繼續往下執行
  • postHandle //請求之後返回之前
  • afterCompletion //處理完成之後
  • afterConcurrentHandlingStarted //如果返回一個current型別的變數,會啟用一個新的執行緒。執行完preHandle方法之後立即會呼叫afterConcurrentHandlingStarted,然後新執行緒再以次執行preHandle,postHandle,afterCompletion
  1. 程式碼實現

【注】以下程式碼基於springboot2.0

(1)攔截器

MyInterceptor1 繼承 HandlerInterceptorAdapter

MyInterceptor2 實現 HandlerInterceptor介面

public class MyInterceptor1 extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("startTime", System.currentTimeMillis());
        System.out.println(">>>>> MyInterceptor1 preHandle >>>>>>>>>>>>>>>>>>>>>>");
        return super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long) request.getAttribute("startTime");
        System.out.println("MyInterceptor1 執行:" + (System.currentTimeMillis() - startTime));
        System.out.println(">>>>> MyInterceptor1 postHandle >>>>>>>>>>>>>>>>>>>>>>");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        request.removeAttribute("startTime");
        System.out.println(">>>>> MyInterceptor1 afterCompletion >>>>>>>>>>>>>>>>>>>>>>");
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        super.afterConcurrentHandlingStarted(request, response, handler);
        System.out.println(">>>>> MyInterceptor1 afterConcurrentHandlingStarted >>>>>>>>>>>>>>>>>>>>>>");
    }
}
複製程式碼
public class MyInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("startTime", System.currentTimeMillis());
        System.out.println(">>>>> MyInterceptor2 preHandle >>>>>>>>>>>>>>>>>>>>>>");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long) request.getAttribute("startTime");
        System.out.println("MyInterceptor2 執行:" + (System.currentTimeMillis() - startTime));
        System.out.println(">>>>> MyInterceptor2 postHandle >>>>>>>>>>>>>>>>>>>>>>");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        request.removeAttribute("startTime");
        System.out.println(">>>>> MyInterceptor2 afterCompletion >>>>>>>>>>>>>>>>>>>>>>");
    }
}
複製程式碼

(2)配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
    }
}
複製程式碼

(3)請求

@RestController
@SpringBootApplication
public class SpringbootInterceptorApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootInterceptorApplication.class, args);
    }


    @GetMapping(value = "/hello1")
    public ResponseEntity<String> hello() throws InterruptedException {
        Thread.sleep(500);
        return ResponseEntity.ok("HelloWorld");
    }

    @GetMapping(value = "/hello2")
    public StreamingResponseBody hello2() throws InterruptedException {
        Thread.sleep(500);
        return (OutputStream outputStream) -> {
            outputStream.write("success".getBytes());
            outputStream.flush();
            outputStream.close();
        };
    }

    @GetMapping(value = "/hello3")
    public Future<String> hello3() throws InterruptedException {
        Thread.sleep(500);
        return new AsyncResult<>("Hello");
    }
}
複製程式碼

(4) 執行結果

  1. 請求/hello1

    >>>>> MyInterceptor1 preHandle >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor2 preHandle >>>>>>>>>>>>>>>>>>>>>>
    MyInterceptor2 執行:516
    >>>>> MyInterceptor2 postHandle >>>>>>>>>>>>>>>>>>>>>
    MyInterceptor1 執行:516
    >>>>> MyInterceptor1 postHandle >>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor2 afterCompletion >>>>>>>>>>>>>>>>
    >>>>> MyInterceptor1 afterCompletion >>>>>>>>>>>>>>>>
    複製程式碼

    執行按preHandle > postHandle > afterCompletion

  2. 請求/hello2 或 /hello3

    >>>>> MyInterceptor1 preHandle >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor2 preHandle >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor1 afterConcurrentHandlingStarted >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor1 preHandle >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor2 preHandle >>>>>>>>>>>>>>>>>>>>>>
    MyInterceptor2 執行:1
    >>>>> MyInterceptor2 postHandle >>>>>>>>>>>>>>>>>>>>>>
    MyInterceptor1 執行:1
    >>>>> MyInterceptor1 postHandle >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor2 afterCompletion >>>>>>>>>>>>>>>>>>>>>>
    >>>>> MyInterceptor1 afterCompletion >>>>>>>>>>>>>>>>>>>>>>
    複製程式碼

    MyInterceptor1 執行順序 preHandle > afterConcurrentHandlingStarted > preHandle > postHandle >afterCompletion

    MyInterceptor2 執行順序 preHandle > preHandle > postHandle > afterCompletion

綜上.對於concurrent型別的返回值,spring會啟用一個新的執行緒來處理concurrent型別訊息,在新的執行緒中會重新呼叫preHandle方法。

(2) 過濾器

(1) 過濾器

public class MyFilter1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println(filterConfig.getInitParameter("initParam"));
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter1 >>>>>>>>>>>");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
複製程式碼

(2) 配置

  • 第一種方式
@Bean
public FilterRegistrationBean<MyFilter1> filterRegistrationBean() {
    FilterRegistrationBean<MyFilter1> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.addUrlPatterns("/*");//過濾所有
    filterRegistrationBean.setFilter(new MyFilter1());
    filterRegistrationBean.setOrder(1);
    filterRegistrationBean.addInitParameter("initParam", "initOk");
    return filterRegistrationBean;
}
複製程式碼
  • 第二種方式
@Bean
public MyFilter1 myFilter() {
    return new MyFilter1();
}
複製程式碼
  • 第三種方式
@WebFilter("/test/*")
public class MyFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter2");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("DoFilter 2");
    }
}
複製程式碼

通過@WebFilter("/test/*")註解,首先需要@ServletComponentScan("com.jiuxian")

Filter 全域性攔截的配置(/*)和 Interceptor(/**)有所區別需要注意

(3) 監聽器

(1) 監聽器

public class MyListener1 implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("MyListener1 ... ");
    }
}
複製程式碼

(2) 配置方式和Filter類似

  • 第一種方式
 @Bean
public ServletListenerRegistrationBean<MyListener1> registrationBean() {
    ServletListenerRegistrationBean<MyListener1> servletListenerRegistrationBean
            = new ServletListenerRegistrationBean<>();
    servletListenerRegistrationBean.setListener(new MyListener1());
    return servletListenerRegistrationBean;
}
複製程式碼
  • 第二種方式
@Bean
public MyListener1 myListener1() {
    return new MyListener1();
}
複製程式碼
  • 第三種方式
@WebListener
public class MyListener2 implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("MyListener2");
    }
}
複製程式碼

使用@WebListener註解,首先需要@ServletComponentScan("com.jiuxian")

【注】以上程式碼基於springboot2.0

GitHub原始碼地址

相關文章