2020-5:15 Filter&Listener

249 阅读8分钟

今日内容

1. Filter:过滤器
2. Listener:监听器

一、Filter:过滤器

1.概念
* 生活中的过滤器:净化器,空气净化器,土匪(筛选出给钱的,而且来回筛选两次)
* web中的过滤器:当访问服务器时过滤器会将请求拦下来,完成一些特殊工功能。

    * 过滤器的作用:比如浏览器访问多个服务器时,对每个服务器都有一些通用的功能需要设置。
                    那么就可以设一个过滤器将这些请求拦下来,统一完成这些通用的操作。
    * 通用的操作:如登录操作。统一编码处理。敏感字符过滤。

2.快速入门:
    1.步骤:
        1.定义一个类,实现接口Filter
        2.复写方法
        3.配置拦截路径
            1.web.xml
            2.注解
            
3.过滤器细节:
    1.web.xml配置
    ```
    <?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_3_1.xsd"
             version="3.1">
    
        <filter>
            <filter-name>demo1</filter-name>
            <filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>demo1</filter-name>
            <!--url-pattern:拦截路径-->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        
    </web-app>
    ```
    2.过滤器执行流程
        浏览器发出请求--->走过滤器--->过滤器放行--->请求相应资源--->响应浏览器--->
        再走过滤器--->走的是放行下面的代码。
        注意:过滤器的代码分两步才走完。
        ```
            public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
                //对request对象请求消息增强
                System.out.println("filterDemo2执行了...");
                
                //放行
                chain.doFilter(req, resp);
                
                //对response对象的响应消息增强
                System.out.println("filterDemo2又回来了...");
            }
        ```
    3.过滤器的生命流程
        1. public void init(FilterConfig config){}
            * 在服务器启动后,会创建Filter对象,然后调用init方法。
            * 用于加载资源,只执行一次
        
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain){}
            * 每一次请求被拦截资源时,会执行,所以可能执行多次
        
        public void destroy()
            * 在服务器关闭后,Filter对象被销毁,如果服务器是正常关闭,调用destroy方法
            * 用于释放资源,只执行一次
            
    4.过滤器配置详解
        * 拦截路径配置:
            1. 具体的资源路径:/index.jsp 只有访问index.jsp资源时,过滤器才会被执行。
            2. 目录拦截:/user/* 访问/user下的所有资源时,过滤器都会被执行。
            3. 后缀名拦截:*.jsp 访问所有后缀名为jsp时,过滤器都会被执行。
            4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
            
        * 拦截方式配置:资源被访问的方式。
            * 注解配置
                * 设置disPatcherTypes属性
                    1. REQUEST:默认值。浏览器直接请求资源
                    2. FORWARD:只拦截转发访问资源。
                    3. INCLUDE:包含访问资源
                    4. ERROR:错误跳转资源
                    5. ASYNC:异步访问资源
            * web.xml
                * 设置<dispatcher>REQUEST</dispatcher>标签即可
            
    5.过滤器链(配置多个过滤器)
        * 执行顺序:
            如果有两个过滤器:过滤器1和过滤器2
            过滤器1-->过滤器2-->访问资源-->过滤器2-->过滤器1
            filterDemo6执行了...
            filterDemo7执行了...
            index.jsp...
            filterDemo7回来了...
            filterDemo6回来了...
        * 那个过滤器先拦截,那个后拦截
            1.注解配置
                按照类名的字符串表规则:值小的先拦截
                如:AFilter 和 BFilter,逐个比较每个字符,AFilter先执行。
            2.web.xml配置
                谁定义在上面,谁先执行。
                    <filter-mapping>
                        <filter-name>demo1</filter-name>
                        &lt;!&ndash;url-pattern:拦截路径&ndash;&gt;
                        <url-pattern>/*</url-pattern>
                        <dispatcher>REQUEST</dispatcher>
                    </filter-mapping>
                哪个<filter-mapping>在上面,哪个先执行。

二、Filter案例

1.案例1_登录案例
    *需求:
	1. 访问day0508_case案例的资源。验证其是否登录
	3. 如果登录了,则直接放行。
	4. 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。

2.案例2_敏感词汇过滤
    *需求:
	1. 对day17_case案例录入的数据进行敏感词汇过滤
	2. 敏感词汇参考《敏感词汇.txt》
	3. 如果是敏感词汇,替换为 *** 
	
    请求index.jsp--->
    request给filter--->                             1.打印一次loginFilter:req
    经过过滤后发现没有登录,要转发到login.jsp--->   2.打印一次loginFilter返程
    ```
    (为什么会打印loginFilter返程:因为没有放行,只是跳转了,所以没有执行chain.doFilter()
    方法。那么就会将loginFilter过滤器doFilter()方法中剩余的代码执行完。不同于放行成功
    后,再返回来执行剩余的代码)
    ```
    转发请求login.jsp--->
    request再给filter-->                            3.打印一次loginFilter:req
    经过过滤发现可以直接访问--->
    显示登录页面--->                                4.打印一次loginFilter返程
    登陆验证,跳转到loginServlet-->
    又要将request给filter--->                       5.打印一次loginFilter:req
    经过过滤后发现可以放行--->                      
    访问到loginServlet--->                          6.打印:Servlet:req
    访问loginServlet成功后--->                      7.打印一次loginFilter返程                                    
    登录验证成功-->
    重定向到到index.jsp--->
    request再给filter-->                            8.打印一次loginFilter:req 
    经过过滤后发现已经登陆了,可以放行--->          9.打印一次loginFilter返程
    
    *每次打印的req都是一样的。
        说明Servlet和Filter拿到的都是同一个request。
    *疑问?
        因为默认的拦截配置的REQUEST,转发为什么也被拦截到了?是我上面的分析有问题吗?
        是因为拦截路径配置的是拦截所有路径吗?@WebFilter("/*")

分析:
    1. 需要对request对象进行增强。
       增强获取参数相关的方法。
    2. 放行。传递代理对象。
增强对象的功能:
    * 设计模式:一些通用的解决固定问题的方式
        1.装饰模式:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个
                    对象的功能。
        2.代理模式:所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口。
                    的接口
            * 概念:
                1.真实对象:被代理的对象。联想总公司
                2.代理对象:西安联想代理商
                3.代理模式:代理对象代理真实对象,来达到增强真实对象功能的目的。
            * 实现方式:
                1.静态代理:生成方式,有一个类文件描述代理模式
                2.动态代理:内存中来形成代理类。
                    * 实现步骤:
                        1.代理对象和真实对象实现相同的接口(兄弟关系)
                        2.代理对象:Proxy.newProxyInstance();
                        3.使用代理对象调用方法。
                        4.增强方法。
                    * 增强方式:
                        1.增强参数列表
                        2.增强返回值类型
                        3.增强方法体执行逻辑
            * 详见public class ProxyTest
```
public class ProxyTest {
public static void main(String[] args) {
    //1.创建真实对象
    Lenovo lenovo = new Lenovo();

    /**
     * 2.动态代理增强lenovo对象
     * Proxy.newProxyInstance()三个参数:
     *  1.类加载器:真实对象.getClass().getClassLoader()
     *  2.接口数组:真实对象.getClass().getInterfaces()-----生成的代理对象要实现的接口字节码数组
     *  3.处理器:  new InvocationHandler() {}
     *  返回一个代理对象: proxy_lenovo
     */

    SaleComputer proxy_lenovo = (SaleComputer)Proxy.newProxyInstance(
        lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), 
        new InvocationHandler() {
        /**
         * 编写代理逻辑的方法:代理对象调用的所有方法都会触发该方法执行。
         * 那么增强代码的逻辑就会写在此处。
         * @param proxy:代理对象
         * @param method:我在步骤3调用的方法sale(代理对象调用的方法),会被封装成一个对象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());
            System.out.println(args[0]);*/

            //1.增强参数
            //判断是否是sale方法
            if(method.getName().equals("sale")){
                double money = (double)args[0];
                money = money * 0.85; //进货价格折扣15%

                //3.增强方法提
                System.out.println("专车接。");
                String obj = (String)method.invoke(lenovo, money);
                System.out.println("免费送货。");

                //2.增强返回值:不仅仅返回电脑还有其他
                return obj + ",鼠标,键盘。";
            }else{
                //如果不是sale函数:按照真实对象中的方法执行
                Object obj = method.invoke(lenovo, args);
                return obj;
            }

            /**
             * Object obj = method.invoke(lenovo, args);
             *  1.真实对象lenovo执行method,并且参数是args。
             *      所以这句话相当于:使用真实对象调用method方法。所以如果参数args改变,那么调用真实对象中的该方法
             *  参数就是改变后的args。
             *  2.并且此处的返回值就是真实对象的返回值。
             *      所以返回值的不同,结果也会不同。
             *
             *  return obj; //3.返回值会传递给步骤3中代理对象调用方法的返回值。
             */

        }
    });

    //3.调用方法:其实不管你调用的什么方法,其实都没有被调用,真正执行的都是增强方法invoke()
    //会将调用的方法封装成一个对象传给步骤2中的method。
    String computer = proxy_lenovo.sale(8000);
    System.out.println(computer);
    /*proxy_lenovo.show();*/
}
}

