1. 程式人生 > 實用技巧 >Servlet高階Fliter(過濾器)&Listener(監聽器)

Servlet高階Fliter(過濾器)&Listener(監聽器)

“別小看任何人,越不起眼的人。往往會做些讓人想不到的事。"你好我是夢陽辰,快來和我一起學習吧!

文章目錄

01.Filter概述

Filter和Listener是Servlet規範中的兩個高階特性,不同於Servlet,他們不用於處理客戶端請求。

Filter用於對request,response物件進行修改。
Filter被稱作過濾器,其基本功能就是對Servlet容器呼叫Servlet的過程進行攔截,從而在Servlet進行響應處理前後實現一些特殊的功能。這就好比現實中的汙水淨化裝置,它可以看作一個過濾器,專門用於過濾汙水雜質。

當瀏覽器訪問伺服器中的目標資源時,會被Filter攔截,在Filter中進行預處理操作,然後再將請求轉發給目標資源。

當伺服器接收到這個請求後會對其進行響應,在伺服器處理響應的過程中,也需要先將響應結果傳送給過濾器,在過濾器中對響應結果進行處理後,才會傳送給客戶端。

過濾器的作用:
一般完成通用性的操作。
比如:登入驗證,判斷使用者是否登入;統一編碼處理,敏感字元處理等。

其實Filter過濾器就是實現了javax.servlet.Filter介面的類,在javax.servlet.Filter定義了三個方法。

Listener用於對context,session,request事件進行監聽。

02.Filter快速入門

步驟:
1.定義一個類,實現介面javax.servlet.Filter。

2.複寫方法

3.配置攔截路徑
3.1.web.xml配置
3.2.註解配置
javax.servlet.Filter定義了三個方法

@WebFilter("/*")//攔截所有資源
public class FilterTest1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //初始化過濾器,在Web程式載入的時候呼叫,配置初始化引數
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.print("我是過濾器!");

        //是否放行,即轉發到請求資源
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        //用於釋放被Filter開啟的資源,當Web伺服器解除安裝Filter物件之前被呼叫
    }
}

web.xml配置
取消註解配置,使用web.xml配置。

 <filter>
        <filter-name>FilterTest1</filter-name>
        <filter-class>filter.FilterTest1</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>FilterTest1</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

03.Filter深入

過濾器的流程:

@WebFilter("/*")
public class FilterTest2 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //用於攔截使用者的請求,如果和當前過濾器的攔截路徑匹配,此方法會被呼叫
        //對request物件請求訊息增強
        System.out.println("我被執行了request!");

        chain.doFilter(req, resp);

        //對response物件的響應訊息增強
        System.out.println("我被又執行了response!");
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

攔截路徑的配置:
具體資源路徑:/index.jsp 只有訪問index.jsp資源時,過濾器才會被執行。

2.攔截目錄:/user/* 訪問/uer下的所有資源時,過濾器都會被執行。

3.字尾名攔截:*.jsp 訪問所有後綴名為jsp資源時,過濾器執行。

4.攔截所有資源:/*
攔截方式的配置:

資源被訪問的方式:
請求轉發過濾器不會被執行。
1.註解配置,可以配置多個值
設定dispatcherTypes屬性

REQUEST:預設值,瀏覽器直接請求資源
FORWARD:轉發訪問資源
INCLUDE:包含訪問資源
ERROR:錯誤跳轉資源
ASYNC:非同步訪問資源

2.web.xml註釋

//瀏覽器直接請求資源時,會被執行,轉發不會
@WebFilter(value = "/*",dispatcherTypes = DispatcherType.REQUEST)
public class FilterTest3 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("我被執行啦!");
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}
	@WebFilter(value = "/*",
	dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD})
<filter>
        <filter-name>FilterTest1</filter-name>
        <filter-class>filter.FilterTest1</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>FilterTest1</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

過濾器鏈(多個過濾器):

執行順序
先執行過濾器1,再執行過濾器2,回來先執行過濾器2,再執行過濾器1。

怎麼判斷過濾器誰在前面:
1.註解配置
按照類名的字串比較規則,較小的先執行。
如:AFilter,BFilter
AFilter就先執行。

2.web.xml配置:

<filter-mapping>

誰定義在上面,誰就先執行。

04.Filter案例

案例一:登入驗證
1.訪問某些資源,驗證其是否登入

2.如果登入了,則直接放行。

3.如果沒有登入,則跳轉到登入頁面,提示,“你尚未登入,請先登入”。

/**
 * 登入驗證的過濾器
 */
