@WebFilter 的使用及采坑


@WebFilter
@WebFilter 用於將一個類聲明為過濾器,該注解將會在部署時被容器處理,容器將根據具體的屬性配置將相應的類部署為過濾器。該注解具有下表給出的一些常用屬性 ( 以下所有屬性均為可選屬性,但是 value、urlPatterns、servletNames 三者必需至少包含一個,且 value 和 urlPatterns 不能共存,如果同時指定,通常忽略 value 的取值 ):

表 3. @WebFilter 的常用屬性

 

 

 

下面是一個簡單的示例:

@WebFilter(servletNames = {"SimpleServlet"},filterName="SimpleFilter") 
public class LessThanSixFilter implements Filter{...}

 

如此配置之后,就可以不必在 web.xml 中配置相應的 <filter> 和 <filter-mapping> 元素了,容器會在部署時根據指定的屬性將該類發布為過濾器。它等價的 web.xml 中的配置形式為:

<filter> 
  <filter-name>SimpleFilter</filter-name> 
  <filter-class>xxx</filter-class> 
</filter> 
<filter-mapping> 
  <filter-name>SimpleFilter</filter-name> 
  <servlet-name>SimpleServlet</servlet-name> 
</filter-mapping>

 

由上文可知,使用@WebFilter相當於配置了web.xml,現在用eclipse自動生成Filter時,默認是提供這個注解的,如下所示

package webcase;
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
 
/**
 * Servlet Filter implementation class CountFilter
 */
@WebFilter("/CountFilter")
public class CountFilter implements Filter {
    private int count;
    private String param;
    private FilterConfig fConfig;
    /**
     * Default constructor.
     */
    /*public CountFilter() {
        // TODO Auto-generated constructor stub
    }*/
 
    /**
     * @see Filter#destroy()
     */

我們知道,tomcat根據<filter-mapping>的順序初始化Filter,由於上面的代碼包含@WebFilter("/CountFilter"),相當於在web.xml中對同一個過濾器CountFilter設置了兩次<filter>和<filter-mapping>,故這個過濾器會初始化兩次,故當@WebFilter設置的過濾器被初始化時,String param=getInitParameter("count")得到的字符串為空,調用Integer.parseInt(param)時引發NumberFormatException異常。去掉@WebFilter("/CountFilter")后則一切正常。

采坑記錄:

業務需求背景:
項目采用微服務架構,在各個服務前面配置一個網關,通過SpringCloud生態中的Zuul組件實現。
該網關同時負責頁面調度,在各個單頁面應用子產品的頁面之間進行調度。
ZuulFilter挺有意思,對於本服務的Controller請求不會進行攔截,因此需要針對頁面請求做一個認證鑒權的Filter。

實現第一版
首先實現一個Filter進行鑒權及頁面重定向(未登錄認證狀態下跳轉到登錄頁面)。
大體邏輯如下:
①通過WebFilter進行Filter聲明,這樣容器在進行部署的時候就會處理該Filter,創建實例並創建配置對象FilterConfig,然后會將該Filter應用到urlPatterns所指定的url;
②在init方法中獲取到初始化參數,自定義的excludedUrls,作為成員在后續執行過濾邏輯的時候使用;
③在doFilter中進行url的鑒定,如果需要執行認證鑒權處理,則執行相應邏輯。不滿足條件的情況下重定向到登錄頁;
④Filter類增加Component注解,讓該Filter被容器管理。

@Component //這樣加對嗎?
@WebFilter(filterName = "WebAuthFilter", urlPatterns = "/web/*",
        initParams = {
            @WebInitParam(name = "excludedUrls", value = "/web/login")
        }
)
public class WebAuthFilter implements Filter {

    private List<String> excludedUrlList;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String excludedUrls = filterConfig.getInitParameter("excludeUrls");
        excludedUrlList = Splitter.on(",").omitEmptyStrings().splitToList(excludedUrls);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String url = ((HttpServletRequest) request).getRequestURI();
        if (excludedUrlList.contains(url)) {
            chain.doFilter(request, response);
        } else {
            String sToken = ((HttpServletRequest) request).getHeader("Authorization");
            if (sToken != null) {
                Map<String, Object> map = TokenUtils.parseToken(sToken);
                if (map == null) {
                    ((HttpServletResponse)response).sendRedirect("/web/login");
                }
            } else {
                ((HttpServletResponse)response).sendRedirect("/web/login");
            }
        }
    }