```

三、Listener:监听器

* 概念:web的三大组件之一
    * 事件监听机制
        * 事件: 一件事情
        * 事件器:事件发生的地方。 按钮被点击了,按钮就是事件源。
        * 监听器:一个对象
        * 注册监听:将事件,事件源,监听器绑定在一起。当事件源发生某个事件后,执行监听代码。
        
* ServletContextListener:监听ServletContext的创建和销毁
  * Interface ServletContextListener :是一个接口,而且没有具体的实现类。
  * 两个方法
      1.void contextDestroyed(ServletContextEvent sce)  
        ServletContext对象被销毁之前会调用该方法
      2.void contextInitialized(ServletContextEvent sce) 
        ServletContext对象创建后会调用该方法。
        
    * 步骤:
        1.定义一个类,实现ServletContextListener接口
        2.复写方法
        3.配置:
            1.web.xml:<listener>指定监听器的类文件
                ```
                <listener>
                    <listener-class>cn.itcast.web.listener.ContextLoaderListener</listener-class>
                </listener>
                ```
                * 还可以初始化参数
                ```
                <context-param>
                    <param-name>contextConfigLocation</param-name>
                    <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
                </context-param>
                ```
            2.注解配置
                @WebListener //这个注解不需要指定路径。直接注解:注册了监听器。