Filter与Listener

一、Filter
  • 过滤器简介
  • 过滤器也称之为拦截器,是Servlet 2.3规范新增的功能。
    Filter是Servlet技术中非常实用的技术,Web开发人员通过Filter技术,可以在用户访问某个Web资源(如:JSP、Servlet、HTML、图片、CSS等)之前,对访问的请求和响应进行拦截,从而实现一些特殊功能。
    例如,验证用户访问权限、记录用户操作、对请求进行重新编码、压缩响应信息等。
  • 过滤器所处的位置:


  • 过滤器的运行原理
  • 预处理:当用户的请求到达指定的网页之前,可以借助过滤器来改变这些请求的内容。
  • 后处理:当执行结果要响应到用户之前,可经过过滤器修改响应输出的内容。
  • 一个过滤器的运行过程可以分解为如下几个步骤:
    1.Web容器判断接收的请求资源是否有与之匹配的过滤器,如果有,容器将请求交给相应过滤器进行处理;
    2.在过滤器预处理过程中,可以改变请求的内容,或者重新设置请求的报头信息,然后将请求发给目标资源;
    3.目标资源对请求进行处理后作出响应;
    4.容器将响应转发回过滤器;
    5.在过滤器后处理过程中,可以根据需求对响应的内容进行修改;
    6.Web容器将响应发送回客户端。
  • 过滤器链

在一个Web应用中,也可以部署多个过滤器,这些过滤器组成了一个过滤器链。
过滤器链中的每个过滤器负责特定的操作和任务,客户端的请求可以在这些过滤器之间进行传递,直到达到目标资源。

例如,一个由两个Filter所组成的过滤器链的过滤过程:


在客户端的请求响应过程中,并不需要经过所有的过滤器链,而是根据过滤器链中每个过滤器的过滤条件来匹配需要过滤的资源。

  • 过滤器核心接口
  • 与开发Servlet需要实现Servlet接口类似,开发Filter要实现javax.servlet.Filter接口,并提供一个公共的不带参数的构造方法。
  • Filter接口的方法及说明:
    1.init(FilterConfig config):容器在过滤器实例化后调用此方法对过滤器进行初始化,同时向其传递FilterConfig对象,用于获得和Servlet相关的ServletContext对象。
    2.doFilter(ServletRequest request,ServletResponse response,FilterChain chain):过滤器的功能实现方法。当用户请求经过时,容器调用此方法对请求和响应进行功能处理。该方法由容器传入三个参数对象,分别用于获取请求对象、响应对象和FilterChain对象,请求和响应对象类型分别为ServletRequest和ServletResponse,并不依赖于具体的协议,FilterChian对象的doFilter(request,response)方法负责将请求传递给下一个过滤器或目标资源。
    3.destroy():该方法在过滤器生命周期结束前由Web容器调用,可用于使用资源的释放
  • FilterConfig接口
    javax.servlet.FilterConfig接口由容器实现,容器将其实例作为参数传入过滤器(Filter)对象的初始化方法init()中,来获取过滤器的初始化参数和Servlet的相关信息。
  • FilterConfig接口的主要方法及作用
    1.getFilterName():获取配置信息中指定的过滤器的名字
    2.getInitParameter(String name):获取配置信息中指定的名为name的过滤器初始化参数值
    3.getInitParameterNames():获取过滤器的所有初始化参数的名字的枚举集合
    4.getServletContext():获取Servlet上下文对象
  • FilterChain接口
    javax.servlet.FilterChain接口由容器实现,容器将其实例作为参数传入过滤器对象的doFilter()方法中。过滤器对象使用FilterChain对象调用过滤器链中的下一个过滤器,如果该过滤器是链中最后一个过滤器,那么将调用目标资源。
  • FilterChain接口主要方法及作用
    doFilter(ServletRequest request,ServletResponse response):
    该方法将使过滤器链中的下一个过滤器被调用,如果调用该方法的过滤器是链中最后一个过滤器,那么目标资源被调用
  • 过滤器的生命周期
  • 加载和实例化
    Web容器启动时,会根据@WebFilter属性filterName所定义的类名的大小写拼写顺序,或者web.xml中声明的Filter顺序依次实例化Filter。
  • 初始化
    Web容器调用init(FilterConfig config)方法来初始化过滤器。容器在调用该方法时,向过滤器传递FilterConfig对象。实例化和初始化的操作只会在容器启动时执行,并且只会执行一次。
  • doFilter()方法的执行
    当客户端请求目标资源的时候,容器会筛选出符合过滤器映射条件的Filter,并按照@WebFilter属性filterName所定义的类名的大小写拼写顺序,或者web.xml中声明的filter-mapping的顺序依次调用这些过滤器的doFilter()方法。在这个链式调用过程中,可以调用FilterChain对象的doFilter方法将请求传给下一个过滤器(或目标资源),也可以直接向客户端返回响应信息,或者利用请求转发或重定向将请求转向到其它资源。需要注意的是,这个方法的请求和响应参数的类型是ServletRequest和ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。
  • 销毁
    Web容器调用destroy()方法指示过滤器的生命周期结束。在这个方法中,可以释放过滤器使用的资源。
  • 过滤器声明配置

在Servlet3.0以上版本中,既可以使用@WebFilter形式的Annotation对Filter进行声明配置,也可以在web.xml文件中进行配置。

@WebFilter中必需的属性

  • urlPatterns/value:用于指定该Filter所拦截的URL, 两个属性相同但不能同时使用。
    URL匹配模式可以是路径匹配,也可以是扩展名匹配。但不能是路径匹配和扩展名匹配的混合。

过滤器是按照类名的字母顺序A-Z来排序的

  • 过滤器应用
  • 设置请求编码
    只有在最初使用请求对象的程序前进行编码设置,才会对后续使用程序起作用,因此,该过滤器在执行顺序上应该保证早于其它过滤器的执行。
public class SetCharacterEncodingFilter implements Filter {

    private String encoding;

    public SetCharacterEncodingFilter() {

    }

    public void init(FilterConfig fConfig) throws ServletException {
        // 获取过滤器配置的初始参数
        this.encoding = fConfig.getInitParameter("encoding");
    }

    public void destroy() {
        this.encoding = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        if (encoding == null)
            encoding = "UTF-8";
        System.out.println("encoding filter....");
        // 设置请求的编码
        request.setCharacterEncoding(encoding);
        // 过滤传递
        chain.doFilter(request, response);
    }

}
 <filter>
    <filter-name>SetCharacterEncodingFilter</filter-name>
    <filter-class>com.neuedu.filter.SetCharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>SetCharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  • 控制用户访问权限
@WebFilter(urlPatterns = { "/*" }, initParams = {
        @WebInitParam(name = "loginPage", value = "login.jsp"),
        @WebInitParam(name = "loginServlet", value = "LoginServlet") })
public class SessionCheckFilter implements Filter {
    // 用于获取初始化参数
    private FilterConfig config;
    public void init(FilterConfig fConfig) throws ServletException {
        this.config = fConfig;
    }

    public void destroy() {
        this.config = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 获取初始化参数
        String loginPage = config.getInitParameter("loginPage");
        String loginServlet = config.getInitParameter("loginServlet");
        HttpServletRequest req = (HttpServletRequest) request;
        // 获取会话对象
        HttpSession session = req.getSession();
        // 获取请求资源路径(不包含请求参数)
        String requestPath = req.getServletPath();
        System.out.println("session filter path.....:"+requestPath);
        if (session.getAttribute("username") != null
                || requestPath.endsWith(loginPage) || requestPath.endsWith(loginServlet)) {
            // 如果用户会话域属性user存在,并且请求资源为登录页面和登录处理的Servlet,则“放行”请求
            chain.doFilter(request, response);
        } else {
            // 对请求进行拦截,返回登录页面
            request.setAttribute("tip", "您还未登录,请先登录!");
            request.getRequestDispatcher(loginPage).forward(request, response);
        }
    }
}
二、Listener
  • 监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。
  • 在Web容器运行过程中,有很多关键点事件,比如Web应用被启动、用户会话开始、用户会话结束、用户请求到达等,Servlet API提供了大量监听器接口来帮助开发者实现对Web应用内特定事件进行监听,从而当Web应用内这些特定事件发生时,回调监听器内的事件监听方法来实现一些特殊功能,监听器的作用是监听Web容器的有效期事件,因此它是由容器管理的。


  • 用于监听的事件源分别为 ServletContext, HttpSession 和 ServletRequest 这三个域对象。
  • 监听器划分为三种类型:
    监听三个域对象创建和销毁的事件监听器
    监听域对象中属性的增加和删除的事件监听器
    监听绑定到 HttpSession 域中的某个对象的状态的事件监听器。
  • Servlet上下文监听器
  • ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。
  • 监听器的实现通过两个步骤完成:
    步骤一:定义监听器实现类,实现监听器接口的所有方法;
    步骤二:通过Annotation或在web.xml文件中声明Listener。
@WebListener
public class MyServletContextListener implements ServletContextListener {

   public void contextDestroyed(ServletContextEvent sce)  { 
        System.out.println(sce.getServletContext().getServletContextName()+"应用停止..");
   }

   public void contextInitialized(ServletContextEvent sce)  { 
      
      System.out.println(sce.getServletContext().getServletContextName()+"应用启动..");
   }  
}
<display-name>ch06-listener</display-name>
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mybatis.xml</param-value>
  </context-param>
  <listener>
      <listener-class>com.neuedu.listener.MyServletContextListener</listener->class>
  </listener>
  • HttpSession监听器

HttpSessionListener接口用于监听HttpSession的创建和销毁

/**
 * 统计在线用户数量
 * 
 */
@WebListener
public class OnlineUserNumberListener implements HttpSessionListener {

    private int num; // 统计在线人数
    /**
     * 会话创建时的监听方法
     */
    public void sessionCreated(HttpSessionEvent se) {
        // 会话创建时,人数加1
        num++;
        ServletContext context = se.getSession().getServletContext();
        // 将在线人数存入应用域属性
        context.setAttribute("onlineUserNum", num);
    }
    /**
     * 会话销毁时的监听方法
     */
    public void sessionDestroyed(HttpSessionEvent se) {
        // 会话销毁时,人数减1
        num--;
        ServletContext context = se.getSession().getServletContext();
        // 将在线人数存入应用域属性
        context.setAttribute("onlineUserNum", num);
    }
}
  • HttpRequest监听器

ServletRequestListener 接口用于监听ServletRequest 对象的创建和销毁。

  • 监听三个域对象属性变化

Servlet规范定义了监听 ServletContext, HttpSession, HttpServletRequest 这三个对象中的属性变更信息事件的监听器。
这三个监听器接口分别是:ServletContextAttributeListener,HttpSessionAttributeListener ,
ServletRequestAttributeListener。
这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeReplaced(HttpSessionBindingEvent hsbe)
public void attributeRmoved(ServletRequestAttributeEvent srae)

/**
 * 同时实现ServletRequestAttributeListener和ServletRequestListener接口的监听器
 * 
 */
//@WebListener
public class RequestOperatorListener implements ServletRequestListener,
        ServletRequestAttributeListener {
    /**
     * 请求结束时触发该方法
     */
    public void requestDestroyed(ServletRequestEvent sre) {
        // 获取HttpServletRequest对象
        HttpServletRequest request = (HttpServletRequest) sre
                .getServletRequest();
        String requestURI = request.getRequestURI();
        System.out.println(requestURI + "请求结束。");
    }

    /**
     * 请求对象被初始化时触发该方法
     */
    public void requestInitialized(ServletRequestEvent sre) {
        // 获取HttpServletRequest对象
        HttpServletRequest request = (HttpServletRequest) sre
                .getServletRequest();
        // 获取请求用户IP地址
        String userIP = request.getRemoteAddr();
        // 获取请求资源地址
        String requestURI = request.getRequestURI();
        // 获取已登录请求用户名
        String username = (String) request.getSession()
                .getAttribute("username");
        // 若未登录,设请求用户名为“游客”
        username = (username == null) ? "游客" : username;
        System.out.println(requestURI + "请求被初始化。");
        StringBuffer sb = new StringBuffer();
        sb.append("本次请求访问信息:");
        sb.append("用户名称:");
        sb.append(username);
        sb.append(";用户IP:");
        sb.append(userIP);
        sb.append(";请求地址:");
        sb.append(requestURI);
        System.out.println(sb.toString());
    }

    /**
     * 请求域属性被移除时触发该方法
     */
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        // 获取属性的名称和值
        String attrName = srae.getName();
        Object attValue = srae.getValue();
        StringBuffer sb = new StringBuffer();
        sb.append("删除的请求域属性名为:");
        sb.append(attrName);
        sb.append(",值为:");
        sb.append(attValue);
        System.out.println(sb.toString());
    }

    /**
     * 添加请求域属性时触发该方法
     */
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        // 获取
        String attrName = srae.getName();
        Object attValue = srae.getValue();
        StringBuffer sb = new StringBuffer();
        sb.append("添加的请求域属性名为:");
        sb.append(attrName);
        sb.append(",值为:");
        sb.append(attValue);
        System.out.println(sb.toString());
    }

    /**
     * 请求域属性值被替换时触发该方法
     */
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        // 获取
        String attrName = srae.getName();
        Object attValue = srae.getValue();
        StringBuffer sb = new StringBuffer();
        sb.append("被替换的请求域属性名为:");
        sb.append(attrName);
        sb.append(",值为:");
        sb.append(attValue);
        System.out.println(sb.toString());
    }

}
<title>与请求相关的监听器测试页面</title>
</head>
<body>
    <%
        //开启Tomcat异步Servlet支持  
        //req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);  
        request.setAttribute("temp", "aaa");
        request.setAttribute("temp", "bbbbb");
        request.removeAttribute("temp");
    %>
</body>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Based on Java™ Servlet Specification v3.1 [TOC] Servlet和S...
    0x70e8阅读 1,312评论 0 7
  • 监听器(listener) 监听器简介 :监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个...
    奋斗的老王阅读 2,507评论 0 53
  • 经典的Java面试题(第二部分),这部分主要是与Java Web和Web Service相关的面试题。 96、阐述...
    nnngu阅读 691评论 0 8
  • Servlet接口 Servlet规范的核心接口即是Servlet接口,它是所有Servlet类必须实现的接口,在...
    java日记阅读 1,819评论 0 2
  • IOC 控制反转容器控制程序对象之间的关系,而不是传统实现中,有程序代码之间控制,又名依赖注入。All 类的创建,...
    irckwk1阅读 940评论 0 0