@WebFilter("/*")//除了登入相關資源外(如login.jsp)
public class FilterTest4 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1.強轉
        HttpServletRequest request = (HttpServletRequest)req;
        //2.獲取資源請求路徑
        String uri = request.getRequestURI();
        //排除登入相關資源,css/js/fonts等
        if(uri.contains("/login.jsp")||uri.contains("/loginServlet")||uri.contains("/css/")||uri.contains("/js/")){
            //使用者就是像登入,放行
            chain.doFilter(req, resp);
        }else{
            //不包含,需要驗證使用者是否登入
            //3.從獲取session中獲取user
            Object user = request.getSession().getAttribute("user");
            if(user!=null){
                //登入了,放行
                chain.doFilter(req, resp);
            }else{
                //沒有登入,跳轉到登入頁面
                request.setAttribute("login_msg","你為登入,請先登入!");
                request.getRequestDispatcher("/login.jsp");
            }
        }

    }

    public void init(FilterConfig config) throws ServletException {

    }

}

案例二:敏感詞彙的過濾
分析:

案例需要對request物件進行增強。

那如進行增強呢?
增強物件的功能
設計模式:一些通用的解決固定問題的方式。
裝飾模式:

代理模式:
概念:
1.真實物件:被代理的物件。
2.代理物件
3.代理模式:代理物件代理真實物件,達到增強真實物件的目的。
實現方式:
1.靜態代理
在一個類檔案描述代理模式。

2.動態代理
在記憶體中形成代理類(在記憶體中動態生成)。

注:請先看以下動態代理相關知識再看案例二的實現
案例二實現:
1.對requset物件進行增強,增強獲取引數相關方法。
2.放行,傳遞代理物件。
程式碼實現:

/**
 * 敏感詞彙過濾器
 */
