title: JavaWeb三大組件之過濾器(Filter)
tags: JavaWeb 過濾器
categories: JavaWeb 過濾器
若圖片無法顯示,請前往我的博客查看,相應文章鏈接:http://codingxiaxw.cn/2016/10/27/27-JavaWeb%E4%B8%89%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8B%E8%BF%87%E6%BB%A4%E5%99%A8/
過濾器可以動態(tài)的攔截請求和響應,以變換或使用包含在請求或響應中的信息。
過濾器是可用于Servlet編程的Java類,可以實現(xiàn)以下目的:
- 在客戶端的請求訪問后端資源之前,攔截這些請求。
- 在服務器的響應發(fā)送回客戶端之前,處理這些響應。
過濾器通過 Web 部署描述符(web.xml)中的 XML 標簽來聲明,然后映射到你的應用程序的部署描述符中的 Servlet 名稱或 URL 模式。
當 Web 容器啟動 Web 應用程序時,它會為你在部署描述符中聲明的每一個過濾器創(chuàng)建一個實例。
Filter的執(zhí)行順序與在web.xml配置文件中的配置順序一致,一般把Filter配置在所有的Servlet之前。
1.編寫過濾器
如何編寫過濾器?
- 創(chuàng)建一個類,必須實現(xiàn)Filter接口
- 在web.xml中進行配置,一般把Filter配置在所有的Servlet配置之前

方法介紹:
- void init():Filter的初始化,F(xiàn)ilter在服務器啟動時就創(chuàng)建,創(chuàng)建之后馬上執(zhí)行這個方法。用來初始化一些參數(shù)
- void doFilter(req,resp,chain):當向服務器請求的Servlet或jsp頁面在過濾器的過濾范圍內(nèi)時就會執(zhí)行這個方法。若方法體中沒有chain.doFilter()操作,則表示當向服務器請求該過濾器過濾范圍內(nèi)的資源(如Servlet/JSP頁面/html頁面等)時,這些資源中的所有方法都不會執(zhí)行(被過濾掉了);若方法體中有chain.doFilter()操作,表示不對過濾器過濾范圍內(nèi)的資源進行過濾。(即你請求的資源下的方法會執(zhí)行)
- void destroy():在服務器關(guān)閉時對Filter進行銷毀,在Filter銷毀之前會執(zhí)行這個方法,用來對非內(nèi)存資源進行釋放。
對方法中設(shè)計到的類介紹:
-
FilterConfig:與ServletConfig相似,該類有如下四個方法:
- getInitParameter():獲取初始化參數(shù)。
- getInitParameterNames():獲取所有初始化參數(shù)的名稱。
- getFilterName():獲取過濾器的配置名稱。
- getServletContext():獲取application。
-
FilterChain類:該類中有一個方法:
- doFilter():是不是會覺得該方法與Filter接口中的doFilter()方法是一樣的呢?沒錯,二者雖然外觀看起來一樣,但功能卻是千差萬別的。該方法被FilterChain對象調(diào)用,表示對Filter過濾器過濾范圍下的資源進行放行。
2.多過濾器的執(zhí)行順序
Web應用程序可以根據(jù)特定的目的定義若干個不同的過濾器,那么就需要在web.xml中對多個過濾器進行多個配置。而在web.xml中使用<filter-mapping>來控制多個過濾器的執(zhí)行順序,即哪個過濾器的<filter-mapping>配置在web.xml中的順序排在前面那這個過濾器就先執(zhí)行。
3.過濾器的四種攔截方式
- 1.攔截直接請求方式:REQUEST
- 2.攔截請求轉(zhuǎn)發(fā)方式:FORWARD
- 3.攔截請求包含方式:INCLUDE
- 4.攔截錯誤轉(zhuǎn)發(fā)方式:ERROR
實現(xiàn)不同的攔截方式需要在<filter-mapping>中進行不同的配置:
<dispatcher>REQUEST</dispatcher><dispatcher>FORWORD</dispatcher><dispatcher>INCLUDE</dispatcher><dispatcher>ERROR</dispatcher>
若在web.xml配置文件中沒有寫出上面四個攔截配置時默認該過濾器只攔截請求。
4.過濾器的應用場景
1.執(zhí)行目標資源之前做"預處理"工作,例如設(shè)置編碼,這種通常都會放行,只是在目標資源執(zhí)行之前做一些準備工作。(例如:幾乎是所有的Servlet中都需要寫request.setCharacteEncoding(),可以把它放入到一個Filter中。)這種過濾器沒有攔截功能。
2.通過條件判斷是否放行,例如校驗當前用戶是否已經(jīng)登錄,或者用戶IP是否已經(jīng)被禁用。(有攔截操作) (粗粒度權(quán)限控制,會員有會員的權(quán)利、游客有游客的權(quán)利)
3.在目標資源執(zhí)行后,做一些后續(xù)的特殊處理工作。例如把目標資源輸出的數(shù)據(jù)進行處理。
5.案例1:分IP統(tǒng)計網(wǎng)站的訪問次數(shù)
功能分析:1.統(tǒng)計工作需要在所有資源之前都執(zhí)行,那么就可以放到Filter中了。2.我們這個過濾器不打算做攔截操作,因為我們只是用來做統(tǒng)計的。3.用什么東西來裝載統(tǒng)計的數(shù)據(jù)。Map<String ,Integer>,整個網(wǎng)站只需要一個Map即可4.Map什么時候創(chuàng)建(使用ServletContextListener,在服務器啟動時完成創(chuàng)建,并保存到SevletContext中),Map保存到哪里:Map需要在Filter中用來保存數(shù)據(jù);Map需要在頁面使用,打印Map中的數(shù)據(jù)。
AListener.java:

AFilter.java:

show.jsp:

效果圖:

6.案例2:解決全站字符亂碼問題
一般我們通過jsp頁面請求轉(zhuǎn)發(fā)到servlet時,若請求方式為POST且請求參數(shù)包含中文參數(shù)時,我們需要在servlet的doPost()方法中設(shè)置POST請求編碼問題:request.setCharacterEncoding("utf-8");、設(shè)置響應編碼問題:response.setContentType("text/html;charset=utf-8");,這樣便可以解決post請求即響應編碼問題;而對于GET請求,若傳遞的請求參數(shù)包含中文參數(shù)時設(shè)置請求編碼就比較麻煩,需要在servlet的doGet()方法中設(shè)置響應編碼:response.setContentType("text/html;charset=utf-8");以及請求編碼:首先獲得傳遞給servlet的請求參數(shù):String username=request.getParameter("username")假設(shè)傳遞的請求參數(shù)為username,然后再輸入代碼username=new String(username.getBytes("ISO8859-1"),"utf-8");,這樣通過jsp頁面轉(zhuǎn)發(fā)到servlet的參數(shù)便解決了編碼問題。即可以通過response.getWrite().prinltn(username)正常顯示在網(wǎng)頁上。
試想:以后的開發(fā)中往往會用到很多的servlet,那我們豈不是要在每一個servlet的doPost()和doGet方法中都寫上上述的解決編碼代碼?這時候我們就可以通過過濾器來解決了。
首先附上頁面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="<c:url value="/AServlet?username=張三"/> ">點擊這里</a>
<form action="<c:url value="/AServlet"/> " method="post">
用戶名:<input type="text" name="username" value="李四">
<input type="submit" value="提交">
</form>
</body>
</html>
顯示在網(wǎng)頁上的界面為:[圖片上傳失敗...(image-cfd911-1526285896147)]
通過"點擊這里"的鏈接我們便完成了通過jsp頁面向servlet發(fā)送GET請求參數(shù),通過"提交"按鈕我們便完成了通過jsp頁面向servlet發(fā)送POST請求參數(shù)。創(chuàng)建一個servlet,我們在servlet中完成響應參數(shù)編碼的問題:
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
}
}
接下來在過濾器中完成請求參數(shù)編碼的問題,創(chuàng)建一個過濾器Filter,在web.xml中注冊:
<filter>
<filter-name>Filter</filter-name>
<filter-class>filter.Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Filter中編碼為:
public class Filter implements javax.servlet.Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
}
public void init(FilterConfig config) throws ServletException {
}
}
對于POST請求參數(shù)的編碼設(shè)置我們直接在doFilter()方法體中添加request.setCharacterEncoding("utf-8");代碼即可(此時運行程序,POST請求參數(shù)編碼的問題成功解決),對于GET請求參數(shù)的編碼,有些同學會覺得直接在doFilter()方法體中添加
String username=request.getParameter("username");username=new String(username.getBytes("ISO-8859-1"),"utf-8");
即可。這樣的參數(shù)是不太靠譜的,因為這里我們知道要傳遞的請求參數(shù)為username所以這里可以明了的指出,以后我們不知道請求參數(shù)為什么或者請求參數(shù)有很多時那就需要更多的上訴代碼,所以這里我們采用裝飾者模式對request進行裝飾(即將本來的request換成我們自己寫的request),創(chuàng)建一個EncodingRequest.java繼承HttpServletRequestWrapper,代碼如下:
public class EncodingRequest extends HttpServletRequestWrapper
{
private HttpServletRequest req;
public EncodingRequest(HttpServletRequest request)
{
super(request);
this.req=request;
}
@Override
public String getParameter(String name) {
String value=req.getParameter(name);
//處理編碼問題
try {
value=new String(value.getBytes("ISO-8859-1"),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return value;
}
}
在構(gòu)造方法中,我們傳入系統(tǒng)的request,然后將這個request賦值給我們自己編寫的req,然后在重寫的getParameter()方法中通過我們自己寫的req獲取請求參數(shù)并解決編碼問題,然后返回解決完編碼后的參數(shù)value(此時這個中文參數(shù)已解決編碼),然后在Filer中對我們自己編寫的request(即Encodingquest對象)放行即可?,F(xiàn)在doFilter()方法的方法體為:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//處理post請求編碼問題
request.setCharacterEncoding("utf-8");
HttpServletRequest req= (HttpServletRequest) request;
/**
* 處理get請求的編碼問題
*/
// String username=request.getParameter("username");
// username=new String(username.getBytes("ISO-8859-1"),"utf-8");
/**
* 調(diào)包request
* 1.寫一個request的裝飾類
* 2.在放行時,使用我們自己的request
*/
EncodingRequest er = new EncodingRequest(req);
chain.doFilter(er, response);
}
運行程序,成功解決GET請求方式的編碼問題,但是POST請求方式的編碼又出現(xiàn)了問題,這是為什么呢?因為我們在doFilter方法中已經(jīng)通過代碼request.setCharacterEncoding("utf-8");處理了POST請求方式的編碼問題,但是此時的請求是系統(tǒng)的request對象而不是我們自己寫的req,我們對req進行了放行而沒有對request進行方式,所以方法體中應該增加if判斷語句,改正后的doFilter()方法體內(nèi)容為:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//處理post請求編碼問題
request.setCharacterEncoding("utf-8");
HttpServletRequest req= (HttpServletRequest) request;
/**
* 處理get請求的編碼問題
*/
// String username=request.getParameter("username");
// username=new String(username.getBytes("ISO-8859-1"),"utf-8");
/**
* 調(diào)包request
* 1.寫一個request的裝飾類
* 2.在放行時,使用我們自己的request
*/
if (req.getMethod().equals("GET")) {
EncodingRequest er = new EncodingRequest(req);
chain.doFilter(er, response);
}else if (req.getMethod().equals("POST")){
chain.doFilter(request, response);
}
}
此時運行程序,成功解決POST請求方式和GET請求方式的編碼問題。在學習框架之前我們都這樣通過Filter解決編碼問題,而當我們學習了Spring MVC框架后我們處理POST請求參數(shù)的編碼問題時直接在web.xml中添加如下配置而不用再寫一個過濾器:
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
解決GET請求方式的編碼問題時有兩種解決方法:1.修改tomcat配置文件添加編碼與工程編碼一致,如下:
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
2.對參數(shù)進行重新編碼:
String userName new
String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
第二種方法需要對每個參數(shù)都進行重新編碼,比較麻煩。
回歸我們的過濾器講解,通過如上包裝request的方式便可以通過過濾器解決全站編碼問題。
2018.3.19更
歡迎加入我的Java交流1群:659957958。群里目前已有1800人,每天都非?;钴S,但為了篩選掉那些不懷好意的朋友進來搞破壞,所以目前入群方式已改成了付費方式,你只需要支付9塊錢,即可獲取到群文件中的所有干貨以及群里面各位前輩們的疑惑解答;為了鼓勵良好風氣的發(fā)展,讓每個新人提出的問題都得到解決,所以我將得到的入群收費收入都以紅包的形式發(fā)放到那些主動給新手們解決疑惑的朋友手中。在這里,我們除了談技術(shù),還談生活、談理想;在這里,我們?yōu)槟愕膶W習方向指明方向,為你以后的求職道路提供指路明燈;在這里,我們把所有好用的干貨都與你分享。還在等什么,快加入我們吧!
2018.4.21更:如果群1已滿或者無法加入,請加Java學習交流2群:305335626 。群2作為群1的附屬群,除了日常的技術(shù)交流、資料分享、學習方向指明外,還會在每年互聯(lián)網(wǎng)的秋春招時節(jié)在群內(nèi)發(fā)布大量的互聯(lián)網(wǎng)內(nèi)推方式,話不多說,快上車吧!
7.聯(lián)系
If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.