談談 Spring 的過濾器和攔截器

fuxing.發表於2024-05-13

前言

我們在進行 Web 應用開發時,時常需要對請求進行攔截或處理,故 Spring 為我們提供了過濾器和攔截器來應對這種情況。那麼兩者之間有什麼不同呢?本文將詳細講解兩者的區別和對應的使用場景。

(本文的程式碼實現首先是基於 SpringBoot,Spring 的實現方式僅簡單描述)




image.png

1. 過濾器

1.1. 什麼是過濾器


過濾器(Filter),是 Servlet 規範規定的,在 Servlet 前執行的。用於攔截和處理 HTTP 請求和響應,可用於身份認證、授權、日誌記錄和設定字符集(CharacterEncodingFilter)等場景。


過濾器位於整個請求處理流程的最前端,因此在請求到達 Controller 層前,都會先被過濾器處理。


過濾器可以攔截多個請求或響應,一個請求或響應也可以被多個過濾器攔截


1.2. 如何建立過濾器


Filter 的生命週期對應的三個關鍵方法:

方法 說明
init() 當請求發起時,會呼叫 init() 方法初始化 Filter 例項,僅初始化一次。若需要設定初始化引數的時可呼叫該方法。
doFilter() 攔截要執行的請求,對請求和響應進行處理。
destroy() 請求結束時呼叫該方法銷燬 Filter 的例項。

下面將介紹二種方法建立 Filter。

1.2.1 實現 Filter 介面


1.建立 Filter 處理類,實現javax.servlet.Filter介面,加上@WebFilter註解配置攔截 Url,但是不能指定過濾器執行順序,也可透過web.xml配置。

@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 用於完成 Filter 的初始化
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
        System.out.println("過濾器已經攔截成功!!!");

        // 執行該方法之前,即對使用者請求進行預處理;執行該方法之後,即對伺服器響應進行後處理。
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        // 用於 Filter 銷燬前,完成某些資源的回收;
        Filter.super.destroy();
    }
}

2.在啟動類新增註解@ServletComponentScan ,讓 Spring 可以掃描到。

@SpringBootApplication
@ServletComponentScan
public class MyFilterDemoApplication {

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

}

3.建立 Controller 發起 Url 請求。
@RestController
public class MyFilterController {

    @GetMapping("/testFilter")
    public String testFilter(){
        return "Hello World";
    }
}

攔截結果

image.png

1.2.2. 透過@Component 註解


1.建立 Filter 處理類,實現javax.servlet.Filter介面,加@Component註解。

  • 可以使用@Order註解保證過濾器執行順序,不加則按照類名排序。
  • 過濾器不能指定攔截的url , 只能預設攔截全部
@Component
@Order(1)
public class MyComponentFilter1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
        System.out.println("我是過濾器1已經攔截成功!!!");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

@Component
@Order(2)
public class MyComponentFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
    
        System.out.println("我是過濾器2已經攔截成功!!!");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

2-3 步驟同 1.2.1,結果如下。

image.png


2. 攔截器

2.1. 什麼是攔截器

攔截器(Interceptor),和Servlet無關,由Spring框架實現。可用於身份認證、授權、日誌記錄、預先設定資料以及統計方法的執行效率等。


一般基於 Java 的反射機制實現,屬於AOP的一種運用。

目前瞭解的 Spring 中的攔截器有:

  • HandlerInterceptor
  • MethodInterceptor

2.2. HandlerInterceptor 攔截器

2.2.1簡介


HandlerInterceptor 類似 Filter,攔截的是請求地址 ,但提供更精細的的控制能力,這裡注意下必須過DispatcherServlet 的請求才會被攔截。


它允許你在請求處理前、處理後以及檢視渲染完成前執行自定義邏輯,可以用來對請求地址做一些認證授權、預處理,也可以計算一個請求的響應時間等,還可以處理跨域(CORS)問題


簡單的執行流程描述:

  1. 請求到達 DispatcherServlet,然後傳送至 Interceptor,執行 preHandler;
  2. 請求到達 Controller,請求結束後,執行 postHandler。

2.2.2如何實現

  1. 建立 Interceptor 類,實現HandlerInterceptor介面,重寫 3 個方法,加@Component註解。

image.png

@Component
public class MyHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        //請求開始時間
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        System.out.println("startTime : " +  new Date(startTime));
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        
        long startTime = (Long)request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        // 統計耗時
        long executeTime = endTime - startTime;
        System.out.println("executeTime : " + executeTime + "ms");

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

2.配置攔截器,實現WebMvcConfigurer介面,加@Configuration註解並重寫addInterceptors方法。

@Configuration
public class MyWebConfigurer implements WebMvcConfigurer {

    @Resource
    private MyHandlerInterceptor myHandlerInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List<String> patterns = new ArrayList<>();

        patterns.add("/test/handlerInterceptor");

        registry.addInterceptor(myHandlerInterceptor)
                .addPathPatterns(patterns) // 需要攔截的請求
                .excludePathPatterns(); // 不需要攔截的請求
    }
}

攔截結果如下:

image.png

Spring 專案如何實現?

可透過使用mvc:interceptors標籤來宣告需要加入到 SpringMVC 攔截器鏈中的攔截器。


2.3. MethodInterceptor 攔截器

2.3.1. 簡介

MethodInterceptor 是 AOP 中的攔截器,它攔截的目標是方法,可以不是 Controller 中的方法。


在對一些普通的方法上的攔截可以使用該攔截器,這是 HandlerInterceptor 無法實現的。

可用來進行方法級別的身份認證、授權以及日誌記錄等,也可基於自定義註解實現一些通用的方法增強功能

2.3.2. 如何實現

MethodInterceptor 是基於 AOP 實現的,所以根據不同的代理有多種實現方式,更多的實現方式和原理我將在整理 Spring AOP 的時候詳細接受。

這裡我將介紹透過BeanNameAutoProxyCreator自動代理實現攔截。該類是基於 Bean 名稱的自動代理,可以針對特定的Bean進行個性化的 AOP 配置。

1.建立簡單的需要攔截的方法。

public interface UserService {
    public String getUser();
}
@Component
public class UserServiceImpl implements UserService{

    @Override
    public String getUser() {
        return "我是福星";
    }
}


2.建立 Interceptor 類,實現MethodInterceptor介面,重寫invoke方法,加@Component註解。

@Component
public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("進入攔截,方法執行前,攔截方法是:" + invocation.getMethod().getName());
        Object result = invocation.proceed();
        System.out.println("方法執行後");
        return result;
    }

}

3.配置自動代理,加@Configuration註解並建立自動代理BeanNameAutoProxyCreator

@Configuration
public class MyMethodConfigurer {
    @Resource
    private MyMethodInterceptor myMethodInterceptor;


    @Bean
    public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
        // 使用BeanNameAutoProxyCreator來建立代理
        BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();

        // 指定一組需要自動代理的Bean名稱,Bean名稱可以使用*萬用字元
        beanNameAutoProxyCreator.setBeanNames("user*");

        //設定攔截器名稱,這些攔截器是有先後順序的
        beanNameAutoProxyCreator.setInterceptorNames("myMethodInterceptor");
        return beanNameAutoProxyCreator;
    }

}

發起請求後,呼叫該方法前會進行攔截。

image.png


3. 總結

過濾器一般用於對 Servlet 請求和響應進行通用性的處理,通常關注請求和響應內容,而不涉及具體的業務邏輯。而攔截器用於對 SpringMVC 的請求和響應進行特定的業務處理,通常與控制器層的請求處理有關。


不論是過濾器和攔截器,都可以有多個。執行順序上攔截器是由配置中的順序決定,而過濾器可透過@Component+@Order決定,也可由web.xml檔案中的配置順序決定。


總的來說,攔截器的使用更加靈活,Filter 能做的事情,攔截器也能做。Filter 一般用於對 URL 請求做編碼處理、過濾無用引數、安全校驗(比如登陸態校驗),如果涉及業務邏輯上的,還是建議用攔截器。

相關文章