過濾器是Servlet的高級特性之一,也別把它想得那麼高深,只不過是實現Filter接口的Java類罷了!java
首先,咱們來看看過濾器究竟Web容器的哪處:web
從上面的圖咱們能夠發現,當瀏覽器發送請求給服務器的時候,先執行過濾器,而後才訪問Web的資源。服務器響應Response,從Web資源抵達瀏覽器以前,也會途徑過濾器。數據庫
咱們很容易發現,過濾器能夠比喻成一張濾網。咱們想一想現實中的濾網能夠作什麼:在泡茶的時候,過濾掉茶葉。那濾網是怎麼過濾茶葉的呢?規定大小的網孔,只要網孔比茶葉小,就能夠實現過濾了!編程
引伸在Web容器中,過濾器能夠作:過濾一些敏感的字符串【規定不能出現敏感字符串】、避免中文亂碼【規定Web資源都使用UTF-8編碼】、權限驗證【規定只有帶Session或Cookie的瀏覽器,才能訪問web資源】等等等,過濾器的做用很是大,只要發揮想象就能夠有意想不到的效果瀏覽器
也就是說:當須要限制用戶訪問某些資源時、在處理請求時提早處理某些資源、服務器響應的內容對其進行處理再返回、咱們就是用過濾器來完成的!緩存
直接舉例子來講明吧:服務器
若是我沒有用到過濾器:瀏覽器經過http請求發送數據給Servlet,若是存在中文,就必須指定編碼,不然就會亂碼!markdown
jsp頁面提交中文數據給Servlet處理cookie
<form action="${pageContext.request.contextPath}/Demo1" method="post"> <input type="text" name="username"> <input type="submit" value="提交"> </form>
Servlet中如何解決中文亂碼問題,個人其餘博文中有:http://blog.csdn.net/hon_3y/article/details/54632004session
也就是說:若是我每次接受客戶端帶過來的中文數據,在Serlvet中都要設定編碼。這樣代碼的重複率過高了!!!!
有過濾器的狀況就不同了:只要我在過濾器中指定了編碼,可使全站的Web資源都是使用該編碼,而且重用性是很是理想的!
只要Java類實現了Filter接口就能夠稱爲過濾器!Filter接口的方法也十分簡單:
其中init()和destory()方法就不用多說了,他倆跟Servlet是同樣的。只有在Web服務器加載和銷燬的時候被執行,只會被執行一次!
值得注意的是doFilter()方法,它有三個參數(ServletRequest,ServletResponse,FilterChain),從前兩個參數咱們能夠發現:過濾器能夠完成任何協議的過濾操做!
那FilterChain是什麼東西呢?咱們看看:
FilterChain是一個接口,裏面又定義了doFilter()方法。這到底是怎麼回事啊??????
咱們能夠這樣理解:過濾器不僅僅只有一個,那麼咱們怎麼管理這些過濾器呢?在Java中就使用了鏈式結構。把全部的過濾器都放在FilterChain裏邊,若是符合條件,就執行下一個過濾器(若是沒有過濾器了,就執行目標資源)。
上面的話好像有點拗口,咱們能夠想象生活的例子:如今我想在茶杯上能過濾出石頭和茶葉出來。石頭在一層,茶葉在一層。因此茶杯的過濾裝置應該有兩層濾網。這個過濾裝置就是FilterChain,過濾石頭的濾網和過濾茶葉的濾網就是Filter。在石頭濾網中,茶葉是屬於下一層的,就把茶葉放行,讓茶葉的濾網過濾茶葉。過濾完茶葉了,剩下的就是茶(茶就能夠比喻成咱們的目標資源)
public class FilterDemo1 implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //執行這一句,說明放行(讓下一個過濾器執行,若是沒有過濾器了,就執行執行目標資源) chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { } }
過濾器和Servlet是同樣的,須要部署到Web服務器上的。
<filter>
用於註冊過濾器
<filter> <filter-name>FilterDemo1</filter-name> <filter-class>FilterDemo1</filter-class> <init-param> <param-name>word_file</param-name> <param-value>/WEB-INF/word.txt</param-value> </init-param> </filter>
<filter-name>
用於爲過濾器指定一個名字,該元素的內容不能爲空。<filter-class>
元素用於指定過濾器的完整的限定類名。<init-param>
元素用於爲過濾器指定初始化參數,它的子元素指定參數的名字,<param-value>
指定參數的值。在過濾器中,能夠使用FilterConfig接口對象來訪問初始化參數。<filter-mapping>
元素用於設置一個Filter 所負責攔截的資源。
一個Filter攔截的資源可經過兩種方式來指定:Servlet 名稱和資源訪問的請求路徑
<filter-mapping> <filter-name>FilterDemo1</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<filter-name>
子元素用於設置filter的註冊名稱。該值必須是在元素中聲明過的過濾器的名字<url-pattern>
設置 filter 所攔截的請求路徑(過濾器關聯的URL樣式)<servlet-name>
指定過濾器所攔截的Servlet名稱。<dispatcher>
指定過濾器所攔截的資源被 Servlet 容器調用的方式,能夠是REQUEST,INCLUDE,FORWARD和ERROR之一,默認REQUEST。用戶能夠設置多個<dispatcher>
子元素用來指定 Filter 對資源的多種調用方式進行攔截。 子元素能夠設置的值及其意義:
- REQUEST:當用戶直接訪問頁面時,Web容器將會調用過濾器。若是目標資源是經過RequestDispatcher的include()或forward()方法訪問時,那麼該過濾器就不會被調用。
- INCLUDE:若是目標資源是經過RequestDispatcher的include()方法訪問時,那麼該過濾器將被調用。除此以外,該過濾器不會被調用。
- FORWARD:若是目標資源是經過RequestDispatcher的forward()方法訪問時,那麼該過濾器將被調用,除此以外,該過濾器不會被調用。
- ERROR:若是目標資源是經過聲明式異常處理機制調用時,那麼該過濾器將被調用。除此以外,過濾器不會被調用。
@WebFilter(filterName = "FilterDemo1",urlPatterns = "/*")
上面的配置是「/*」,全部的Web資源都須要途徑過濾器
若是想要部分的Web資源進行過濾器過濾則須要指定Web資源的名稱便可!
上面已經說過了,過濾器的doFilter()方法是極其重要的,FilterChain接口是表明着全部的Filter,FilterChain中的doFilter()方法決定着是否放行下一個過濾器執行(若是沒有過濾器了,就執行目標資源)。
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println("我是過濾器1"); //執行這一句,說明放行(讓下一個過濾器執行,或者執行目標資源) chain.doFilter(req, resp); }
咱們發現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("準備放行"); //執行這一句,說明放行(讓下一個過濾器執行,或者執行目標資源) chain.doFilter(req, resp); System.out.println("放行完成"); }
答案也很是簡單,確定會打印在控制檯上的。咱們來看看:
注意,它的完整流程順序是這樣的:客戶端發送http請求到Web服務器上,Web服務器執行過濾器,執行到」準備放行「時,就把字符串輸出到控制檯上,接着執行doFilter()方法,Web服務器發現沒有過濾器了,就執行目標資源(也就是test.jsp)。目標資源執行完後,回到過濾器上,繼續執行代碼,而後輸出」放行完成「
咱們再多加一個過濾器,看看執行順序。
System.out.println("過濾器1開始執行"); //執行這一句,說明放行(讓下一個過濾器執行,或者執行目標資源) chain.doFilter(req, resp); System.out.println("過濾器1開始完畢");
System.out.println("過濾器2開始執行"); chain.doFilter(req, resp); System.out.println("過濾器2開始完畢");
System.out.println("我是Servlet1");
當咱們訪問Servlet1的時候,看看控制檯會出現什麼:
執行順序是這樣的:先執行FilterDemo1,放行,執行FilterDemo2,放行,執行Servlet1,Servlet1執行完回到FilterDemo2上,FilterDemo2執行完畢後,回到FilterDemo1上
注意:過濾器之間的執行順序看在web.xml文件中mapping的前後順序的,若是放在前面就先執行,放在後面就後執行!若是是經過註解的方式配置,就比較urlPatterns的字符串大小
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //讓Web資源不緩存,很簡單,設置http中response的請求頭便可了! //咱們使用的是http協議,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); }
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 List<User> users = new ArrayList<>(); static { users.add(new User("aaa", "123")); users.add(new User("bbb", "123")); users.add(new User("ccc", "123")); } public static List<User> getUsers() { return users; } public static void setUsers(List<User> users) { UserDB.users = users; } }
public User find(String username, String password) { List<User> userList = UserDB.getUsers(); //遍歷List集合,看看有沒有對應的username和password for (User user : userList) { if (user.getUsername().equals(username) && user.getPassword().equals(password)) { return user; } } return null; }
<form action="${pageContext.request.contextPath}/LoginServlet"> 用戶名<input type="text" name="username"> <br> 密碼<input type="password" name="password"> <br> <input type="radio" name="time" value="10">10分鐘 <input type="radio" name="time" value="30">30分鐘 <input type="radio" name="time" value="60">1小時 <br> <input type="submit" value="登錄"> </form>
//獲得客戶端發送過來的數據 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()));
public User find(String username) { List<User> userList = UserDB.getUsers(); //遍歷List集合,看看有沒有對應的username和password for (User user : userList) { if (user.getUsername().equals(username)) { return user; } } return null; }
//獲得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); } }