摘要:我們很容易發(fā)現,過濾器可以比喻成一張濾網。這究竟是怎么回事啊我們可以這樣理解過濾器不單單只有一個,那么我們怎么管理這些過濾器呢在中就使用了鏈式結構。第一種方式在文件中配置用于注冊過濾器用于為過濾器指定一個名字,該元素的內容不能為空。
什么是過濾器
過濾器是Servlet的高級特性之一,也別把它想得那么高深,只不過是實現Filter接口的Java類罷了!
首先,我們來看看過濾器究竟Web容器的哪處:
從上面的圖我們可以發(fā)現,當瀏覽器發(fā)送請求給服務器的時候,先執(zhí)行過濾器,然后才訪問Web的資源。服務器響應Response,從Web資源抵達瀏覽器之前,也會途徑過濾器。。
我們很容易發(fā)現,過濾器可以比喻成一張濾網。我們想想現實中的濾網可以做什么:在泡茶的時候,過濾掉茶葉。那濾網是怎么過濾茶葉的呢?規(guī)定大小的網孔,只要網孔比茶葉小,就可以實現過濾了!
引申在Web容器中,過濾器可以做:過濾一些敏感的字符串【規(guī)定不能出現敏感字符串】、避免中文亂碼【規(guī)定Web資源都使用UTF-8編碼】、權限驗證【規(guī)定只有帶Session或Cookie的瀏覽器,才能訪問web資源】等等等,過濾器的作用非常大,只要發(fā)揮想象就可以有意想不到的效果
也就是說:當需要限制用戶訪問某些資源時、在處理請求時提前處理某些資源、服務器響應的內容對其進行處理再返回、我們就是用過濾器來完成的!
為什么需要用到過濾器直接舉例子來說明吧:
沒有過濾器解決中文亂碼問題如果我沒有用到過濾器:瀏覽器通過http請求發(fā)送數據給Servlet,如果存在中文,就必須指定編碼,否則就會亂碼!
jsp頁面提交中文數據給Servlet處理
Servlet沒有指定編碼的情況下,獲取得到的是亂碼
Servlet中如何解決中文亂碼問題,我的其他博文中有:http://blog.csdn.net/hon_3y/article/details/54632004
也就是說:如果我每次接受客戶端帶過來的中文數據,在Serlvet中都要設定編碼。這樣代碼的重復率太高了?。。。?/strong>
有過濾器解決中文亂碼問題有過濾器的情況就不一樣了:只要我在過濾器中指定了編碼,可以使全站的Web資源都是使用該編碼,并且重用性是非常理想的!
過濾器 API只要Java類實現了Filter接口就可以稱為過濾器!Filter接口的方法也十分簡單:
其中init()和destory()方法就不用多說了,他倆跟Servlet是一樣的。只有在Web服務器加載和銷毀的時候被執(zhí)行,只會被執(zhí)行一次!
值得注意的是doFilter()方法,它有三個參數(ServletRequest,ServletResponse,FilterChain),從前兩個參數我們可以發(fā)現:過濾器可以完成任何協(xié)議的過濾操作!
那FilterChain是什么東西呢?我們看看:
FilterChain是一個接口,里面又定義了doFilter()方法。這究竟是怎么回事?。??????
我們可以這樣理解:過濾器不單單只有一個,那么我們怎么管理這些過濾器呢?在Java中就使用了鏈式結構。把所有的過濾器都放在FilterChain里邊,如果符合條件,就執(zhí)行下一個過濾器(如果沒有過濾器了,就執(zhí)行目標資源)。
上面的話好像有點拗口,我們可以想象生活的例子:現在我想在茶杯上能過濾出石頭和茶葉出來。石頭在一層,茶葉在一層。所以茶杯的過濾裝置應該有兩層濾網。這個過濾裝置就是FilterChain,過濾石頭的濾網和過濾茶葉的濾網就是Filter。在石頭濾網中,茶葉是屬于下一層的,就把茶葉放行,讓茶葉的濾網過濾茶葉。過濾完茶葉了,剩下的就是茶(茶就可以比喻成我們的目標資源)
快速入門 寫一個簡單的過濾器實現Filter接口的Java類就被稱作為過濾器
public class FilterDemo1 implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //執(zhí)行這一句,說明放行(讓下一個過濾器執(zhí)行,如果沒有過濾器了,就執(zhí)行執(zhí)行目標資源) chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { } }filter部署
過濾器和Servlet是一樣的,需要部署到Web服務器上的。
第一種方式:在web.xml文件中配置 filterFilterDemo1 FilterDemo1 word_file /WEB-INF/word.txt
一個Filter攔截的資源可通過兩種方式來指定:Servlet 名稱和資源訪問的請求路徑
FilterDemo1 /*
REQUEST:當用戶直接訪問頁面時,Web容器將會調用過濾器。如果目標資源是通過RequestDispatcher的include()或forward()方法訪問時,那么該過濾器就不會被調用。
INCLUDE:如果目標資源是通過RequestDispatcher的include()方法訪問時,那么該過濾器將被調用。除此之外,該過濾器不會被調用。
FORWARD:如果目標資源是通過RequestDispatcher的forward()方法訪問時,那么該過濾器將被調用,除此之外,該過濾器不會被調用。
ERROR:如果目標資源是通過聲明式異常處理機制調用時,那么該過濾器將被調用。除此之外,過濾器不會被調用。
第二種方式:通過注解配置@WebFilter(filterName = "FilterDemo1",urlPatterns = "/*")
上面的配置是“/*”,所有的Web資源都需要途徑過濾器
如果想要部分的Web資源進行過濾器過濾則需要指定Web資源的名稱即可!
過濾器的執(zhí)行順序上面已經說過了,過濾器的doFilter()方法是極其重要的,FilterChain接口是代表著所有的Filter,FilterChain中的doFilter()方法決定著是否放行下一個過濾器執(zhí)行(如果沒有過濾器了,就執(zhí)行目標資源)。
測試一首先在過濾器的doFilter()中輸出一句話,并且調用chain對象的doFilter()方法
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println("我是過濾器1"); //執(zhí)行這一句,說明放行(讓下一個過濾器執(zhí)行,或者執(zhí)行目標資源) chain.doFilter(req, resp); }
我們來訪問一下test.jsp頁面:
我們發(fā)現test.jsp(我們的目標資源)成功訪問到了,并且在服務器上也打印了字符串!
測試二我們來試試把chain.doFilter(req, resp);這段代碼注釋了看看!
test.jsp頁面并沒有任何的輸出(也就是說,并沒有訪問到jsp頁面)。
測試三直接看下面的代碼。我們已經知道了”準備放行“會被打印在控制臺上和test.jsp頁面也能被訪問得到,但“放行完成“會不會打印在控制臺上呢?
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println("準備放行"); //執(zhí)行這一句,說明放行(讓下一個過濾器執(zhí)行,或者執(zhí)行目標資源) chain.doFilter(req, resp); System.out.println("放行完成"); }
答案也非常簡單,肯定會打印在控制臺上的。我們來看看:
注意,它的完整流程順序是這樣的:客戶端發(fā)送http請求到Web服務器上,Web服務器執(zhí)行過濾器,執(zhí)行到”準備放行“時,就把字符串輸出到控制臺上,接著執(zhí)行doFilter()方法,Web服務器發(fā)現沒有過濾器了,就執(zhí)行目標資源(也就是test.jsp)。目標資源執(zhí)行完后,回到過濾器上,繼續(xù)執(zhí)行代碼,然后輸出”放行完成“
測試四我們再多加一個過濾器,看看執(zhí)行順序。
過濾器1
System.out.println("過濾器1開始執(zhí)行"); //執(zhí)行這一句,說明放行(讓下一個過濾器執(zhí)行,或者執(zhí)行目標資源) chain.doFilter(req, resp); System.out.println("過濾器1開始完畢");
過濾器2
System.out.println("過濾器2開始執(zhí)行"); chain.doFilter(req, resp); System.out.println("過濾器2開始完畢");
Servlet
System.out.println("我是Servlet1");
當我們訪問Servlet1的時候,看看控制臺會出現什么:
執(zhí)行順序是這樣的:先執(zhí)行FilterDemo1,放行,執(zhí)行FilterDemo2,放行,執(zhí)行Servlet1,Servlet1執(zhí)行完回到FilterDemo2上,FilterDemo2執(zhí)行完畢后,回到FilterDemo1上
注意:過濾器之間的執(zhí)行順序看在web.xml文件中mapping的先后順序的,如果放在前面就先執(zhí)行,放在后面就后執(zhí)行!如果是通過注解的方式配置,就比較urlPatterns的字符串優(yōu)先級
Filter簡單應用
filter的三種典型應用:
1、可以在filter中根據條件決定是否調用chain.doFilter(request, response)方法,即是否讓目標資源執(zhí)行
2、在讓目標資源執(zhí)行之前,可以對requestresponse作預處理,再讓目標資源執(zhí)行
3、在目標資源執(zhí)行之后,可以捕獲目標資源的執(zhí)行結果,從而實現一些特殊的功能
禁止瀏覽器緩存所有動態(tài)頁面public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //讓Web資源不緩存,很簡單,設置http中response的請求頭即可了! //我們使用的是http協(xié)議,ServletResponse并沒有能夠設置請求頭的方法,所以要強轉成HttpServletRequest //一般我們寫Filter都會把他倆強轉成Http類型的 HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; response.setDateHeader("Expires", -1); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); //放行目標資源的response已經設置成不緩存的了 chain.doFilter(request, response); }
沒有過濾之前,響應頭是這樣的:
過濾之后,響應頭是這樣的:
實現自動登陸 開發(fā)實體、集合模擬數據庫、Dao實體:
private String username ; private String password; public User() { } public User(String username, String password) { this.username = username; this.password = password; } //各種setter和getter
集合模擬數據庫
public class UserDB { private static Listusers = new ArrayList<>(); static { users.add(new User("aaa", "123")); users.add(new User("bbb", "123")); users.add(new User("ccc", "123")); } public static List getUsers() { return users; } public static void setUsers(List users) { UserDB.users = users; } }
開發(fā)dao
public User find(String username, String password) { List登陸界面userList = UserDB.getUsers(); //遍歷List集合,看看有沒有對應的username和password for (User user : userList) { if (user.getUsername().equals(username) && user.getPassword().equals(password)) { return user; } } return null; }
處理登陸的Servlet
//得到客戶端發(fā)送過來的數據 String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao userDao = new UserDao(); User user = userDao.find(username, password); if (user == null) { request.setAttribute("message", "用戶名或密碼是錯的!"); request.getRequestDispatcher("/message.jsp").forward(request, response); } //如果不是為空,那么在session中保存一個屬性 request.getSession().setAttribute("user", user); request.setAttribute("message", "恭喜你,已經登陸了!"); //如果想要用戶關閉了瀏覽器,還能登陸,就必須要用到Cookie技術了 Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + user.getPassword()); //設置Cookie的最大聲明周期為用戶指定的 cookie.setMaxAge(Integer.parseInt(request.getParameter("time")) * 60); //把Cookie返回給瀏覽器 response.addCookie(cookie); //跳轉到提示頁面 request.getRequestDispatcher("/message.jsp").forward(request, response);過濾器
HttpServletResponse response = (HttpServletResponse) resp; HttpServletRequest request = (HttpServletRequest) req; //如果用戶沒有關閉瀏覽器,就不需要Cookie做拼接登陸了 if (request.getSession().getAttribute("user") != null) { chain.doFilter(request, response); return; } //用戶關閉了瀏覽器,session的值就獲取不到了。所以要通過Cookie來自動登陸 Cookie[] cookies = request.getCookies(); String value = null; for (int i = 0; cookies != null && i < cookies.length; i++) { if (cookies[i].getName().equals("autoLogin")) { value = cookies[i].getValue(); } } //得到Cookie的用戶名和密碼 if (value != null) { String username = value.split(".")[0]; String password = value.split(".")[1]; UserDao userDao = new UserDao(); User user = userDao.find(username, password); if (user != null) { request.getSession().setAttribute("user", user); } } chain.doFilter(request, response);
效果:
改良我們直接把用戶名和密碼都放在了Cookie中,這是明文的。懂點編程的人就會知道你的賬號了。
于是乎,我們要對密碼進行加密!
Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + md5.md5(user.getPassword()));
在過濾器中,加密后的密碼就不是數據庫中的密碼的。所以,我們得在Dao添加一個功能【根據用戶名,找到用戶】
public User find(String username) { ListuserList = UserDB.getUsers(); //遍歷List集合,看看有沒有對應的username和password for (User user : userList) { if (user.getUsername().equals(username)) { return user; } } return null; }
在過濾器中,比較Cookie帶過來的md5密碼和在數據庫中獲得的密碼(也經過md5)是否相同
//得到Cookie的用戶名和密碼 if (value != null) { String username = value.split(".")[0]; String password = value.split(".")[1]; //在Cookie拿到的密碼是md5加密過的,不能直接與數據庫中的密碼比較 UserDao userDao = new UserDao(); User user = userDao.find(username); //通過用戶名獲得用戶信息,得到用戶的密碼,用戶的密碼也md5一把 String dbPassword = md5.md5(user.getPassword()); //如果兩個密碼匹配了,就是正確的密碼了 if (password.equals(dbPassword)) { request.getSession().setAttribute("user", user); } }
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章的同學,可以關注微信公眾號:Java3y
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/70947.html
摘要:采用完全獨立于任何程序語言的文本格式,使成為理想的數據交換語言為什么需要提到,我們就應該和來進行對比。也是一種存儲和交換文本信息的手段。那么好在哪里呢比更小更快,更易解析。使用的時候,也支持將轉成但是,我們不一定使用框架來做開發(fā)呀。 什么是JSON JSON:JavaScript Object Notation 【JavaScript 對象表示法】 JSON 是存儲和交換文本信息的語法...
摘要:目錄前言架構安裝第一個爬蟲爬取有道翻譯創(chuàng)建項目創(chuàng)建創(chuàng)建解析運行爬蟲爬取單詞釋義下載單詞語音文件前言學習有一段時間了,當時想要獲取一下百度漢字的解析,又不想一個個漢字去搜,復制粘貼太費勁,考慮到爬蟲的便利性,這篇文章是介紹一個爬蟲框架, 目錄 前言 架構 安裝 第一個爬蟲:爬取有道翻譯 創(chuàng)建項目 創(chuàng)建Item 創(chuàng)建Spider 解析 運行爬蟲-爬取單詞釋義 下載單詞語音文件 ...
摘要:相信很多人在格式化字符串的時候都用的語法,提出一種更先進的格式化方法并成為的標準用來替換舊的格式化語法,從開始已經實現了這一方法其它解釋器未考證。 showImg(https://segmentfault.com/img/remote/1460000018650325); 相信很多人在格式化字符串的時候都用%s % v的語法,PEP 3101 提出一種更先進的格式化方法 str.for...
摘要:前言由于寫的文章已經是有點多了,為了自己和大家的檢索方便,于是我就做了這么一個博客導航。 前言 由于寫的文章已經是有點多了,為了自己和大家的檢索方便,于是我就做了這么一個博客導航。 由于更新比較頻繁,因此隔一段時間才會更新目錄導航哦~想要獲取最新原創(chuàng)的技術文章歡迎關注我的公眾號:Java3y Java3y文章目錄導航 Java基礎 泛型就這么簡單 注解就這么簡單 Druid數據庫連接池...
閱讀 2361·2021-10-11 10:59
閱讀 2624·2021-10-11 10:58
閱讀 3339·2021-09-08 09:35
閱讀 3876·2021-09-02 15:21
閱讀 1495·2019-08-30 15:53
閱讀 2644·2019-08-29 14:16
閱讀 2097·2019-08-26 14:00
閱讀 2982·2019-08-26 13:52