Spring系列(8)- 過濾器+監聽器+攔截器+AOP 比較

一、前言

在後端項目開發中,會有一些須要基於全局處理的程序。傳統基於Servlet容器的程序中,咱們可使用過濾器和監聽器,在Java 框架中還可使用攔截器,而面向切面編程AOP更是做爲Spring框架中的核心思想被你們所關注。本文一方面從概念上講解Filter、Listener、Interceptor和aop的區別,另外一方面更是從代碼層面講解如何在SpringBoot中應用開發。java

它們的執行順序以下(@ControllerAdvice本文暫不作介紹):web

攔截順序:ServletContextListener> Filter > Interception > AOP > 具體執行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListenerredis

根據實現原理分紅下面兩大類:spring

  1. Filter和Listener:依賴Servlet容器,基於函數回調實現。能夠攔截全部請求,覆蓋範圍更廣,但沒法獲取ioc容器中的bean。
  2. Interceptor和aop:依賴spring框架,基於java反射和動態代理實現。只能攔截controller的請求,能夠獲取ioc容器中的bean。

Filter -> Interceptor -> aop ,攔截的功能愈來愈細緻、強大,尤爲是Interceptor和aop能夠更好的結合spring框架的上下文進行開發。可是攔截順序也是愈來愈靠後,請求是先進入Servlet容器的,越早的過濾和攔截對系統性能的消耗越少。具體選用哪一種方法,就須要開發人員根據實際業務狀況綜合考慮了。數據庫

二、過濾器(Filter)

Filter過濾器是Servlet容器層面的,在實現上基於函數回調,能夠對幾乎全部請求進行過濾。過濾器是對數據進行過濾,預處理過程,當咱們訪問網站時,有時候會發布一些敏感信息,發完之後有的會用*替代,還有就是登錄權限控制等,一個資源,沒有通過受權,確定是不能讓用戶隨便訪問的,這個時候,也能夠用到過濾器。過濾器的功能還有不少,例如實現URL級別的權限控制、壓縮響應信息、編碼格式等等。編程

SpringBoot實現過濾器,常見有三種方式,越複雜功能越強大。後端

2.一、無路徑無順序@Component

