• JavaWeb学习——监听器和过滤器


    监听器 与 Filters


     

    一、概述

        监听器是用于监听Web应用而实现了特定接口的Java类。监听器可以在事件发生前、后做一些有必要的处理。

        Servlet API提供了一系列的事件和事件监听接口。上层的servlet/JSP应用能够通过调用这些API进行事件驱动的开发。

        这里监听的所有事件都继承自java.util.EventObject对象。

     Servlet 中的监听器分类:

    •    监听三个域对象(ServletContext、HttpSession、ServletRequest)的创建和销毁的三个监听器。
    •    监听三个域对象的属性变更(添加、移除、替换)的三个监听器。
    •    监听HttpSession中JavaBean的状态变换的监听(两个)。

        Filters是拦截Request请求的对象: 在用户的请求访问资源前处理ServletRequest以及ServletResponse,它可以用于日志记录、解加密、Session检查、图像文件保护等。

        

    二、监听器

    2.1 监听器接口和注册

        监听器接口主要在 javax.servlet 和 javax.servlet.http 的包中。对应上文的三类,有以下这些接口:

        第一类:

    名称 作用
    javax.servlet.ServletContextListener    它能响应Servlet生命周期事件,它提供了ServletContext创建之后和ServletContext关闭之前的会被调用的方法。  
    javax.servlet.http.HttpSessionListener 它能响应HttpSession的创建、超时和失效事件。
    javax.servlet.http.HttpActivationListener   它在一个HttpSession激活或者失效时被调用。
    javax.servlet.ServletRequestListener 它能响应一个ServletRequest的创建或删除。

        第二类:

    名称 作用
    javax.servlet.ServletContextAttributeListener   它能响应ServletContext范围内的属性添加、删除、替换事件。 
    javax.servlet.http.HttpSessionAttributeListener  它能响应HttpSession范围内的属性添加、删除、替换事件。
    javax.servlet.http.HttpSessionBindingListener 可以实现这个接口来保存HttpSession范围的属性。当有属性从HttpSession添加或者删除时,它能做出响应。 
    javax.servlet.ServletRequestListener 它能响应ServletRequest范围的属性添加、删除、修改事件。

     编写一个监听器,只需要写一个Java类来实现对应的监听器接口就可以了。在Servlet 3.0和Servlet 3.1中提供了两种注册监听器的方法。一种是使用WebListener注解。例如:

    @WebListener
    public class ListenerClass implements ListenerInterface {
    }

        第二种是在部署描述文档中增加一个listener元素。

    <listener>
        <listener-class>fully-qualified listener class</listener-class>
    </listener>

    2.2 ServletContext 监听器

        2.2.1 ServletContextListener

        当ServletContext 初始化时,容器会调用所有注册的ServletContextListeners 的 contextInitialized方法。

        当ServletContext 将要销毁是,容器会调用所有注册的ServletContextListeners 的 contextDestroyed方法。

    void contextInitialized(ServletContextEvent event)
    
    void contextDestroyed(ServletContextEvent event)

        contextInitialized方法和contextDestroyed方法都会从容器获取到一个ServletContextEvent。

        javax.servlet.ServletContextEvent 是一个 java.util.EventObject的子类,它定义了一个访问ServletContext的getServletContext方法。

        2.2.2 ServletContextAttributeListener

        当一个ServletContext范围的属性被添加、删除或者替换时,ServletContextAttributeListener接口的实现类会接受到消息。这个接口定义了三个方法:

    void attributeAdded(ServletContextAttributeEvent event)
    
    void attributeRemoved(ServletContextAttributeEvent event)
    
    void attributeReplaced(ServletContextAttributeEvent event)

        这三个方法都能获取到一个 ServletContextAttributeEvent 的对象,通过这个对象可以获取属性的名称和值。

    2.3 Session Listeners

        2.3.1 HttpSessionListener

         当一个HttpSession创建或者销毁时,容器都会通知所有的HttpSessionListener监听器,HttpSessionListener接口有两个方法:sessionCreated 和 sessionDestroyed。

    void sessionCreated(HttpSessionEvent event)
    
    void sessionDestroyed(HttpSessionEvent event)

        这两个方法都可以接收到一个继承于 java.util.EventObject 的HttpSessionEvent 对象。可以通过调用HttpSessionEvent 对象的getSession方法来获取当前的HttpSession。

        下面举一个例子,这个监听器可以用来统计HttpSession的数量。

    package app08a.listener;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    
    @WebListener
    public class SessionListener implements HttpSessionListener, ServletContextListener{
        
        @Override
        public void contextInitialized(ServletContextEvent e){
            ServletContext servletContext = e.getServletContext();
            servletContext.setAttribute("userCount", new AtomicInteger());
        }
        
        @Override
        public void contextDestroyed(ServletContextEvent e){
        }
        
        @Override
        public void sessionCreated(HttpSessionEvent e){
            HttpSession session = e.getSession();
            ServletContext servletContext = session.getServletContext();
            AtomicInteger userCounter = (AtomicInteger)servletContext.getAttribute("userCount");
            int userCount = userCounter.incrementAndGet();
            System.out.println("userCount incremented to :"+userCount);
        }
        
        public void sessionDestroyed(HttpSessionEvent e){
            HttpSession session = e.getSession();
            ServletContext servletContext = session.getServletContext();
            AtomicInteger userCounter = (AtomicInteger)servletContext.getAttribute("userCount");
            int userCount = userCounter.decrementAndGet();
            System.out.println("---------- userCount decremented to :"+userCount);
        }
    }

       当第一次访问页面时,控制台打印如下信息:

      

     2.3.2 HttpSessionAttributeListener

        HttpSessionAttributeListener接口有以下方法:

    void attributeAdded(HttpSessionBindingEvent event)
    
    void attributeRemoved(HttpSessionBindingEvent event)
    
    void attributeReplaced(HttpSessionBindingEvent event)

        这三个方法都能获取到一个 HttpSessionBindingEvent的对象,通过这个对象可以获取属性的名称和值。

        由于HttpSessionBinding是 HttpSessionEvent的子类,因此也可以在HttpSessionAttributeListener实现类中获得HttpSession。

       

        2.3.3 HttpSessionActivationListener

        在分布式环境下,会用多个容器进行负载均衡,有可能需要将session保存起来,在容器之间传递。例如当一个容器内存不足时,会把很少用到的对象转存到其他容器上,这时候,容器就会通知所有HttpSessionActivationListener接口实现的类。

        HttpSessionActivationListener接口有两个方法,sessionDidActivate 和 sessionWillPassivate。

    void sessionDidActivate(HttpSessionEvent event)
    
    void sessionWillPassivate(HttpSessionEvent event)

        当HttpSession被转移到其他容器之后,sessionDidActivate就会被调用。容器将一个HttpSession方法传递到方法里,可以从这个对象获得HttpSession。

        

        2.3.4 HttpSessionBindingListener

        当有属性绑定或者解绑到HttpSession上时,HttpSessionBindingListener 监听器就会被调用。

    2.4 ServletRequest Listeners

        2.4.1 ServletRequestListener

        ServletRequestListener监听器会对ServletRequest的创建和销毁事件进行响应。容器通过一个池来存放并重复利用多个ServletRequest,ServletRequest的创建是从容器池里被分配出来的时刻开始,而它的销毁时刻是放回池里的事件。

        ServletRequestListener接口有两个方法:

    void requestInitialized(ServletRequestEvent event)
    
    void requestDestroyed(ServletRequestEvent event)

        这两个方法都会接收到一个ServletRequestEvent对象,可以通过这个对象的getServletRequest方法来获取ServletRequest对象。

        另外,ServletRequestEvent接口也提供了一个getServletContext方法来获取ServletContext。

        2.4.2 ServletRequestAttributeListener

        此接口提供了三个方法:

    void attributeAdded(ServletRequestAttributeEvent event)
    
    void attributeRemoved(ServletRequestAttributeEvent event)
    
    void attributeReplaced(ServletRequestAttributeEvent event)

    三、Filters

    3.1 Filter API

        下面介绍与Filter相关的接口,包含 Filter、FilterConfig、Filter Chain。

        Filter的实现必须继承 javax.servlet.Filter 接口。这个接口包含了 Filter 的三个生命周期:init,doFilter,destroy。

     Servlet容器初始化 Filter 时,会触发 Filter 的 init 方法,一般在应用开始时,而不是在相关资源使用时才初始化,这个方法只调用一次。init 定义如下:

    void init(FilterConfig filterConfig)

        当Servlet 容器每次处理Filter相关资源时,都会调用该Filter实例的doFilter方法。doFilter包含三个参数,定义如下:

    void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

        在doFilter中可以访问ServletRequest、ServletResponse。

        在Filter的doFilter的实现中,最后一行需要调用FilterChain 中的 doFilter 方法。注意Filter的doFilter方法里的第三个参数,就是filterChain的实例:

    filterChain.doFilter(request, response)

        一个资源可能被多个Filter关联到(即Filter 链条),这时Filter.doFilter()的方法将会触发Filter链条中的下一个Filter。只有在Filter链条中最后一个Filter里调用的FilterChain.doFilter(),才会触发处理资源的方法。

     如果在Filter,doFilter()的实现中,没有在结尾处调用FilterChain.doFilter() 的方法,那么该Request请求终止,后面的处理就会中断。

        注意:FilterChain接口中,唯一的方法是doFilter。该方法与Filter中的doFilter方法的定义是不一样的,它只有两个参数。

        Filter接口中,最后一个方法是destroy,定义如下:

    void destroy()

        该方法在Servlet容器要销毁Filter时触发,一般在应用停止的时候进行调用。

    3.2 Filter配置

        当完成Filter的实现后,就要进行配置Filter了。步骤如下:

    •  确认哪些资源需要使用这个Filter拦截处理。
    •  配置Filter的初始化参数值,这些参数可以在Filter的init方法中读取到:
    •  给Filter取一个名称。一般来说,这个名称没什么特别含义。特殊的情况,例如要记录Filter的初始化时间,但这个应用中有许多的Filter,这时它就可以用来识别Filter了。

        FilterConfig接口允许通过它的getServletContext的方法来访问ServletContext:

    ServletContext  getServletContext()

        如果配置了 Filter 的名字,在FilterConfig 的getFilterName 中就可以获取Filter的名字。定义如下:

    java.lang.String getFilterName()

     当然,最重要还是要获取配置给Filter的初始化参数,需要用到FilterConfig中的两个方法,第一个方法是getParameterNames:

    java.util.Enumeration<java.lang.String> getInitParametersNames()

     这个方法返回Filter参数名字的Enumeration对象。如果没有给这个Filter配置任何参数,返回空。

     第二个方法是getParameter:

    java.lang.String getInitParameter(java.lang.String parameterName)

        有两种方法可以配置Filter:一种是通过WebFilter的Annotation来配置Filter,另一种是通过部署描述来注册。下面是WebFilter的参数,所有参数都是可选的。

    属性 描述
    asyncSupported     Filter是否支持异步操作  
    description Filter的描述
    dispatcerTypes Filter所生效范围
    displayName Filter的显示名
    filterName Filter的名称
    initParams Filter的初始化参数
    largeIcon Filter的大图名称
    servletName Filter所生效的Servlet名称  
    smallIcon Filter的小图名称
    urlPatterns Filter所生效的URL路径
    value   Filter所生效的URL路径

     下面举个例子:

    @WebFilter(filterName = "Security Filter", urlPatterns = {"/*"}, 
                    initParams = {
                        @WebInitParam(name = "frequency", value = "1909"),
                        @WebInitParam(name = "resolution", value = "1024")
                    }                     
    }

     如果使用部署描述符,那么对应的配置为:

    <filter>
        <filter-name>Security Filter</filter-name>
        <filter-class>filterClass</filter-class>
        <init-param>
            <param-name>frequency</param-name>
            <param-value>1909</param-value>
        </init-param>
        <init-param>
            <param-name>resolution</param-name>
            <param-value>1024</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>DateCompressionFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    3.3 示例1:日志Filter

    package filter;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Date;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.annotation.WebInitParam;
    import javax.servlet.http.HttpServletRequest;
    
    @WebFilter(filterName = "LoggingFilter", urlPatterns= {"/*"},
            initParams = {
                    @WebInitParam(name = "logFileName", value = "log.txt"),
                    @WebInitParam(name = "prefix", value="URI: ")
            })
    public class LoggingFilter implements Filter{
        
        private PrintWriter logger;
        private String prefix;
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException{
            prefix = filterConfig.getInitParameter("prefix");
            String logFileName = filterConfig.getInitParameter("logFileName");
            String appPath = filterConfig.getServletContext().getRealPath("/");
            
            System.out.println("logFileName:" + logFileName);
            try{
                logger = new PrintWriter(new File(appPath, logFileName));
            }catch(FileNotFoundException e){
                e.printStackTrace();
                throw new ServletException(e.getMessage());
            }
        }
        
        @Override
        public void destroy(){
            System.out.println("destroying filter");
            if(logger != null)
                logger.close();
        }
        
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws IOException, ServletException{
            System.out.println("LoggingFilter.doFilter");
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            logger.println(new Date() + "" + prefix + httpServletRequest.getRequestURL());
            logger.flush();
            filterChain.doFilter(request, response);
        }
    }
    View Code

     通过检查日志文件的内容,就可以验证这个Filter是否运行正常。

    3.4 示例2:图像文件保护Filter

     实现检查HTTP Header的referrer值,只有通过页面访问获取图片资源,直接输入文件路径无效。

    package filter;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    
    @WebFilter(filterName = "ImageProtetorFilter", urlPatterns = {"*.png","*.jpg","*.gif"})
    public class ImageProtectorFilter implements Filter{
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException{
        }
        
        @Override
        public void destroy(){}
        
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws IOException, ServletException{
            System.out.println("ImageProtectorFilter");
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String referrer = httpServletRequest.getHeader("referer");
            System.out.println("referrer:" + referrer);
            if(referrer!=null)
                filterChain.doFilter(request, response);
            else
                throw new ServletException("Image not available");
        }
    }
    View Code

    3.5 示例3:下载计数Filter

    package filter;
    
    import java.io.File;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.Properties;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    
    @WebFilter(filterName = "DownloadCounterFilter", url = {"/*"})
    public class DownLoadCounterFilter implements Filter{
        
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Properties downloadLog;
        File logFile;
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException{
            System.out.println("DownloadCounterFilter");
            String appPath = filterConfig.getServletContext().getRealPath("/");
            logFile = new File(appPath, "downloadLog.txt");
            if(!logFile.exists()){
                try{
                    logFile.createNewFile();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            downloadLog = new Properties();
            try{
                downloadLog.load(new FileReader(logFile));
            }catch(IOException e){
                e.printStackTrace();
            }
        }
        
        @Override 
        public void destroy(){}
        
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException{
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            
            final String uri = httpServletRequest.getRequestURI();
            executorService.execute(new Runnable(){
                @Override
                public void run(){
                    String property = downloadLog.getProperty(uri);
                    if(property == null){
                        downloadLog.setProperty(uri, "1");
                    }else{
                        int count = 0;
                        try{
                            count = Integer.parseInt(property);
                        }catch(NumberFormatException e){
                            
                        }
                        count++;
                        downloadLog.setProperty(uri, Integer.toString(count));
                    }
                    try{
                        downloadLog.store(new FileWriter(logFile),"");
                    }catch(IOException e){}
                }
            });
            filterChain.doFilter(request, response);
        }
    }
    View Code

    3.6 Filter顺序

     如果多个Filter应用于同一个资源,Filter的触发顺序将会很重要,需要使用部署描述符来管理Filter。举个例子,Filter1要在Filter2前被触发:

    <filter>
        <filter-name>Filter1</filter-name>
        <filter-class>the fully-qualified name1</filter-class>
    </filter>
    <filter>
        <filter-name>Filter2</filter-name>
        <filter-class>the fully-qualified name2</filter-class>
    </filter>

        如果需要保持或者改变Filter实现中的状态,就要考虑到线程安全问题。

    <------   天若有情天亦老,人间正道是沧桑   ------>

  • 相关阅读:
    法院
    Spring Cloud常用组件
    PowerShell使用教程
    浅谈3DES加密解密
    SC win consul
    SB-Token-Jwt
    前端MVC Vue2学习总结
    spring-session-data-redis
    SpringBoot WS
    SpringBoot之使用Spring Session集群-redis
  • 原文地址:https://www.cnblogs.com/cardiolith/p/9671211.html
Copyright © 2020-2023  润新知