Filter過濾器 1.Filter過濾器說明 為什麼需要過濾器? 先來看一個例子: 我們在登錄網站頁面時,需要先進行登錄驗證。 用戶訪問的正常的流程應該是: 用戶先通過登錄頁面進行驗證,然後才可以訪問各種頁面。 為了防止用戶繞過登錄驗證,我們需要在每個頁面進行驗證, 獲取session,驗證用戶是 ...
Filter過濾器
1.Filter過濾器說明
-
為什麼需要過濾器?
先來看一個例子:
我們在登錄網站頁面時,需要先進行登錄驗證。
用戶訪問的正常的流程應該是:
-
用戶先通過登錄頁面進行驗證,然後才可以訪問各種頁面。
-
為了防止用戶繞過登錄驗證,我們需要在每個頁面進行驗證, 獲取session,驗證用戶是否登錄過。
但是上述的方法又會產生下麵的問題:
- 使用傳統方法,每個頁面都要進行登錄驗證
- 這將會造成代碼的冗餘,而且功能是重覆的,比較麻煩,維護起來也不方便
這時候就需要filter過濾器,它可以統一進行驗證,比如許可權,身份的驗證,還可以進行日誌記錄,事務管理等...
-
-
過濾器介紹
- Filter過濾器是JavaWeb的三大組件之一(Servlet程式,Listener監聽器,Filter過濾器)
- Filter過濾器是JavaEE規範,是介面
- Filter過濾器的作用是:攔截請求,過濾響應
- 應用場景:
- 許可權檢查
- 日誌操作
- 事務管理
2.過濾器的基本原理
上述整個過程如下:
- 瀏覽器請求某個資源,Tomcat接收請求後,先判斷是否需要使用過濾器。
- 在web.xml文件中配置過濾器,並指定過濾器的url-pattern(規則),tomcat會根據你指定的規則來決定是否需要使用過濾器。
- 如果需要,就使用過濾器,過濾器會根據你寫的業務需要來進行驗證。如果驗證成功,就可以繼續訪問;如果驗證失敗,就返回業務指定的地方。
- 如果不需要過濾器,就直接訪問/返回資源。
3.過濾器快速入門
需求:在web工程下,有後臺管理目錄manage,要求該目錄下所有資源(html,圖片,jsp,servlet)等資源需要用戶登錄後才能訪問。
3.1思路分析
上述流程如下:
- 用戶訪問login.jsp頁面填寫數據,login.jsp將數據發送到loginCheckServlet驗證。
- 如果驗證成功,servlet創建用戶瀏覽器關聯的session並保存。
- servlet轉發到過濾器,驗證還是不是真的登錄過(有無session)
- 如果驗證成功,就可以訪問資源
- 如果驗證失敗,就直接返回登錄頁面
- 如果用戶直接訪問資源,會先通過過濾器進行驗證。
- 如果沒有登陸過,就打回登錄頁面,不允許未經登錄就直接訪問資源
- 如果登錄過,可以直接訪問資源
3.2-1代碼實現
-
首先創建項目,添加web支持,添加需要的jar包
-
完成模塊的整體流程
先完成一個正確完整的流程,再加入其它的功能,完善功能。
總地來說就是先把框架搭建起來,再完善細節。
2.1 先完整一個正確的流程-看到一個效果->後面代碼就可以驗證
2.2 加入其它功能
2.3 完善功能
-
配置Tomcat
login.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2022/11/28
Time: 17:03
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>管理後臺頁面登錄</title>
</head>
<body>
<h1>管理後臺頁面登錄</h1>
<form action="<%=request.getContextPath()%>/loginCheckServlet" method="post">
u: <input type="text" name="username"/><br/><br/>
p: <input type="password" name="password"/><br/><br/>
<input type="submit" value="用戶登錄"/>
</form>
</body>
</html>
LoginCheckServlet:
package com.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class LoginCheckServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取到用戶名和密碼(這裡應到資料庫獲取)
// 這裡假設密碼是123456就可以通過
String username = request.getParameter("username");
String password = request.getParameter("password");
if ("123456".equals(password)) {
//合法,將用戶名加入session
request.getSession().setAttribute("username", username);
//請求轉發到admin.jsp
//根據過濾器原理:請求轉發的鏈接路徑是不會被過濾器攔截的,而重定向會被過濾器攔截
request.getRequestDispatcher("/manage/admin.jsp").forward(request, response);
} else {
//非法
//返回登錄頁面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
admin.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2022/11/28
Time: 17:05
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>admin</title>
<%--指定瀏覽器解析的目錄--%>
<base href="<%=request.getContextPath()%>/manage/">
</head>
<body>
<h1>後臺管理</h1>
<a href="#">用戶列表</a>||<a href="#">添加用戶</a>||<a href="#">刪除用戶</a>
<hr>
<%--該圖片放在manage目錄下--%>
<img src="myphoto.png" height="300"/>
</body>
</html>
ManageFilter:
package com.filter;
import javax.servlet.*;
import java.io.IOException;
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//當Tomcat創建filter後,會調用該方法,進行初始化
System.out.println("ManageFilter init方法被調用...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//當每次調用該filter時,doFilter方法就會被調用
System.out.println("ManageFilter doFilter被調用...");
}
@Override
public void destroy() {
//當filter對像被銷毀時,就會調用該方法
System.out.println("ManageFilter destroy被調用...");
}
}
在web.xml配置Filter和Servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--filter一般寫在其他servlet的前面-->
<!--
1.filter的配置和servlet非常相似,filter也是被Tomcat管理和維護的
(它的創建和銷毀也是tomcat來處理的)
2.<url-pattern> 配置的是filter生效的規則。即當請求的url和其匹配的時候,會觸發調用該filter
3. /manage/* 裡面第一個斜杠/,會解析成http://ip:port/工程路徑
4. 完整的路徑就是 http://ip:port/工程路徑/manage/*
意為當請求的url資源滿足該條件時,都會調用該filter
-->
<filter>
<filter-name>ManageFilter</filter-name>
<filter-class>com.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManageFilter</filter-name>
<url-pattern>/manage/*</url-pattern>
</filter-mapping>
<!--配置servlet-->
<servlet>
<servlet-name>LoginCheckServlet</servlet-name>
<servlet-class>com.servlet.LoginCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginCheckServlet</servlet-name>
<url-pattern>/loginCheckServlet</url-pattern>
</servlet-mapping>
</web-app>
redeployTomcat,可以看到後臺輸出:
這說明filter對象是由Tomcat去創建的。我們先來看一下filter底層實現。
3.3filter底層原理分析
filter的xml配置和servlet非常相似,filter也是被Tomcat管理和維護的。我們之前在學習servlet的時候知道了Tomcat是如何管理維護servlet的:
詳見:手動實現Tomcat底層機制+自己設計servlet->day03-實現02->3.4容器設計
同理,我們也可以這樣理解:Tomcat還維護了兩個關於filter的容器。一個容器filterURLMapping存放url-pattern和filter-name。另一個容器filterMapping存放filter-name和filter實例。
-
一旦Tomcat啟動以後:
- 把程式員配置的url和filter-name,放到另一容器filterURLMapping<String,String>中。
- 會將filter實例化,把filter-name和filter實例,放到filterMapping<String,Filter>容器裡面。
-
Tomcat接收到請求時:獲取請求中的url,和filterURLMapping容器中的url進行匹配。
- 如果匹配上了,在容器filterURLMapping中根據配置的url找到對應的filter-name
- 再到另一容器filterMapping中根據filter-name,找到對應filter實例,並調用實例,完成這個filter的doFilter方法。
-
並且,有了filter機制後,在調用servlet前會先匹配filter。
- 根據request對象封裝的uri,先到filterUrlMapping中匹配
- 如果匹配上了,就走上訴流程,最後調用相應filter對象的doFilter方法
- 如果沒有匹配上,就直接走我們後面的servlet/jsp/html等。(至於是servlet,又會走它那一套機制)
3.2-2代碼實現
回到之前的程式,我們在瀏覽器中直接訪問manage目錄下的資源admin.jsp,可以看到後臺輸出如下:
說明過濾器已經起作用了。
如果沒有顯示doFilter被調用,可能是因為瀏覽器緩存機制,建議在調試台點擊禁用緩存。
現在來完善ManageFilter過濾器裡面的業務代碼:
既然請求manage目錄下的資源首先要經過過濾器,那麼我們可以在過濾器中獲取session,如果發現session中已經保存了用戶信息(即登錄過),就放行;如果沒有session信息,就返回登錄頁面。
package com.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//當Tomcat創建filter後,會調用該方法,進行初始化
System.out.println("ManageFilter init方法被調用...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
//當每次調用該filter時,doFilter方法就會被調用
System.out.println("ManageFilter doFilter被調用...");
//如果這裡沒有調用繼續請求的方法,就會停止在這
// 如果繼續訪問目標資源-->等價於放行
// 在調用過濾器前,request對象已經被創建並封裝起來了
// 所以我們這裡就可以通過servletRequest獲取很多信息,比如訪問url,session,比如訪問的參數...
// 同時可以做事務管理,數據獲取,日誌管理等等
//獲取session
//用戶瀏覽器向伺服器發送帶有jsessionid的cookie,伺服器根據該sid找到對應session
//這樣寫後面還可以接續使用httpServletRequest的相關方法
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
//獲取username session對象,後面還可以繼續使用
Object username = session.getAttribute("username");
//如果在對應的session中,沒有找到用戶的用戶名,就說明沒有登錄過,否則就是登錄過
if (username != null) {
//用戶登錄成功過,直接放行
/**
* filterChain.doFilter(servletRequest, servletResponse);
* 1.doFilter代表繼續訪問目標資源
* 2.ServletRequest和 ServletResponse對象會傳遞給目標資源/文件
* 3.一定要理解filter傳遞的兩個對象,在一次http請求中,
* 和後面的servlet/jsp中的request,response是同一個對象
*/
filterChain.doFilter(servletRequest, servletResponse);
} else {//說明沒有登錄過,令其返回登錄頁面
servletRequest.getRequestDispatcher("/login.jsp")
.forward(servletRequest, servletResponse);
}
/**
* 關於chain.doFilter()方法:
* 在doFilter()方法中,在chain.doFilter()之前的代碼,一般是對request執行的過濾操作;
* 在chain.doFilter()後面的代碼,一般是對response執行的操作;
* chain.doFiter()執行下一個過濾器或者業務處理器。
* 如果在doFilter()方法中,不寫chain.doFilter(),業務無法繼續往下處理。
*/
}
@Override
public void destroy() {
//當filter對像被銷毀時,就會調用該方法
System.out.println("ManageFilter destroy被調用...");
}
}
3.4測試
-
redeployTomcat,在瀏覽器訪問login.jsp,因為login.jsp不在過濾器配置的url下,因此請求不會被攔截。
-
在login.jsp輸入正確的用戶數據,點擊登錄,可以看到後臺成功轉發到了manage目錄下的admin.jsp。
整個過程為:
-
用戶在login.jsp頁面輸入數據,login.jsp將表單數據提交到loginCheckServlet中檢查
-
servlet發現數據合法,於是給瀏覽器返回一個jsessionid,併在伺服器記憶體中創建和此jsessionid關聯的session,然後在此session中放入用戶數據,最後請求轉發到/manage/admin.jsp。
-
根據filter原理,請求轉發不會經過過濾器。但是在返回admin.jsp後,瀏覽器需要向伺服器請求圖片資源,並且,我們的圖片資源剛好匹配在filter配置的url。因此關於圖片的請求,將會經過過濾器。
一次請求只能返回一個資源,想要獲取圖片資源,因此瀏覽器發出了第二次請求,去獲取圖片資源。
綜上所述,在這次過程中,只經過了一次過濾器,後臺輸出如下:
-
-
這時,打開瀏覽器新頁面,訪問
http://localhost:8080/filter/manage/admin.jsp
,可以看到訪問成功,因為之前已經登錄過了。
3.5補充說明
3.5.1關於filterChain.doFilter()
filterChain.doFilter(servletRequest, servletResponse);
chain.doFilter將請求轉發給過濾器鏈下一個filter , 如果沒有filter那就是你請求的資源
在doFilter()方法中,在chain.doFilter()之前的代碼,一般是對request執行的過濾操作;在chain.doFilter()後面的代碼,一般是對response執行的操作;chain.doFiter()執行下一個過濾器或者業務處理器。如果在doFilter()方法中,不寫chain.doFilter(),業務無法繼續往下處理;
-
doFilter代表繼續訪問目標資源,如servlet,jsp,或是下一個filter
-
ServletRequest和 ServletResponse對象會傳遞給目標資源/文件
-
在一次http請求中,filter傳遞的兩個對象,和後面的servlet/jsp中的requestresponse是同一個對象
驗證:filter傳遞的兩個對象,在一次http請求中,與其後面的servlet/jsp中的request/response是同一對象
-
在ManageFilter中:
-
在admin.jsp中:
想法:我們直接訪問web應用下的/manage/admin.jsp,因為該資源在filter的url規則下,因此當訪問時,會調用過濾器,而過濾器中又將ServletRequest傳遞給admin.jsp。
我們同時在filter和admin.jsp中輸出request,查看對象的hash值如何。
後臺輸出如下:兩者的哈希值是一樣的。
這說明在一個請求裡面,過濾器裡面的ServletRequest,Servletresponse,和後面目標資源拿到的request,response是同一個對象。是可以共用的。
3.5.2關於過濾器中的ServletRequest
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
我們將過濾器中的ServletRequest轉為HttpServletRequest類型,由於HttpServletRequest中有很多方法,所以我們可以在過濾器中做一些日誌記錄。