這種方式最簡單,直接實現Filter接口,並使用@Component註解標註爲組件自動注入bean。可是缺點是沒辦法設置過濾的路徑,默認是 /* 過濾全部。緩存

Filter接口有 init、doFilter、destroy 三個方法,但 init、destroy 是有默認方法實現,能夠不重寫。服務器

import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;

@Component
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

2.二、有路徑無順序@WebFilter+@ServletComponentScan

這種方式要稍微複雜一點,但更全面。使用 @WebFilter替代 @Component,能夠經過該註解設置過濾器的匹配路徑。不過須要在啓動類中使用@ServletComponentScan。@ServletComponentScan掃描帶@WebFilter、@WebServlet、@WebListener並將幫咱們注入bean。session

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "filter1",urlPatterns = {"/hello/*"})
public class DemoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }

}

2.三、有路徑有順序@Configuration

這種方式徹底經過配置類來實現,在只實現過濾器的接口,並不須要經過任何註解注入IOC容器,都經過配置類來注入。

AFilter.java(BFilter.java也相似)

import javax.servlet.*;
import java.io.IOException;

public class AFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filterA----------");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

FilterConfig.java 配置類

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.kerry.demo.webfilter.filter.AFilter;
import pers.kerry.demo.webfilter.filter.BFilter;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean AFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        AFilter aFilter=new AFilter();
        filterRegistrationBean.setFilter(aFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("AFilter");
        filterRegistrationBean.setOrder(1);
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean BFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        BFilter bFilter=new BFilter();
        filterRegistrationBean.setFilter(bFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("BFilter");
        filterRegistrationBean.setOrder(2);
        return filterRegistrationBean;
    }
}

三、監聽器(Listener )

Listener監聽器也是Servlet層面的,能夠用於監聽Web應用中某些對象、信息的建立、銷燬和修改等動做發生,而後作出相應的響應處理。根據監聽對象,將監聽器分爲3類:

  1. ServletContext:對應application,實現接口ServletContextListener。在整個Web服務中只有一個,在Web服務關閉時銷燬。可用於作數據緩存,例如結合redis,在Web服務建立時從數據庫拉取數據到緩存服務器。
  2. HttpSession:對應session會話,實現接口HttpSessionListener。在會話起始時建立,一端關閉會話後銷燬。可用做獲取在線用戶數量。
  3. ServletRequest:對應request,實現接口ServletRequestListener。request對象是客戶發送請求時建立的,用於封裝請求數據,請求處理完畢後銷燬。可用做封裝用戶信息。

在寫Listener的類時,實現方式和Filter同樣,一樣有兩種實時方式。一種是隻加@Component;另外一種是 @WebListener 和 @ServletComponentScan 配合使用。不過實現接口則根據監聽對象區分,如:ServletContextListener、HttpSessionListener和ServletRequestListener。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class DemoListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContextListener 初始化上下文");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContextListener 銷燬");
    }
}

四、攔截器(Interceptor )

Interceptor攔截器和Filter和Listener有本質上的不一樣,前面兩者都是依賴於Servlet容器,而Interceptor則是依賴於Spring框架,是aop的一種表現,基於Java的動態代理實現的。在SpringBoot中實現攔截器的方式,有點相似於實現過濾器的第三種方式,因此要經過下面兩個步驟。

  1. 聲明攔截器的類:經過實現 HandlerInterceptor接口,實現preHandle、postHandle和afterCompletion方法。
  2. 經過配置類配置攔截器:經過實現WebMvcConfigurer接口,實現addInterceptors方法。

DemoInterceptor.java

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class DemoInterceptor implements HandlerInterceptor{

    /**
     * 預處理回調方法,實現處理器的預處理(如檢查登錄),第三個參數爲響應的處理器
     * 返回值:true表示繼續流程(如調用下一個攔截器或處理器);false表示流程中斷(如登陸檢查失敗),不會繼續調用其餘的攔截器或處理器,此時咱們須要經過response來產生響應
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String methodName = method.getName();
        System.out.println("====攔截到了方法:"+methodName+",preHandle====");
        return true;
    }

    /**
     * 後處理回調方法,實現處理器的後處理(但在渲染視圖以前),此時咱們能夠經過modelAndView(模型和視圖對象)對模型數據進行處理或對視圖進行處理,modelAndView也可能爲null
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("====postHandle====");
    }

    /**
     *整個請求處理完畢回調方法,即在視圖渲染完畢時回調,如性能監控中咱們能夠在此記錄結束時間並輸出消耗時間,還能夠進行一些資源清理,相似於try-catch-finally中的finally,但僅調用處理器執行鏈中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("====afterCompletion====");
    }
}

InterceptorConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pers.kerry.demo.webfilter.interceptor.DemoInterceptor;

/**
 * spring 2.x 之前,經過繼承 WebMvcConfigurerAdapter 類
 * spring 2.x 以後,實現 WebMvcConfigurer 接口
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
    }

}

五、AOP

相比較於攔截器,Spring 的aop則功能更強大,封裝的更細緻,須要單獨引用 jar包。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在定義AOP的類時,不須要和前面攔截器同樣麻煩了,只須要經過註解,底層實現邏輯都經過IOC框架實現好了,涉及到的註解以下:

  • @Aspect:將一個 java 類定義爲切面類。
  • @Pointcut:定義一個切入點,能夠是一個規則表達式,好比下例中某個 package 下的全部函數,也能夠是一個註解等。
  • @Before:在切入點開始處切入內容。
  • @After:在切入點結尾處切入內容。
  • @AfterReturning:在切入點 return 內容以後處理邏輯。
  • @Around:在切入點先後切入內容,並本身控制什麼時候執行切入點自身的內容。原則上能夠替代@Before和@After。
  • @AfterThrowing:用來處理當切入內容部分拋出異常以後的處理邏輯。
  • @Order(100):AOP 切面執行順序, @Before 數值越小越先執行,@After 和 @AfterReturning 數值越大越先執行。

DemoAspect.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Aspect 註解 使之成爲切面類
 * @Component 註解 把切面類加入到IOC容器中
 */
@Aspect
@Component
public class DemoAspect {

    @Pointcut( "execution( public * pers.kerry.demo.webfilter.controller.DemoController.*(..) )")
    public void doPointcut(){
    }

    @Before("doPointcut()")
    public void doBefore(){
        System.out.println("==doBefore==");
    }

    @After("doPointcut()")
    public void doAfter(){
        System.out.println("==doAfter==");
    }

    @AfterReturning("doPointcut()")
    public void doAfterReturning(){
        System.out.println("==doAfterReturning==");
    }

    /**
     * 返回值類型Object,對應全部被攔截方法的返回值類型,不能爲void
     */
    @Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        System.out.println("==doAround.before==");
        Object ret=proceedingJoinPoint.proceed();
        System.out.println("==doAround.after==");
        return ret;
    }
}
相關文章
相關標籤/搜索