Filter的使用和經(jīng)典案例
一伴箩、什么是Filter
Filter也稱之為過濾器。
通過Filter可以攔截所有對(duì)Web服務(wù)器管理的任意Web資源的請求绝葡,從而實(shí)現(xiàn)特殊的控制功能深碱。例如實(shí)現(xiàn)url權(quán)限的控制,過濾敏感信息以及壓縮響應(yīng)格式等一些高級(jí)的功能藏畅。
-
FilterChain
- 在一個(gè)Web工程中敷硅,對(duì)同一個(gè)Web資源訪問時(shí),可以有多個(gè)Filter過濾器對(duì)其進(jìn)行攔截,多個(gè)過濾器在tomcat服務(wù)器啟動(dòng)時(shí),組成過濾器鏈FilterChain愉阎。
每一個(gè)Filter必須執(zhí)行chain.doFilter(request,response)方法,才能將控制權(quán)交到下一個(gè)過濾器或者目標(biāo)資源绞蹦。
二、Filter的生命周期
生命周期方法
* init(FilterConfig):
在服務(wù)器啟動(dòng)時(shí),Filter對(duì)象會(huì)被創(chuàng)建榜旦,init方法被執(zhí)行,Filter對(duì)象只有一個(gè),該方法只執(zhí)行一次幽七。
這里與Servlet不同,servlet在第一次訪問時(shí),執(zhí)行servlet對(duì)象的初始化。
* doFilter(request,response):
在每一次請求時(shí)溅呢,只要是攔截的目標(biāo)資源澡屡,就會(huì)執(zhí)行
* destroy():
在服務(wù)器正常關(guān)閉猿挚、重啟時(shí)執(zhí)行。
生命周期
- 什么時(shí)候創(chuàng)建
- 服務(wù)器啟動(dòng)的時(shí)候創(chuàng)建
- 什么時(shí)候銷毀
- 服務(wù)器正常關(guān)閉驶鹉、重啟時(shí)銷毀
三绩蜻、映射配置注意事項(xiàng)
-
如何決定過濾器執(zhí)行順序
- 過濾器先后執(zhí)行的順序取決于filter-mapping標(biāo)簽的順序.
- 如果使用的是注解,執(zhí)行的順序是類名的字母排列順序
配置過濾器進(jìn)行過濾時(shí),除了使用<url-pattern>之外室埋,也可以使用<servlet-name>標(biāo)簽,但是對(duì)應(yīng)的servlet也必須在web.xml中配置.
-
<dispatcher> 指定過濾器攔截目標(biāo)資源的調(diào)用方式
- 四種調(diào)用方式
- REQUEST : 客戶端請求過濾(實(shí)際開發(fā)中一般使用該默認(rèn)方式)
- FORWARD : 轉(zhuǎn)發(fā)過濾
- INCLUDE : 包含過濾
- ERROR : 錯(cuò)誤頁面過濾 web.xml 配置error-page
- 如果沒有指定攔截模式,默認(rèn)攔截REQUEST
- 四種調(diào)用方式
四办绝、案例
案例一:解決全局亂碼的問題
需求:
我們在用servlet執(zhí)行服務(wù)器與客戶端的請求響應(yīng)時(shí),為了避免產(chǎn)生中文亂碼現(xiàn)象,會(huì)在servlet中解決亂碼問題.
但是一旦我們的工程量比較大,servlet的數(shù)量太多的時(shí)候,解決編碼的代碼就會(huì)變得十分臃腫。
因?yàn)镕ilter過濾器可以攔截所有訪問該站點(diǎn)的請求,因此在Filter中提前將亂碼問題處理后,servlet中再獲取請求提交的參數(shù)
時(shí)姚淆,就不用再考慮編碼問題了孕蝉。
案例分析:
1. 寫一個(gè)表單提交頁面,提交表單時(shí)訪問servlet
2. 寫一個(gè)Filter過濾器,攔截所有訪問本站點(diǎn)的請求,提前處理編碼問題
1. 在filter中獲取表單提交的方式
2. 如果是POST方式提交的表單,直接用request.setCharacterEncoding("UTF-8")即可解決。
3. 如果是GET方式提交的表單,就需要先獲取到提交的包含中文的參數(shù)
4. 但是我們在這里即使是將該參數(shù)重新編碼,在放行的時(shí)候也不能把這個(gè)參數(shù)往下傳遞給servlet呀肉盹?
因此在這里就需要對(duì)HttpServletRequest的getParameter(String name)進(jìn)行包裝,對(duì)這個(gè)getParameter()方法進(jìn)行增強(qiáng)昔驱。
裝飾者模式:
- 寫一個(gè)類(包裝類)與被包裝類實(shí)現(xiàn)同一個(gè)接口
- 在包裝類中創(chuàng)建一個(gè)特殊的構(gòu)造方法,參數(shù)為被包裝類的對(duì)象,從而對(duì)被包裝類進(jìn)行裝飾。
- 為了在包裝類中能夠使用被包裝類進(jìn)行操作,將傳遞進(jìn)來的被包裝類對(duì)象傳遞給本類的成員變量上忍。
- 對(duì)需要增強(qiáng)或修改的方法重寫。
在這里纳本,Sun公司考慮到我們可能要對(duì)HttpServletRequest與HttpServletResponse的方法進(jìn)行自定義,
因此sun公司已經(jīng)定義了兩個(gè)實(shí)現(xiàn)了上述兩個(gè)接口的實(shí)現(xiàn)類:HttpServletRequestWrapper與HttpServletResponseWrapper.
因此我們直接繼承HttpServletRequestWrapper,重寫getParameter方法即可窍蓝。
代碼實(shí)現(xiàn):
JSP:
<form action="${pageContext.request.contextPath}/encodingServlet" method="GET">
用戶名<input type="text" name="username" /><br/>
密碼<input type="password" name="password" /><br/>
<input type="submit" value="提交" />
</form>
Servlet:(測試)
//獲取表單提交的用戶名
String username = request.getParameter("username");
System.out.println(username);
//將其寫回客戶端
response.getWriter().write(username);
Filter代碼:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
// 處理全應(yīng)用的編碼問題
// 1.強(qiáng)制轉(zhuǎn)換
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setContentType("text/html;charset=UTF-8");
// 2.處理業(yè)務(wù)邏輯
// 因?yàn)樵赟ervlet中都是通過request.getParameter()獲取參數(shù),因此用裝飾者模式對(duì)該方法進(jìn)行增強(qiáng)
MyRequest myRequest = new MyRequest(request);//對(duì)request進(jìn)行裝飾
//設(shè)置服務(wù)器寫回?cái)?shù)據(jù)時(shí)的編碼格式
// 3.放行,將裝時(shí)候的myrequest傳遞給servlet,這樣servlet中使用getParameter方法時(shí)使用的就是增強(qiáng)后的方法
chain.doFilter(myRequest, response);
}
裝飾者模式包裝類MyRequest:
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
public MyRequest(HttpServletRequest request) {
super(request);
// 將被包裝對(duì)象傳遞給本類的成員變量,以便對(duì)其進(jìn)行操作
this.request = request;
}
// 對(duì)getParameter方法增強(qiáng)
@Override
public String getParameter(String name) {
try {
// 2.1 獲取數(shù)據(jù)提交的方式
String method = request.getMethod();
System.out.println(method);
// 2.2 根據(jù)提交的方式不同,處理編碼亂碼的問題
if ("POST".equalsIgnoreCase(method)) { // 如果是POST請求
request.setCharacterEncoding("UTF-8");// POST請求告知服務(wù)器使用UTF-8編碼
// 如果是GET請求,就需要對(duì)request的getParameter()方法進(jìn)行增強(qiáng)了
// 使用裝飾者模式,寫一個(gè)類繼承sun公司提供的已經(jīng)實(shí)現(xiàn)了HttpServletRequest的實(shí)現(xiàn)類
} else if ("GET".equalsIgnoreCase(method)) {
//獲取GET方式提交的參數(shù)
String parameter = request.getParameter(name);
//對(duì)獲取到的參數(shù)重新解碼和編碼
parameter = new String(parameter.getBytes("ISO-8859-1"),"UTF-8");
return parameter;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return super.getParameter(name);
}
案例二、登錄頁面自動(dòng)登錄功能的實(shí)現(xiàn)
分析:
自動(dòng)登陸的功能
1.完成簡易的登陸功能繁成。
1.用戶點(diǎn)擊登陸后,將表單中的賬密發(fā)送至服務(wù)器進(jìn)行查詢
2.服務(wù)器查詢完畢后返回一個(gè)User對(duì)象,servlet根據(jù)user的狀態(tài)做出不同的轉(zhuǎn)發(fā)定向
3. 登錄失斚朋稀:服務(wù)器發(fā)送錯(cuò)誤信息,客戶端解析并顯示在相應(yīng)的位置
登陸成功:跳轉(zhuǎn)到購物網(wǎng)頁的首頁
準(zhǔn)備工作(搭建開發(fā)環(huán)境):
1:準(zhǔn)備數(shù)據(jù)庫用戶表,至少需要有id,name,username,password的字段。準(zhǔn)備用戶登錄的頁面
2:導(dǎo)入jar包:
數(shù)據(jù)庫連接驅(qū)動(dòng),c3p0的jar包與配置文件,dbutils的jar包,因?yàn)樵趈sp需要對(duì)數(shù)據(jù)進(jìn)行解析,還需要jstl.jar和與之配套的standard.jar
3. 準(zhǔn)備javabean類User和工具類JDBCUtils
4. 創(chuàng)建包結(jié)構(gòu)
2.自動(dòng)登陸功能,在第一步的登陸功能上進(jìn)行完善巾腕。
Servlet:
1.servlet查詢完成后,對(duì)User進(jìn)行判斷,如果User不為空,即用戶登錄成功面睛。
這時(shí),需要獲取到name為autoLogin的checkbox的復(fù)選框狀態(tài)。
2.如果復(fù)選框狀態(tài)是未選中,則不進(jìn)行任何任何額外的操作尊搬。
如果復(fù)選框的狀態(tài)是被選中的,那么就需要將用戶的賬密存儲(chǔ)起來,下次用戶再訪問時(shí),服務(wù)器就自動(dòng)進(jìn)行自動(dòng)登陸.
3. Cookie可以完成上述的要求,如果復(fù)選框被選中,將用戶的賬號(hào)密碼存入Cookie中寫回客戶端存儲(chǔ)叁鉴。
Filter : (需求)無論用戶下一次訪問應(yīng)用的哪個(gè)頁面,都會(huì)檢查是否有賬密這個(gè)Cookie存在。
1. Filter可以攔截所有訪問當(dāng)前站點(diǎn)的請求,對(duì)其進(jìn)行篩選,因此在Filiter中進(jìn)行自動(dòng)登錄的操作佛寿。保證了用戶無論訪問哪個(gè)頁面都能進(jìn)行有效的檢測.
2. Filter中獲取用戶請求攜帶過來的所有Cookies,遍歷獲取代表用戶賬號(hào)和密碼的Cookie,檢查其是否為空,如果為空,直接放行,讓其訪問網(wǎng)頁,不提供登陸幌墓。
3.如果不為空,證明賬密存在,調(diào)用業(yè)務(wù)層service處理登陸操作,操作返回一個(gè)User對(duì)象,這里也要將其存入Session冀泻。
最后放行,讓其既訪問了本頁面又完成了自動(dòng)登錄的操作常侣。
自動(dòng)登錄思路圖:
1.搭建環(huán)境
1.1 新建數(shù)據(jù)庫用戶表User
1.2 導(dǎo)入jar包
1.3 準(zhǔn)備javabean類User和工具類JDBCUtils
1.4 創(chuàng)建包結(jié)構(gòu)
2.代碼實(shí)現(xiàn)
2.1 登陸的基本功能實(shí)現(xiàn)
用戶提交登陸數(shù)據(jù)到服務(wù)器查詢,服務(wù)器查詢完畢后根據(jù)返回的User對(duì)象是否存在,進(jìn)行不同的操作。
如果存在,跳轉(zhuǎn)到首頁弹渔。如果不存在,提示用戶錯(cuò)誤信息胳施。
由于有代碼的重復(fù),這里就不列舉具體代碼了,在后面一起寫出來。
2.2 自動(dòng)登錄的功能實(shí)現(xiàn)
用戶登錄成功后,判斷用戶是否選擇了自動(dòng)登錄復(fù)選框,如果選擇了將其賬密存入Cookie寫回客戶端肢专。
同時(shí),將該User對(duì)象存入Session中,如果用戶不退出當(dāng)前瀏覽器而去訪問其他頁面舞肆,可以直接從session對(duì)象中獲取代表該用戶的該User對(duì)象要實(shí)現(xiàn):無論用戶訪問本站點(diǎn)的任何頁面,都會(huì)事先監(jiān)測其是否勾選了自動(dòng)登錄,(勾選了自動(dòng)登錄的用戶請求會(huì)存在代表賬密的Cookie)您没。
要完成上面的要求,Filter剛好可以對(duì)訪問本應(yīng)用的所有資源的請求進(jìn)行攔截,因此使用Filter過濾器進(jìn)行操作胆绊。
1. 使用用戶的請求獲取Session,從而獲取存在Session域?qū)ο蟮腢ser對(duì)象.這一步主要是因?yàn)橐粋€(gè)用戶在沒有
退出當(dāng)前客戶端時(shí),進(jìn)行的其他頁面訪問時(shí),可以直接從Session中獲取到代表該用戶的user對(duì)象而不需要重新登陸氨鹏。
2. 如果用戶存在,直接放行,可以從Session中獲取user對(duì)象。
如果user對(duì)象不存在,就需要獲取請求所攜帶的所有Cookies,獲取它們當(dāng)中代表用戶賬號(hào)密碼的Cookie压状。
3. 從代表用戶賬密的Cookie中獲取用戶的賬號(hào)密碼,判斷賬號(hào)密碼的狀態(tài)仆抵。
如果賬號(hào)密碼為空,那么放行,其訪問頁面資源,但是是未登錄的狀態(tài)种冬。
如果賬號(hào)密碼不為空,那么就調(diào)用業(yè)務(wù)層去數(shù)據(jù)庫中查詢,根據(jù)該賬密返回一個(gè)User對(duì)象镣丑。
4. 判斷返回的user對(duì)象的狀態(tài)。
如果為空娱两,說明登錄失敗,放行莺匠。
如果不為空,說明登陸成功,將該user對(duì)象存入session對(duì)象,放行十兢。
web層
Servlet代碼 :
// 1.獲取登陸表單提交的賬號(hào)密碼
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username);
System.out.println(password);
try {
// 2.調(diào)用service/dao層查詢,將獲取的賬密作為參數(shù)傳遞
LoginService service = new LoginService();
User user = service.login(username, password);
// 3.dao層查詢返回一個(gè)User對(duì)象,根據(jù)User對(duì)象是否為null做出不同的處理
if (user != null) {
// 獲得自動(dòng)登陸的復(fù)選框狀態(tài),檢查用戶是否勾選了該復(fù)選框
String autologin = request.getParameter("autologin");
// 判斷,如果勾選了該復(fù)選框,就將賬號(hào)密碼添加到Cookie中寫回客戶端
if ("autologin".equals(autologin)) { // 用戶勾選了該復(fù)選框
Cookie cookie = new Cookie("username", username);// 存儲(chǔ)賬號(hào)的Cookie
Cookie cookie2 = new Cookie("password", password);// 存儲(chǔ)密碼的Cookie
cookie.setMaxAge(60*60*24*7);//設(shè)置cookie的最大生存時(shí)間為7天
cookie2.setMaxAge(60*60*24*7);
cookie.setPath("/");//設(shè)置該cookie的有效路徑,斜線"/"代表當(dāng)前應(yīng)用都有效
cookie2.setPath("/");
//將Cookie寫回客戶端
response.addCookie(cookie);
response.addCookie(cookie2);
}
// 登陸成功,將該user對(duì)象存入session對(duì)象,以便在當(dāng)前web站點(diǎn)所有頁面都能獲取到該user對(duì)象
HttpSession session = request.getSession();
session.setAttribute("user", user);
// 重定向去到首頁
response.sendRedirect(request.getContextPath() + "/index.jsp");
} else {
// 登陸失敗給用戶提示錯(cuò)誤信息
request.setAttribute("msg", "賬號(hào)或密碼錯(cuò)誤");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (SQLException e) {
e.printStackTrace();
}
service層
LoginDAO dao = new LoginDAO();
return dao.login(username,password);
dao層
QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from user where username=? and password =?";
return runner.query(sql, new BeanHandler<User>(User.class), username,password);
Filter過濾器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// Filter的url-pattern設(shè)為/*攔截訪問當(dāng)前應(yīng)用的所有請求
// 1.強(qiáng)制轉(zhuǎn)換request和response
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 2.實(shí)現(xiàn)業(yè)務(wù)邏輯
// 2.1 首先獲取存在session中的user對(duì)象.
// 這一步是因?yàn)椋喝绻脩粼诘顷懞?繼續(xù)點(diǎn)擊其他頁面而沒有退出當(dāng)前瀏覽器的時(shí)候,就可以直接從session中取出登陸成功時(shí)存在里面的user對(duì)象,不需要再次執(zhí)行登陸的操作趣竣。
HttpSession session = request.getSession();// 獲取該客戶端當(dāng)前web應(yīng)用存儲(chǔ)的session域?qū)ο? User user = (User) session.getAttribute("user");// 獲取該客戶端用戶存儲(chǔ)在當(dāng)前web應(yīng)用的user對(duì)象
try {
// 2.2接下來判斷該user對(duì)象是否存在,如果存在,,放行,session中本就存在該user對(duì)象,無需在存儲(chǔ)直接放行即可.如果不存在,說明session可能過期或其他原因找不到了.
// 如果user對(duì)象不存在,就需要獲取請求中帶過來的cookies,如果用戶勾選了自動(dòng)登錄并且cookie沒有過期,那么其請求中會(huì)攜帶賬密的cookie過來
if (user == null) { // 用戶user不存在 用戶存在的話不需要進(jìn)行操作,不會(huì)進(jìn)入if語句,在最后執(zhí)行放行代碼即可
// 獲取請求攜帶的所有cookies
Cookie[] cookies = request.getCookies();
String username = null;// 用戶的賬號(hào)
String password = null;// 用戶的密碼
// 遍歷所有cookie,獲取代表用戶賬號(hào)密碼的Cookie
if (cookies != null) {
for (Cookie cookie : cookies) { // 如果用戶勾選了自動(dòng)登錄并且Cookie未失效,就會(huì)存在username和password的Cookie
String name = cookie.getName();// 獲取每一個(gè)Cookie的key
String value = cookie.getValue();// 獲取每一個(gè)Cookie的value
if ("username".equals(name)) {
username = value;
}
if ("password".equals(name)) {
password = value;
}
}
// 2.3獲取到賬密的Cookie進(jìn)行判斷,如果為空,就直接放行讓其訪問頁面,如果不為空,就調(diào)用業(yè)務(wù)層執(zhí)行登陸操作,返回一個(gè)User對(duì)象。
if (username != null && password != null) { // 從Cookie中獲得的用戶名和賬號(hào)都不為空,調(diào)用業(yè)務(wù)層執(zhí)行登陸操作
LoginService service = new LoginService();
User user2 = service.login(username, password);// 登陸后返回的user對(duì)象
// 2.4判斷User對(duì)象是否存在,如果不存在說明賬密錯(cuò)誤自動(dòng)登陸失敗,放行旱物。如果存在,將該對(duì)象存入session中,自動(dòng)登錄成功,并放行遥缕。
if (user2 != null) { // 用戶登陸成功,將代表該用戶的對(duì)象存入session域
session.setAttribute("user", user2);// 這里的存入與Servlet中存儲(chǔ)的name要保持一致
}
}
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 3.放行
chain.doFilter(request, response);
}
}
測試結(jié)果:勾選自動(dòng)登錄后,無論訪問本站點(diǎn)的哪個(gè)頁面,重復(fù)登陸無需再輸入用戶密碼,直接跳轉(zhuǎn)到首頁宵呛。