JavaWeb三大組件之Filter學習詳解

JavaWeb三大組件之Filter學習詳解

Filter基本上能夠說存在全部的JavaWeb項目中,好比最基本的一個請求參數的編碼CharacterEncodingFilter,你們通常都會配置下,那麼filter是幹嗎的呢?java

本篇將主要集中在fitler的如下幾個知識點:web

  • 幹嗎的
  • 怎麼用
  • 多個Filter執行的前後順序
  • 注意事項

I. 基本知識

Filter稱之爲過濾器,是用來作一些攔截的任務, 在Servlet接受請求以前,作一些事情,若是不知足限定,能夠拒絕進入Servletspring

arch

從上面的圖,能夠看出一個Filter的工做流程:安全

一個http請求過來以後mvc

  • 首先進入filter,執行相關業務邏輯
  • 若斷定通行,則進入Servlet邏輯,Servlet執行完畢以後,又返回Filter,最後在返回給請求方
  • 斷定失敗,直接返回,不須要將請求發給Servlet

經過上面的流程,能夠推算使用場景:app

  • 在filter層,來獲取用戶的身份
  • 能夠考慮在filter層作一些常規的校驗(如參數校驗,referer校驗等)
  • 能夠在filter層作穩定性相關的工做(如全鏈路打點,能夠在filter層分配一個traceId;也能夠在這一層作限流等)

1. 基本使用姿式

要使用一個Filter,一半須要兩步,實現Filter接口的自定義類,web.xml中對filter的定義curl

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
	
	
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;


    public void destroy();
}

主要就三個方法,從命名來看,async

  • 也比較清晰,在建立Filter對象的時候,調用 init 方法
  • 銷燬Filter對象的時候,調用 destroy 方法
  • 當請求過來以後,調用 doFilter,也就是主要的業務邏輯所在了

詳細case後面再說ide

接下來就是xml的配置了,和Servlet相似,每自定義一個,都須要在xml中加上一個配置(挺繁瑣的操做的)學習

<!-- 解決亂碼的問題 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

配置也比較簡單了,一個 <filter> 一個 <filter-mapping> 前者定義具體的Filter,後者表示這個Filter攔截的URL (看起來和Servlet的配置規則沒什麼兩樣)


II. 實例

咱們的實例,就拿大名鼎鼎的CharacterEncodingFilter來講明,順帶膜拜下Spring的大神的優秀源碼

public class CharacterEncodingFilter extends OncePerRequestFilter {

	private String encoding;

	private boolean forceEncoding = false;

	public CharacterEncodingFilter() {
	}

	public CharacterEncodingFilter(String encoding) {
		this(encoding, false);
	}

	public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
		Assert.hasLength(encoding, "Encoding must not be empty");
		this.encoding = encoding;
		this.forceEncoding = forceEncoding;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	public void setForceEncoding(boolean forceEncoding) {
		this.forceEncoding = forceEncoding;
	}

	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
			request.setCharacterEncoding(this.encoding);
			if (this.forceEncoding) {
				response.setCharacterEncoding(this.encoding);
			}
		}
		filterChain.doFilter(request, response);
		System.out.printl("servelt 執行完成,又返回filter");
	}
}

上面的實現比較簡單,主要將視線集中在 doFilterInternal 方法內部,若是要設置編碼參數,則直接修改 HttpServletRequest, HttpServletResponse 兩個參數,操做完成以後,執行下面這一行

filterChain.doFilter(request, response);

注意

  • 上面這一行執行,表示Filter層已經經過了,請求能夠轉發給下一個Filter或者直接傳給Servlet
  • 而下一個Filter, Servlet執行完成以後,還會繼續往下走,就是上面的那一行輸出,也會被調用(那一行是我加的,源碼中沒有)

因此,若是你不但願繼續往下走,那麼就簡單了,不執行上面的那一行便可

疑問

問題一:看了上面的源碼,一個很明顯的問題就是,參數怎麼設置的?

仔細看上面的源碼,發現自定義Filter是繼承 org.springframework.web.filter.OncePerRequestFilter 而不是直接實現的 Filter 接口,並且方法內也沒有顯示的實現 init()方法,全部很容易猜到是父類中實現了參數的初始化過程

具體的實現邏輯是在 org.springframework.web.filter.GenericFilterBean#init 中,一樣是Spring實現的,主要代碼撈出來

