@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