首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring 源碼深度解析:DelegatingFilterProxy 如何橋接 Servlet Filter 與 Spring Bean

     編輯:Java 相關知識

在 Spring Web 應用中,DelegatingFilterProxy 是連接 Servlet 容器 Filter 鏈與 Spring IOC 容器 Bean 的關鍵橋樑。本文深入源碼,揭示其初始化流程、委託機制、目標 Bean 查找策略、生命周期管理及常見配置陷阱。

一、 整體架構與類繼承關係

javax.servlet.Filter (接口)
    ↑ 實現
org.springframework.web.filter.GenericFilterBean (抽象類)
    ↑ 繼承
org.springframework.web.filter.DelegatingFilterProxy (核心類)
  • GenericFilterBean 實現 Filter.init(FilterConfig),內部調用模板方法 initFilterBean(),留給子類實現具體初始化邏輯。
  • DelegatingFilterProxy 重寫 initFilterBean(),完成「從 Web.xml filter-name 解析目標 Bean 名 → 獲取 WebApplicationContext → 從 IOC 容器獲取目標 Filter Bean → 初始化委託對象」全流程。

二、 核心源碼流程解析

1. initFilterBean():初始化入口

@Override
protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
        if (this.delegate == null) {
            // 1. 若未指定 targetBeanName,默認使用 filter-name
            if (this.targetBeanName == null) {
                this.targetBeanName = getFilterName();
            }
            // 2. 獲取 Spring 根上下文
            WebApplicationContext wac = findWebApplicationContext();
            if (wac != null) {
                // 3. 早期初始化委託對象(若上下文已就緒)
                this.delegate = initDelegate(wac);
            }
            // 若上下文未就緒,延遲到首次 doFilter 時懶加載
        }
    }
}

2. findWebApplicationContext():上下文查找

  • 優先查找 WebApplicationContextUtils.getWebApplicationContext(servletContext)(根上下文,ContextLoaderListener 初始化)。
  • 支持自定義 contextAttribute 指定上下文屬性名。
  • 若未找到,拋出 IllegalStateException 或延遲初始化。

3. initDelegate(WebApplicationContext wac):委託對象創建

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    // 1. 從 IOC 容器獲取目標 Filter Bean(必須實現 javax.servlet.Filter)
    Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
    
    // 2. 若配置 targetFilterLifecycle=true,調用委託對象的 init(FilterConfig)
    if (isTargetFilterLifecycle()) {
        delegate.init(getFilterConfig());
    }
    return delegate;
}

4. doFilter():委託過濾執行

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws ServletException, IOException {
    
    // 懶加載:首次請求時若 delegate 為 null,嘗試初始化
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
        synchronized (this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }
    }
    
    if (delegateToUse == null) {
        throw new ServletException("Delegate Filter 未找到: " + getTargetBeanName());
    }
    
    // 真正執行業務過濾邏輯
    delegateToUse.doFilter(request, response, chain);
}

三、 關鍵配置要點

web.xml 配置

<filter>
    <filter-name>userInfoFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>customFilterBean</param-value> <!-- 可選:指定 Bean 名,默認同 filter-name -->
    </init-param>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value> <!-- 可選:是否調用目標 Filter 的 init/destroy,默認 false -->
    </init-param>
</filter>

<filter-mapping>
    <filter-name>userInfoFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

applicationContext.xml 配置

<bean id="userInfoFilter" class="com.example.filter.UserInfoFilter">
    <!-- 注入 Service、Repository 等 Spring Bean -->
    <property name="userService" ref="userService"/>
</bean>

核心約定:web.xml 中的 <filter-name> 必須與 Spring 配置文件中目標 Filter Bean 的 id/name 完全一致(除非顯式指定 targetBeanName)。

四、 生命周期管理細節

| 階段 | DelegatingFilterProxy 行為 | 目標 Filter Bean 行為 | |------|---------------------------|----------------------| | 容器啟動 | init(FilterConfig) → initFilterBean() → 查找/創建 delegate | 若 targetFilterLifecycle=true,調用 init(FilterConfig) | | 請求處理 | doFilter → 懶加載 delegate → delegate.doFilter | 執行業務邏輯 | | 容器關閉 | destroy() → 若 targetFilterLifecycle=true 調用 delegate.destroy() | 執行銷毀邏輯 |

五、 常見坑點與避坑指南

  1. Bean 名不匹配:filter-name ≠ Bean id → NoSuchBeanDefinitionException。統一命名規範或顯式配置 targetBeanName。
  2. 目標 Bean 未實現 Filter 接口:getBean(..., Filter.class) 會拋 BeanNotOfRequiredTypeException。確保實現 javax.servlet.Filter。
  3. 上下文未就緒導致懶加載失敗:ContextLoaderListener 必須在 DelegatingFilterProxy 之前初始化(web.xml 順序)。
  4. 重複初始化風險:targetFilterLifecycle=true 時,容器調用 DelegatingFilterProxy.init 會初始化 delegate,Spring 容器刷新時可能再次初始化同一 Bean。建議保持默認 false,由 Spring 管理完整生命周期。
  5. Spring Boot 環境:無需 web.xml,使用 FilterRegistrationBean 註冊 Filter,直接注入 Spring Bean,更簡潔。

六、 總結

DelegatingFilterProxy 通過模板方法模式在 GenericFilterBean.initFilterBean() 中完成「Servlet Filter 名 → Spring Bean 名 → IOC 容器查找 → 委託對象創建 → 生命周期綁定」的橋接。掌握其初始化時機、懶加載邏輯、targetFilterLifecycle 語義、Bean 名匹配規則,是排查 Spring Web 過濾器不生效、雙重初始化、上下文找不到等經典問題的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved