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

2021-06-22 17:04

阅读:500

标签:3.5   isp   current   exce   bsp   parameter   因此   rac   syn   

监听器 与 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 classlistener-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 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 Filterfilter-name>
    filter-class>filterClassfilter-class>
    init-param>
        param-name>frequencyparam-name>
        param-value>1909param-value>
    init-param>
    init-param>
        param-name>resolutionparam-name>
        param-value>1024param-value>
    init-param>
filter>
filter-mapping>
    filter-name>DateCompressionFilterfilter-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前被触发:

Filter1class>the fully-qualified name1class>
Filter2class>the fully-qualified name2class>

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

 

 

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

标签:3.5   isp   current   exce   bsp   parameter   因此   rac   syn   

原文地址:https://www.cnblogs.com/cardiolith/p/9671211.html


评论


亲,登录后才可以留言!