    @Override
    public void destroy() {

    }
}

然后在SpringBoot的Application中增加注解@ServletComponentScan,這樣容器會掃描到@Component注解的Filter。

問題出現
出現的問題是:訪問的url為/user/*或者/product/*的時候,該過濾器也執行了!
也就是說,WebFilter注解配置的urlPatterns沒有起作用。
問題定位:
在查看容器啟動日志的時候,發現WebAuthFilter被兩次注冊,兩次映射:

 

 

 

從上圖可以看到,WebAuthFilter這個Filter是我們自己定義的,它被做了兩次映射,而且兩次映射的名字不同(WebAuthFilter和webAuthFilter),分別映射到的URL是“/web/*”和”“/*”。其中WebAuthFilter是我們自己命名的。
這樣就解釋了為什么所有的URL都會被該Filter處理。

問題定位
WebAuthFilter的第一次映射容易理解,是我們自己通過@WebFilter定義的。
那么webAuthFilter是誰給映射的呢?
必然是Spring容器處理的。
在跟蹤源碼的時候找到AbstractFilterRegistrationBean抽象類,該類中有一個方法onStartup,應該是容器啟動的時候執行的,做的是一些Bean注冊的工作。該方法最后調用了configure,在該方法中進行了映射處理。

if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
            this.logger.info("Mapping filter: '" + registration.getName() + "' to: "
                    + Arrays.asList(DEFAULT_URL_MAPPINGS));
            registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
                    DEFAULT_URL_MAPPINGS);
        }
        else {
            if (!servletNames.isEmpty()) {
                this.logger.info("Mapping filter: '" + registration.getName()
                        + "' to servlets: " + servletNames);
                registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
                        servletNames.toArray(new String[servletNames.size()]));
            }
            if (!this.urlPatterns.isEmpty()) {
                this.logger.info("Mapping filter: '" + registration.getName()
                        + "' to urls: " + this.urlPatterns);
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
                        this.urlPatterns.toArray(new String[this.urlPatterns.size()]));
            }
        }

在servletNames和urlPatterns為空的情況下,進行了缺省映射,即映射到“/*”。
置於servletNames和urlPatterns為空的情況,這里沒有深究了。
那么,為什么會出現定義的WebAuthFilter被兩次注冊的情況呢?
仔細分析了一下,認為可能的原因是:@Component和@WebFilter雙重注冊導致的。

解決辦法
解決辦法一@WebFilter
在這種情況下,去掉了@Component注解,再次啟動服務。查看日志,發現該Filter僅被映射一次,通過瀏覽器訪問相應的url也表現正確。

解決辦法二@Component
這種情況下,保留了@Component注解,那么要進行配置的urlPatterns怎么處理呢?
通過FilterRegistrationBean進行@Bean聲明,查看源碼知道,onStartup進行注冊的時候,實際上也是找到了各類RegistrationBean然后分別注冊,配置映射。
有各種類型的RegistrationBean:
①AbstractFilterRegistrationBean;
②FilterRegistrationBean;
③ServletListenerRegistrationBean;
④ServletRegistrationBean;
那么我們自然可以通過自聲明一個FilterRegistrationBean來進行注冊。這種處理方式如下:
去掉FIlter上的@WebFilter注解,增加如下的Configuration類:

@Configuration
public class WebAuthFilterConfig {

    @Bean
    public FilterRegistrationBean webAuthFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(webAuthFilter());
        registration.setName("WebAuthFilter");
        registration.addUrlPatterns("/web/*");
        registration.addInitParameter("excludeUrls", "/web/login");
        registration.setOrder(0);
        return registration;
    }

    @Bean
    public Filter webAuthFilter() {
        return new WebAuthFilter();
    }
}

如此處理,也能達到同樣的效果。

經過對比,當然第一種解決方案更直白,更簡潔。

后述:網上的很多東西都是帶着坑的,直接搬過來用真的有風險!

參考鏈接:

https://blog.csdn.net/weixin_42114097/article/details/81530628

https://blog.csdn.net/achang07/article/details/79282789

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM