過濾器第一篇【介紹、入門、簡單應用】

什麼是過濾器

過濾器是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沒有指定編碼的狀況下,獲取獲得的是亂碼

Servlet中如何解決中文亂碼問題,個人其餘博文中有:http://blog.csdn.net/hon_3y/article/details/54632004session

也就是說:若是我每次接受客戶端帶過來的中文數據,在Serlvet中都要設定編碼。這樣代碼的重複率過高了!!!!


有過濾器解決中文亂碼問題

有過濾器的狀況就不同了:只要我在過濾器中指定了編碼,可使全站的Web資源都是使用該編碼,而且重用性是很是理想的!


過濾器 API

只要Java類實現了Filter接口就能夠稱爲過濾器!Filter接口的方法也十分簡單:

其中init()和destory()方法就不用多說了,他倆跟Servlet是同樣的。只有在Web服務器加載和銷燬的時候被執行,只會被執行一次!

值得注意的是doFilter()方法,它有三個參數(ServletRequest,ServletResponse,FilterChain),從前兩個參數咱們能夠發現:過濾器能夠完成任何協議的過濾操做

那FilterChain是什麼東西呢?咱們看看:

FilterChain是一個接口,裏面又定義了doFilter()方法。這到底是怎麼回事啊??????

咱們能夠這樣理解:過濾器不僅僅只有一個,那麼咱們怎麼管理這些過濾器呢?在Java中就使用了鏈式結構把全部的過濾器都放在FilterChain裏邊,若是符合條件,就執行下一個過濾器(若是沒有過濾器了,就執行目標資源)

上面的話好像有點拗口,咱們能夠想象生活的例子:如今我想在茶杯上能過濾出石頭和茶葉出來。石頭在一層,茶葉在一層。因此茶杯的過濾裝置應該有兩層濾網。這個過濾裝置就是FilterChain,過濾石頭的濾網和過濾茶葉的濾網就是Filter。在石頭濾網中,茶葉是屬於下一層的,就把茶葉放行,讓茶葉的濾網過濾茶葉。過濾完茶葉了,剩下的就是茶(茶就能夠比喻成咱們的目標資源)


快速入門

寫一個簡單的過濾器

  • 實現Filter接口的Java類就被稱做爲過濾器
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 {

        }
    }

filter部署

過濾器和Servlet是同樣的,須要部署到Web服務器上的。

第一種方式:在web.xml文件中配置

filter

<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-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 對資源的多種調用方式進行攔截。

dispatcher

子元素能夠設置的值及其意義:
- REQUEST:當用戶直接訪問頁面時,Web容器將會調用過濾器。若是目標資源是經過RequestDispatcher的include()或forward()方法訪問時,那麼該過濾器就不會被調用。
- INCLUDE:若是目標資源是經過RequestDispatcher的include()方法訪問時,那麼該過濾器將被調用。除此以外,該過濾器不會被調用。
- FORWARD:若是目標資源是經過RequestDispatcher的forward()方法訪問時,那麼該過濾器將被調用,除此以外,該過濾器不會被調用。
- ERROR:若是目標資源是經過聲明式異常處理機制調用時,那麼該過濾器將被調用。除此以外,過濾器不會被調用。

第二種方式:經過註解配置

 @WebFilter(filterName = "FilterDemo1",urlPatterns = "/*") 

上面的配置是「/*」,全部的Web資源都須要途徑過濾器

若是想要部分的Web資源進行過濾器過濾則須要指定Web資源的名稱便可!


過濾器的執行順序

上面已經說過了,過濾器的doFilter()方法是極其重要的,FilterChain接口是表明着全部的Filter,FilterChain中的doFilter()方法決定着是否放行下一個過濾器執行(若是沒有過濾器了,就執行目標資源)

測試一

  • 首先在過濾器的doFilter()中輸出一句話,而且調用chain對象的doFilter()方法
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        System.out.println("我是過濾器1");

        //執行這一句,說明放行(讓下一個過濾器執行,或者執行目標資源)
        chain.doFilter(req, resp);
    }
  • 咱們來訪問一下test.jsp頁面:

咱們發現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)。目標資源執行完後,回到過濾器上,繼續執行代碼,而後輸出」放行完成「

測試四

咱們再多加一個過濾器,看看執行順序。

  • 過濾器1
System.out.println("過濾器1開始執行");

        //執行這一句,說明放行(讓下一個過濾器執行,或者執行目標資源)
        chain.doFilter(req, resp);

        System.out.println("過濾器1開始完畢");
  • 過濾器2
System.out.println("過濾器2開始執行");
        chain.doFilter(req, resp);
        System.out.println("過濾器2開始完畢");
  • Servlet
 System.out.println("我是Servlet1"); 

當咱們訪問Servlet1的時候,看看控制檯會出現什麼:

執行順序是這樣的:先執行FilterDemo1,放行,執行FilterDemo2,放行,執行Servlet1,Servlet1執行完回到FilterDemo2上,FilterDemo2執行完畢後,回到FilterDemo1上


注意:過濾器之間的執行順序看在web.xml文件中mapping的前後順序的,若是放在前面就先執行,放在後面就後執行!若是是經過註解的方式配置,就比較urlPatterns的字符串大小


Filter簡單應用

  • filter的三種典型應用:
    • 一、能夠在filter中根據條件決定是否調用chain.doFilter(request, response)方法,便是否讓目標資源執行
    • 二、在讓目標資源執行以前,能夠對request\response做預處理,再讓目標資源執行
    • 三、在目標資源執行以後,能夠捕獲目標資源的執行結果,從而實現一些特殊的功能

禁止瀏覽器緩存全部動態頁面

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);
    }
  • 沒有過濾以前,響應頭是這樣的:

  • 過濾以後,響應頭是這樣的:


實現自動登錄

開發實體、集合模擬數據庫、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 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;
        }
    }
  • 開發dao
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> 

處理登錄的Servlet

//獲得客戶端發送過來的數據
        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) {
        List<User> userList = 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);
            }

        }
相關文章
相關標籤/搜索