過濾器是一個駐留在服務器端的Web組建,可以截取客戶端和資源之間的請求和響應信息。Web過濾器是不能直接處理客戶端請求,返回客戶端數據的!
舉例來說:當我們登錄CSDN或郵箱的時候,輸入應用名和密碼就可以進入我們請求的頁面,當我們點擊退出后,下一次進入時需要重新輸入登錄用戶名與密碼。這是過濾器應用的一個場景。
我們需要了解:
- 過濾器的工作原理
- 生命周期
- 過濾器的幾種類型
- 實例
1、過濾器的工作原理

上面的圖簡單說明了過濾器在客戶端和服務器之間的作用。
2、過濾器的生命周期

Web容器啟動的時候,會加載web.xml並執行一次init()函數,然后每次客戶端的請求都會執行doFilter()函數,最后當容器關閉的時候會執行destroy()函數

上面顯示的是一個Web應用程序的結構,所有的Webroot中的內容都是Web的內容,Web-INF下所有的資源都不能直接被url訪問,其他的文件,用戶可以通過url訪問。

關於url-pattern的書寫規范,
(A)一個filter映射一個url:這種情況下的url與url-pattern中配置的url進行精確匹配。url-pattern中的訪問路徑必須以 / 開頭,表示的是Web應用程序的根目錄,而不是Web站點的根目錄,路徑名稱可以是多級目錄的形式,例如
<url-pattern>/demo/index.html</url-pattern>
(B)一個filter映射多個url:這種情況下可以使用通配符,需要注意的也有兩種情況:
(1)*.擴展名,*點前面不能有 “/”
(2)以/開頭,並以 /* 結尾,例如
<url-pattern>/action/*</url-pattern>表示的是整個action目錄下的url
<url-pattern>/</url-pattern>表示的是整個web應用程序下的url
過濾器鏈
- 在一個web應用中,可以開發編寫多個Filter,這些Filter組合起來稱之為一個Filter鏈。
- Web服務器根據Filter在web.xml文件中的注冊順序(按filter-mapping元素的順序),決定先調用哪個Filter,當第一個Filter的doFilter方法被調用時,Web服務器會創建一個代表Filter鏈的FilterChain對象傳遞給該方法。
- 在doFilter方法中,如果調用了FilterChain對象的doFilter方法,則web服務器會檢查FilterChain對象中是否還有filter,如果有,則調用第2個filter,如果沒有,則調用目標資源。

每次客戶端的請求,服務器都會Filter中的doFilter方法,因此,我們可以在diFilter中完成一些處理,下面是一個簡單的代碼實現:
public class FilterFirst implements Filter {
// 應用被加載時完成過濾器的實例化
public FilterFirst() {
System.out.println("調用了默認構造方法");
}
// 初始化:服務器傳入FilterConfig參數
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("調用初始化方法"); }
// 用戶每次訪問被過濾資源都會調用過濾器的doFilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo1第一次攔截");
// 對request、response進行預處理,代碼放在chain.doFilter()前
chain.doFilter(request, response);// 放行
// 對響應信息進行攔截,代碼放在chain.doFilter()后
System.out.println("FilterDemo1第二次攔截");
}
// 應用從服務器上卸載時調用銷毀方法
public void destroy() {
System.out.println("調用銷毀方法");
}
}
web.xml的配置
<filter> <filter-name>FilterDemo1</filter-name> <filter-class>org.flyne.filter.FilterDemo1</filter-class> </filter> <filter-mapping> <filter-name>FilterDemo1</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
注意代碼中出現的doFilter函數以及其中的參數。ServletRequest request, ServletResponse response, FilterChain chain 因此:
(1)我們可以實現將客戶端到服務器,服務器到客戶端的交換數據進行處理。
(2) 處理完成后,我們一定要記得寫上chain.doFilter(request, response);否則,Web訪問會一直處於被攔截的狀態;
四、實例
1)SetCharacterEncodingFilter:解決POST請求參數和響應輸出的中文亂碼
public class SetCharacterEncodingFilter implements Filter {
private String encoding; //編碼可以通過<init-param>元素配置
public void init(FilterConfig filterConfig) throws ServletException {
encoding = filterConfig.getInitParameter("encoding");
if(encoding == null){//如果用戶忘記配置,默認encoding為UTF-8
encoding = "UTF-8";
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//只能解決POST請求參數亂碼問題
request.setCharacterEncoding(encoding);
//指定輸出編碼(最后帶上,后面會有說明)
response.setCharacterEncoding(encoding);
//指定輸出流編碼及客戶端應使用的碼表
response.setContentType("text/html;charset="+encoding);
chain.doFilter(request, response);
}
public void destroy() { }
}
------------------------web.xml配置------------------------
<filter>
<filter-name>SetCharacterEncodingFilter</filter-name>
<filter-class>org.flyne.examples.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SetCharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2)2)NoCacheFilter:禁止客戶端緩存動態資源
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
//響應頭為HTTP協議里的,需轉換一下
HttpServletRequest request = null;
HttpServletResponse response = null;
try{
request = (HttpServletRequest)req;
response = (HttpServletResponse)resp;
}catch(Exception e){
throw new RuntimeException("not-http request or response");
}
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "no-cache"); //
response.setHeader("Pragma", "no-cache"); //這三個參數的意義差不多
chain.doFilter(request, response);
}
------------------------web.xml配置------------------------
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>org.flyne.examples.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
3)SetCacheExpiresFilter:控制靜態資源緩存時間 (這個主要是理解HTTP協議中Expire的作用)
public class SetCacheExpiresFilter implements Filter {
private int htmlExp;//HTML的緩存時間,單位為小時,下同
private int cssExp;//CSS的緩存時間
private int jsExp;//JS的緩存時間
public void init(FilterConfig filterConfig) throws ServletException {
htmlExp = Integer.parseInt(filterConfig.getInitParameter("html"));
cssExp = Integer.parseInt(filterConfig.getInitParameter("css"));
jsExp = Integer.parseInt(filterConfig.getInitParameter("js"));
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
// 響應頭為HTTP協議里的,需轉換一下
HttpServletRequest request = null;
HttpServletResponse response = null;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) resp;
} catch (Exception e) {
throw new RuntimeException("not-http request or response");
}
//根據請求資源的后綴名確定緩存時間
String uri = request.getRequestURI();
String extName = uri.substring(uri.lastIndexOf(".")+1);
long expTime = 0;
if("html".equals(extName)){
expTime = System.currentTimeMillis()+htmlExp*60*60*1000;
}else if("css".equals(extName)){
expTime = System.currentTimeMillis()+cssExp*60*60*1000;
}else if("js".equals(extName)){
expTime = System.currentTimeMillis()+jsExp*60*60*1000;
}
response.setDateHeader("Expires", expTime);
chain.doFilter(request, response);
}
public void destroy() { }
}
------------------------web.xml配置------------------------
<filter>
<filter-name>SetCacheExpiresFilter</filter-name>
<filter-class>org.flyne.examples.SetCacheExpiresFilter</filter-class>
<init-param>
<param-name>html</param-name>
<param-value>1</param-value>
</init-param>
<init-param>
<param-name>css</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>js</param-name>
<param-value>3</param-value>
</init-param> </filter> <filter-mapping>
<filter-name>SetCacheExpiresFilter</filter-name>
<url-pattern>*.html</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.js</url-pattern>
</filter-mapping>
4)AutoLoginFilter :用戶自動登錄
當用戶登陸時,將表單提交到LoginServlet處理,如果用戶勾選了記住我,則將用戶的登陸信息存入Cookie:Cookie名為“logInfo”,值為(用戶名的base64加密結果_密碼的md5加密結果)。下面的自動登錄過濾器基於以上信息:
public class AutoLoginFilter implements Filter {
//表現層同service層打交道
private UserService service = new UserServiceImpl();
public void init(FilterConfig filterConfig) throws ServletException { }
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
// 轉為Http協議的request和response
HttpServletRequest request = null;
HttpServletResponse response = null;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) resp;
} catch (Exception e) {
throw new RuntimeException("not-http request or response");
}
HttpSession session = request.getSession();
// 判斷用戶有沒有登錄:只管沒有登錄的
User sUser = (User) session.getAttribute("user");
if (sUser == null) {
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
Cookie cookie = cookies[i];
//名為logInfo的Cookie記錄了登錄信息(用戶名、密碼)
if ("logInfo".equals(cookie.getName())) {
String value = cookie.getValue();
//Cookie中的用戶名是經過Base64加密的,所以需要解密
String username = SecurityUtil.base64Decode(value.split("_")[0]);
String password = value.split("_")[1];
//Cookie中的密碼是md5加密后的,所以第三個參數為true
User user = service.login(username, password, true);
//通過則在session中設置登陸標記
if(user!=null){
session.setAttribute("user", user);
}
break;
}
}
}
chain.doFilter(request, response);
}
public void destroy() { }
}
未完......
參考:MOOC 過濾器 實例來自 http://www.flyne.org/article/636/2
