Spring 過濾器Filter總結

一直以來沒有仔細研究過Filter和Intercepter,意識到過濾器的攔截器的認識很模糊。所以仔細學習了一下。其實還是很簡單的。本來想把Filter和Interceptor寫在一起,文章太長了還是分兩篇寫吧~

區別

過濾器和攔截器最大的區別在於:過濾器是servlet的規範規定的,只能用於過濾請求,而interceptor是Spring裏面基於切面編程的一種實現。

過濾器作用於請求到達servlet之前,在spring中也就是在dispacherServlet之前。而攔截器最早只能作用於請求到達servlet之後。

基於這個特點,過濾器經常被用於處理請求的編碼格式,session,日誌等。

業務邏輯大多實現在攔截器中。

Filter

Spring的過濾器在Spring-web的包裏。

現有的filter類結構圖如下,這是直接從網上找的圖,懶得自己再畫了。
這裏寫圖片描述

通常實現攔截器的方式:

  • 實現javax.servlet.Filter接口,Filter是servlet包的定義的接口,現有的這種實現方法有CompositeFilter
  • 繼承抽象類GenericFilterBean 這個類實現了Filter接口。現有的實現有DelegatingFilterProxy
  • 繼承抽象類OncePerRequestFilter 這個類是GenericFilterBean 的子類。現有的實現類有:CharacterEncodingFilterHiddenHttpMethodFilterHttpPutFormContentFilterRequestContextFilterShallowEtagHeaderFilter
  • 繼承抽象類AbstractRequestLoggingFilter,該類爲OncePerRequestFilter的直接子類,這一類過濾器包括CommonsRequestLoggingFilterLog4jNestedDiagnosticContextFilterServletContextRequestLoggingFilter

GenericFilterBean

從類結構圖可以看到,這個類是Spring中直接實現了servlet的Filter的抽象類。所有的Filter都是基於這個類來實現的。

類的定義如下(Spring4.3):

public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {...}

實現的接口有:

  • Filter:過濾器的實現
  • BeanNameAware:setBeanName方法,便於Bean管理器生成Bean
  • EnvironmentAware:用於指明Bean的運行環境
  • EnvironmentCapable:用於獲取bean的運行環境
  • ServletContextAware:實現該接口的setServletContextAware方法,指明ServletContext
  • InitializingBean:實現該接口的afterPropertiesSet方法,在Bean初始化時設置屬性
  • DisposableBean :實現該接口的destroy方法,用於回收資源。

先後執行的方法爲:init-doFilter-destroy。

filter可以在request執行之前進行處理,也可以在request執行之後進行處理。只需要在調用filterChain的doFilter方法之前和之後加入自己的處理邏輯就可以了。

沒有實現Filter接口的doFilter方法,是在子類OncePerRequestFilterDelegatingFilterProxy中實現的。接下來看它的兩個子類。

OncePerRequestFilter

OncePerRequestFilter 依然是一個抽象類。實現了doFilter方法。但是真正要實現的邏輯在doFilterInternal() 方法中,這個方法由子類自己實現。所以如果要通過實現OncePerRequestFilter 類來實現過濾器,需要實現doFilterInternal() 方法

 public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            HttpServletRequest httpRequest = (HttpServletRequest)request;
            HttpServletResponse httpResponse = (HttpServletResponse)response;
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
            if (!hasAlreadyFilteredAttribute && !this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
                request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

                try {
                    this.doFilterInternal(httpRequest, httpResponse, filterChain);
                } finally {
                    request.removeAttribute(alreadyFilteredAttributeName);
                }
            } else {
                filterChain.doFilter(request, response);
            }

        } else {
            throw new ServletException("OncePerRequestFilter just supports HTTP requests");
        }
    }

DelegatingFilterProxy

這個類是具體的實現類,不是抽象的。

因爲Filter是servlet容器規範的,會在servlet啓動的時候就初始化,這個時候Spring的context還沒有被初始化。當我們需要在Filter裏面初始化一些spring的bean或者spring的特性時默認的Filter實現就不行。