public final void init(FilterConfig filterConfig) throws ServletException {
		Assert.notNull(filterConfig, "FilterConfig must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
		}

		this.filterConfig = filterConfig;

		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			String msg = "Failed to set bean properties on filter '" +
				filterConfig.getFilterName() + "': " + ex.getMessage();
			logger.error(msg, ex);
			throw new NestedServletException(msg, ex);
		}

		// Let subclasses do whatever initialization they like.
		initFilterBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
		}
}

看上面一大串的代碼,到底幹了嘛? 簡單來說,就是獲取xml中配置的參數,而後填充到Filter對象中(對Srping而言,CharacterEncodingFilter就是一個bean),這個具體的邏輯和本篇關係不大,就直接跳過了

問題二:在Filter層中能夠獲取參數麼

從doFilter的方法簽名中看,既然有Request參數,那麼應該是能夠獲取到請求參數的,那麼實際驗證一下

先實現一個最最最簡單的Filter

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        System.out.println("in filter");
        System.out.println("args: " + JSON.toJSONString(request.getParameterMap()));
        chain.doFilter(request, response);
        System.out.println("out filter");
    }

    @Override
    public void destroy() {
    }
}

開始測試

curl -d 'name=Hello&password=world' http://127.0.0.1:8088/123

輸出以下

in filter
args: {"name":["Hello"],"password":["world"]}
out filter

注意

在Filter中獲取參數時,最好不要直接使用獲取請求流的方式,若是獲取請求流,那麼Servlet就獲取不到請求參數了

問題三:多個filter的順序怎麼定

前面學習Servlet的時候,也有這個問題,一個URL被多個Servlet命中了,那麼前後順序是怎樣的呢?

  • 精確匹配 > 最長匹配 > 其餘模糊匹配 > 沒有匹配的則是404

那麼Filter呢,他們的區別仍是比較明顯的,不少Filter都是攔截全部的請求,即不少Filter的命中規則都是同樣的,那麼怎麼辦?

  • 先執行帶有url-pattern標籤的filter,再執行帶有servlet-name標籤的filter
  • 若是同爲url-pattern或servlet-name,則會按照在web.xml中的聲明順序執行

測試case以下,咱們定義三個Filter:

  • TestFilter: 匹配全部路徑
  • ATestFilter: 匹配全部路徑
  • ServletFilter: 匹配 mvc-servlet
// ATestFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("in ATestFilter");
    chain.doFilter(request, response);
}


// TestFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("in TestFilter");
    chain.doFilter(request, response);
}

// ServletFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("in ServletFilter");
    chain.doFilter(request, response);
}

對應的xml配置以下

<filter>
    <filter-name>servletFilter</filter-name>
    <filter-class>com.test.ServletFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>servletFilter</filter-name>
    <servlet-name>mvc-dispatcher</servlet-name>
</filter-mapping>

<filter>
    <filter-name>testFilter</filter-name>
    <filter-class>com.test.TestFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>


<filter>
    <filter-name>atestFilter</filter-name>
    <filter-class>com.test.ATestFilter</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>atestFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

輸出結果

in TestFilter
in ATestFilter
in ServletFilter

III. 小結

Filter 一般用於JavaWeb的過濾使用,經過doFilter方法中執行 chain.doFilter(request, response);,進入下一個Filter或者Servlet執行邏輯,當執行完成以後,依然會回到Filter這一層,繼續走下去

針對上面的邏輯,Filter的常見應用場景有:

  • 用戶信息獲取,身份校驗
  • 安全校驗(referer校驗失敗,直接拒絕)
  • 穩定性相關(限流,監控埋點,全鏈路日誌埋點)

Filter的執行順序:

  • url-mapping 的優先執行,其次是 servlet-mapping
  • 同一個匹配方式(如都是url-mapping)中,根據在xml中定義的前後順序來肯定

Filter的注意事項:

  • 正常業務,請記得必定執行 chain.doFilter(request, response), 最後把它放在finnal塊中,防止你在Filter中的代碼拋異常致使進入不到後續的邏輯
  • 在Filter中不要直接獲取請求數據流(請求流被讀取完以後,Servlet就get不到了!)

IV. 其餘

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正

掃描關注,java分享

QrCode

相關文章
相關標籤/搜索