@WebFilter("/*")
public class FilterSensitiveWords implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //建立代理物件,增強getParameter方法
        ServletRequest proxy_req=(ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //增強getParameter方法
                //判斷是否是該方法
                if(method.getName().equals("getParameter")){
                    //增強返回值
                    //獲取返回值
                    String value = (String)method.invoke(req,args);
                    if(value!=null){
                        for(String str:list){
                            if(value.contains(str)){
                                value = value.replaceAll(str,"**");
                            }
                        }
                    }
                    return value;
                }
                return method.invoke(req,args);
            }
        });
        //2.放行
        chain.doFilter(proxy_req, resp);
    }
    private List<String> list = new ArrayList<>();//敏感詞彙
    public void init(FilterConfig config) throws ServletException {

        try {
            //1.載入配置檔案(獲取檔案的真實路徑)
            ServletContext servletContext = config.getServletContext();
            String realPath = servletContext.getRealPath("/WEB-INF/classes/SensitiveWords.txt");
            //2.讀取檔案
            BufferedReader br = new BufferedReader(new FileReader(realPath));
            //3.將檔案的每一行載入到list中
            String line =null;
            while ((line = br.readLine())!=null){
                list.add(line);
            }
            br.close();
            System.out.println(list);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

測試的servlet:

@WebServlet("/ServletSensitiveWordsTest")
public class ServletSensitiveWordsTest extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        String msg =request.getParameter("msg");
        System.out.println(name+":"+msg);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

結果:

重點:注意路徑問題:
SensitiveWords在src原始檔目錄下。
//1.載入配置檔案(獲取檔案的真實路徑)
ServletContext servletContext = config.getServletContext();
String realPath = servletContext.getRealPath
("/WEB-INF/classes/SensitiveWords.txt");

05.動態代理

實現步驟:
1.代理物件和真實物件實現相同的介面。

2.代理物件= Proxy.newProxyInstance();

3.使用代理物件呼叫方法。

4.增強方法。
增強方式:
1.增強引數列表。

2.增強返回值型別。

3.增強方法體執行邏輯。
練習理解:

package proxy;

public interface SaleComputer {
    public String sale(double money);
    public void show();
}

/**
 * 真實類
 */
public class ASUS implements SaleComputer{
    @Override
    public String sale(double money) {
        System.out.println("花了"+money+"拍下一臺電腦!");
        return "ASUS";
    }

    @Override
    public void show() {
        System.out.println("展示電腦!");
    }
}
public class ProxyTest {
    public static void main(String[] args) {
        //建立真實物件
        ASUS asus1 = new ASUS();
        //動態代理增強ASUS物件
        /*
        三個引數:
        1.類載入器:真實物件.getClass().getClassLoader()
        2.介面陣列:真實物件.getClass().getInterfaces()
        3.處理器:new InvocationHandler()
         */
        SaleComputer proxy_asus=(SaleComputer) Proxy.newProxyInstance(asus1.getClass().getClassLoader(), asus1.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 代理邏輯編寫的方法:代理物件呼叫的所有方法都會觸發該方法執行
             * @param proxy 代理物件
             * @param method 代理物件呼叫的方法被封裝成物件
             * @param args 代理物件呼叫方法時,傳遞的實際引數
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*System.out.println("該方法執行了...");
                System.out.println(method.getName());*/
                //使用真實物件呼叫該方法
               /* Object obj = method.invoke(asus1,args);*/

                //增強引數;判斷是否時sale方法
                if(method.getName().equals("sale")){
                    //增強引數
                    double money = (double)args[0];
                    money*=0.8;
                    //使用真實物件呼叫該方法
                    String  obj = (String) method.invoke(asus1,money);

                    //增強返回值型別

                    return  obj+"_滑鼠墊";
                }else {
                    Object obj = method.invoke(asus1,args);
                    return obj;
                }

            }
        });

        //3.呼叫方法
        String computer = proxy_asus.sale(9000);
        System.out.println(computer);
    }
}

06.Listener概述

概念:web的三大元件之一。
事件監聽機制
程式開發中,經常需要對某些事件進行監聽,如滑鼠單擊事件,監聽鍵盤按下事件等,此時就需要監聽器。

事件:使用者的一個操作,如點選按鈕…

事件源:產生事件的物件。

監聽器:負責監聽發生在事件源上的事件。

註冊監聽:將事件,事件源,監聽器繫結在一起。當事件源上發生某個事件後,執行監聽器程式碼。

ServletContextListener:監聽ServletContext物件的建立和銷燬。

//Servlet物件被銷燬前會呼叫此方法
void contextDestroyed(ServletContextEvent sce) 

//ServletContext物件被建立後會呼叫該方法。
void contextInitialized(ServletContextEvent sce)   

07.ServletContextListener

步驟:
1.定義一個類實現ServletContextListener介面。

2.複寫方法。

3.配置
1.web.xml

<listener>
        <listener-class>listener/ServletContextListenerTest1</listener-class>
    </listener>

指定初始化引數:

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
    </context-param>

2.註解配置

@WebListener
@WebListener
public class ServletContextListenerTest1 implements ServletContextListener {
    //監聽ServletContext建立,ServletContext物件伺服器啟動自動建立
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //載入資原始檔
        //1.獲取ServletContext物件
        ServletContext servletContext = sce.getServletContext();
        //2.載入資原始檔
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
        //3.獲取真實路徑
        String realPath = servletContext.getRealPath(contextConfigLocation);
        //4.載入進記憶體
        try {
            FileInputStream fis = new FileInputStream(realPath);
            System.out.println(fis);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    //在伺服器關閉後,ServletContext物件被銷燬。當伺服器正常關閉後該方法被呼叫
    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

一切事無法追求完美,唯有追求盡力而為。這樣心無壓力,出來的結果反而會更好。