比如我們如果要在Filter裏注入一個用於鑑權的Service,因爲在Filter初始化時spring容器還沒有啓動,因此這樣不能實現我們的需求,這種情況下DelegatingFilterProxy可以實現我們的需求,它可以作爲Filter在servlet容器中被啓動,在使用的時候再去spring容器中獲取被代理的對象。這樣被代理對象的初始化就可以和普通spring的bean一樣使用一些複雜的依賴注入等。

從類名可以看出,是一個委派過濾器代理。它作爲一個過濾器沒有實現任何邏輯功能。只是執行它所代理的類的方法。

看一下類的申明:

public class DelegatingFilterProxy extends GenericFilterBean {
    private String contextAttribute;
    private WebApplicationContext webApplicationContext;
    private String targetBeanName;
    private boolean targetFilterLifecycle;
    private volatile Filter delegate;
    private final Object delegateMonitor;
    ...
    }

在使用時,傳入targetBeanName,它會通過這個beanName找到對用的bean,然後在doFilter中調用這個bean的doFilter方法。
具體:

 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            Object var5 = this.delegateMonitor;
            synchronized(this.delegateMonitor) {
            //獲取實際的被代理的對象
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }
//從spring容器中獲取bean
                    delegateToUse = this.initDelegate(wac);
                }

                this.delegate = delegateToUse;
            }
        }
//調用被代理類的doFilter方法
        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }

可以看出,如果要通過DelegatingFilterProxy 來實現過濾器,可以自己定義一個類實現Filter接口,把這個類的beanName注入到DelegatingFilterProxy的申明中或者filter-name和bean的name相同:

<filter>  
        <filter-name>myFilter</filter-name>  
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
        <init-param>  
            <param-name>targetFilterLifecycle</param-name>  
            <param-value>true</param-value>  
        </init-param>  
    </filter>
<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>*.htm</url-pattern>
</filter-mapping>

或者

<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>myFilter</param-value>
    </init-param>
</filter>

filter的生命週期的方法Filter.init和Filter.destory是由servlet容器來執行的,對於這個代理過濾器,init和destroy 方法默認是不會調用被代理對象來執行的。如果希望spring容器來管理被代理對象的這些生命週期,需要把targetFilterLifecycle參數設置爲true(默認是false),這樣當servlet容器執行init和destory方法時DelegatingFilterProxy也會代理給被代理對象。

spring對targetFilterLifecycle參數的解釋:
Default is “false”; target beans usually rely on the Spring application context for managing their lifecycle. Setting this flag to “true” means that the servlet container will control the lifecycle of the target Filter, with this proxy delegating the corresponding calls.

Spring官方的解釋:
Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that implements the Filter interface. Supports a “targetBeanName” filter init-param in web.xml, specifying the name of the target bean in the Spring application context.
web.xml will usually contain a DelegatingFilterProxy definition, with the specified filter-name corresponding to a bean name in Spring’s root application context. All calls to the filter proxy will then be delegated to that bean in the Spring context, which is required to implement the standard Servlet Filter interface.

This approach is particularly useful for Filter implementation with complex setup needs, allowing to apply the full Spring bean definition machinery to Filter instances. Alternatively, consider standard Filter setup in combination with looking up service beans from the Spring root application context.

NOTE: The lifecycle methods defined by the Servlet Filter interface will by default not be delegated to the target bean, relying on the Spring application context to manage the lifecycle of that bean. Specifying the “targetFilterLifecycle” filter init-param as “true” will enforce invocation of the Filter.init and Filter.destroy lifecycle methods on the target bean, letting the servlet container manage the filter lifecycle.

As of Spring 3.1, DelegatingFilterProxy has been updated to optionally accept constructor parameters when using Servlet 3.0’s instance-based filter registration methods, usually in conjunction with Spring 3.1’s WebApplicationInitializer SPI. These constructors allow for providing the delegate Filter bean directly, or providing the application context and bean name to fetch, avoiding the need to look up the application context from the ServletContext.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章