Filter与Listener简介及源码分析

前言

Filter也是一种服务端小程序,它的功能是在请求到达对应的Servlet实例以及从Servlet返回的响应到达客户端的过程中,对RequestResponse进行相应的处理,比较常见的应用如权限验证等。和Servlet一样,Filter只是一个实现了特定接口等普通Java对象而已。Filter采用了责任链的设计模式,关于责任链设计模式可以浏览

责任链设计模式

Listener也是一种特殊的服务端小程序,它的功能是监听一些事件的开始与结束,以及某些类的属性变化。下面首先介绍Filter

Filter

一个类实现了Filter接口就可以成为一个Filter,以下是Filter接口的定义。

  • Filter接口
public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

其中init()destroy()Filter的生命周期方法,分别在创建和销毁的时候被调用,这一点与 Servlet类似。其中doFilter()操作则是正在在执行过滤操作,让我现在web应用里使用Filter,看看能起到什么效果。

使用Filter

  • 使用idea构建的web项目结构
    项目结构

首先让我们定义一个需要被访问的HelloServlet并在index.jsp中给它提供一个访问入口。

  • HelloServlet
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("这里是HelloServlet的doGet()方法");
    }
}

  • index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <a href="HelloServlet">点击访问这里访问HelloServlet</a>
  </body>
</html>

配置web.xml,把HelloServlet交给tomcat管理。

  • web.xml
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/HelloServlet</url-pattern>
    </servlet-mapping>

再没有配置Filter前,让我们先访问一下HelloServlet,观察一下控制台的输出。

这里是HelloServlet的doGet()方法

可以看到可以成功调用到HelloServletdoGet()方法。
现在,再让我们实现一个Filter

  • MyFilter
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("这里是MyFilter的doFilter方法");
    }
}

Servlet一样我们也需要把MyFilter注册到tomcat所管理的容器中去

  • web.xml
    <filter>
        <filter-name>myFilter</filter-name>
        <filter-class>MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>myFilter</filter-name>
        <url-pattern>/HelloServlet</url-pattern>
    </filter-mapping>

配置方法和Servlet差不多,<url-pattern>指明了当访问该url地址时,该FilterdoFilter()会被调用进行过滤操作。配置好了之后再让我们访问HelloServlet

  • 配置MyFilter后控制台的输出结果。
Connected to server
[2020-04-18 01:57:17,405] Artifact springweb:war exploded: Artifact is being deployed, please wait...
[2020-04-18 01:57:17,671] Artifact springweb:war exploded: Artifact is deployed successfully
[2020-04-18 01:57:17,671] Artifact springweb:war exploded: Deploy took 266 milliseconds
这里是MyFilter的doFilter方法

我们可以看到当执行完MyFilterdoFilter()方法后,整个过程就结束了,Request并没有继续传递给Servlet,也就是说请求被MyFilter拦截了。原因很简单,我们在doFilter()中只是简单的打印了一行字符,然后就直接return了,当然不会继续执行了。那如何才能将这个请求放行呢?事实我们要注意到同时传递进来的还有一个FilterChain实例,这个FilterChain并不是我们创建的,而是tomcat维护的一个Filter链,我们可以把其看作是一个队列,当我们MyFilter的执行逻辑处理完以后,我们应当将该请求递交给这个Chain中的下一个Filter处理,如果没有下一个Filter了就交给这个Request所想要访问的Servlet处理。

  • 调用chain.doFilter()将请求传递
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("这里是MyFilter的doFilter方法");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

然后让我们再来访问一次HelloServlet

Connected to server
[2020-04-18 02:09:09,024] Artifact springweb:war exploded: Artifact is being deployed, please wait...
[2020-04-18 02:09:09,254] Artifact springweb:war exploded: Artifact is deployed successfully
[2020-04-18 02:09:09,254] Artifact springweb:war exploded: Deploy took 230 milliseconds
这里是MyFilter的doFilter方法
这里是HelloServlet的doGet()方法

可以看到请求被成功放行了。
之前我们提到过FilterRequestResponse都可以进行处理,这里似乎只对Request进行了处理,那么在调用 ServletService方法后返回的Response呢?让我们在filterCahin.doFilter(...)下面加一行打印信息。

  • 处理response
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Before:这里是MyFilter的doFilter方法");
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("After:这里是MyFilter的doFilter方法");
    }
  • 控制台输出
Before:这里是MyFilter的doFilter方法
这里是HelloServlet的doGet()方法
After:这里是MyFilter的doFilter方法

看到这个输出,不难想到如下的执行流程。


执行流程

在执行filterChaindoFilter()方法放行之前,对Request进行处理,当从filterChaindoFilter()方法中返回时,再对Response进行处理。这里只有一个Filter,还不够有说服力,放我们再追加一个Filter,来验证这个调用流程。

  • MyFiliter2
public class MyFilter2 implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Before:这里是MyFilter2的doFilter方法");
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("After:这里是MyFilter2的doFilter方法");
    }
}

MyFilter2也配置进tomcat容器中去

  • web.xml
    <filter>
        <filter-name>myFilter2</filter-name>
        <filter-class>MyFilter2</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>myFilter2</filter-name>
        <url-pattern>/HelloServlet</url-pattern>
    </filter-mapping>

再次访问HelloServlet,控制台的输出信息如下

Before:这里是MyFilter的doFilter方法
Before:这里是MyFilter2的doFilter方法
这里是HelloServlet的doGet()方法
After:这里是MyFilter2的doFilter方法
After:这里是MyFilter的doFilter方法

也就是下图的执行流程


执行流程

方法调用的次序如下


调用顺序.png

很明显这是一个递归执行的过程。

实现Filter

结合上述分析,和javax.servlet下的FilterFilterChain接口的定义,我们可以来模拟一下Filter的执行流程。

  • 定义Filter接口
interface Filter {
    void doFilter(Request request, Response response, FilterChain filterChain);
}
  • 定义演示用的RequestResponse
class Request {
    String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

class Response {
    String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
  • 实现一些Filter实例
class AFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        System.out.println("AFilter处理"+request.getMsg());
        filterChain.doFilter(request, response);
        System.out.println("AFilter处理"+response.getMsg());
    }
}
class BFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        System.out.println("BFilter处理"+request.getMsg());
        filterChain.doFilter(request, response);
        System.out.println("BFilter处理"+response.getMsg());
    }
}
class CFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        System.out.println("CFilter处理"+request.getMsg());
        filterChain.doFilter(request, response);
        System.out.println("CFilter处理"+response.getMsg());
    }
}
class DFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain filterChain) {
        System.out.println("DFilter处理"+request.getMsg());
        filterChain.doFilter(request, response);
        System.out.println("DFilter处理"+response.getMsg());
    }
}
  • 实现FilterChain
class FilterChain {
    private List<Filter> filters;
    private int pos;
    public FilterChain(){
        this.filters = new ArrayList<>();
        this.pos = 0;
    }
    public FilterChain add(Filter filter) {
        filters.add(filter);
        return this;
    }
    public void doFilter(Request request, Response response) {

        // 通过了所有的`Filter`
        if (pos == filters.size()) {
            System.out.println("通过了所有的Filter, 访问到了`Servlet的特定方法`");
            return;
        }

        Filter filter = filters.get(pos);
        pos++; //进入下一个`Filter`
        filter.doFilter(request, response, this);
    }
}

FilterChainFilter执行中最关键一环,FilterChaintomcat生成,这个过程可以简单的看作是扫描web.xml文件,生成多个定义好的Filter实例,然后再添加到一个FilterChain中,FilterChain中设置了一个变量pos,用来指明当前到达第几个Filter,若pos==filter.size(),则说明通过了最后一个过滤器,接下去去访问Servlet即可。整个过程同上面一样,是一个递归调用的过程。
让我们对整个环节进行测试。

  • test

public class Test {
    public static void main(String[] args) {
        Request request = new Request();
        Response response = new Response();
        request.setMsg("请求1");
        response.setMsg("响应1");


        Filter afilier = new AFilter();
        Filter bfilier = new BFilter();
        Filter cfilier = new CFilter();
        Filter dfilier = new DFilter();

        FilterChain filterChain = new FilterChain();
        filterChain.add(afilier)
                .add(bfilier)
                .add(cfilier)
                .add(dfilier);

        filterChain.doFilter(request,response);
    }
}

  • 执行结果
AFilter处理请求1
BFilter处理请求1
CFilter处理请求1
DFilter处理请求1
通过了所有的Filter, 访问到了`Servlet的特定方法`
DFilter处理响应1
CFilter处理响应1
BFilter处理响应1
AFilter处理响应1

Listener

Listener通常监听ServletContext(由tomcat管理的Servlet容器)、ServletRequestSession三大对象。根据监听的内容又可以分为生命周期(创建、销毁)、属性变更两大部分。

监听生命周期

想要监听ServletContextServletRequestSession只要实现对应的ServletContextListenerServletRequestListenerHttpSessionListener接口就可以了。我们创建一个MyListener类,一次性实现这3个接口进行演示。

  • MyListener
public class MyListener implements ServletRequestListener, ServletContextListener, HttpSessionListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("MyListener:" + "ServletContext被初始化" );
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("MyListener:" + "ServletContext被销毁" );
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("MyListener:" + "ServletRequest被初销毁" );
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("MyListener:" + "ServletRequest被初始化" );
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("MyListener:" + "httpsession被创建" );
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("MyListener:" + "httpsession被销毁" );
    }
}

同样,我们需要把MyListener也注册进tomcatServlet容器里。

  • web.xml
    <listener>
        <listener-class>MyListener</listener-class>
    </listener>

现在访问下主页,可以观察到控制台有如下打印信息

MyListener:ServletContext被初始化
MyListener:ServletRequest被初始化
MyListener:httpsession被创建
MyListener:ServletRequest被初销毁

可见ServletContext这个象征着Servlet容器的实例再tomcat启动后就立即被实例化,当我们访问网站首页时,会先创建一个ServletRequest对象,请求到达后会自动创建一个session,然后销毁这个ServletRequest。那我们如何销毁这个创建好的session呢?
创建一个用于销毁session的页面,并在主页上设立一个指向该页面的超链接。

  • destorysession.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%
        session.invalidate();
    %>
</body>
</html>
  • index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <a href="destroysession.jsp">点击销毁session</a>
  </body>
</html>

现在首先访问index.jsp,再去访问destorysession.jsp,后台打印信息如下。

MyListener:ServletRequest被初始化
MyListener:httpsession被创建
MyListener:ServletRequest被初销毁
MyListener:ServletRequest被初始化
MyListener:httpsession被销毁
MyListener:ServletRequest被初销毁

即使不去访问destorysession.jsp, session也会在一段时间后自动销毁。

属性监听

我们还可以设置监听器来监听ServletContextServletRequestSession对象中的属性的增加、删除、替换的操作。只要实现对应的ServletContextAttributeListenerServletRequestAttributeListenerHttpSessionAttributeListener接口即可,下面实现AttriListener来测试所有属性监听相关的方法。

  • AttriListener
public class AttrListener implements ServletContextAttributeListener, ServletRequestAttributeListener, HttpSessionAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        // 新添加的属性名
        String name = scae.getName();
        // 和该属性名关联的值
        Object val = scae.getServletContext().getAttribute(name);
        System.out.println("ServletContext AttributeAdded:" + name + " " + val);
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        //被删除的属性名
        String name = scae.getName();
        System.out.println("ServletContext AttributeRemoved:" + name);
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        // 被替换的属性名
        String name = scae.getName();
        // 该属性名的新值
        Object val = scae.getServletContext().getAttribute(name);
        System.out.println("ServletContext AttributeReplaced:" + name + " " + val);
    }

    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        // 和该属性名关联的值
        Object val = srae.getServletRequest().getAttribute(name);
        System.out.println("ServletRequest AttributeAdded:" + name + " " + val);

    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        System.out.println("ServletRequest AttributeRemoved:" + name);
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        // 该属性名的新值
        Object val = srae.getServletRequest().getAttribute(name);
        System.out.println("ServletRequest AttributeReplaced:" + name + " " + val);
    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        String name = se.getName();
        // 和该属性名关联的值
        Object val = se.getSession().getAttribute(name);
        System.out.println("HttpSession AttributeAdded:" + name + " " + val);

    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        String name = se.getName();
        System.out.println("HttpSession AttributeRemoved:" + name);
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        String name = se.getName();
        // 该属性名的新值
        Object val = se.getSession().getAttribute(name);
        System.out.println("HttpSession AttributeReplaced:" + name + " " + val);
    }
}

每个方法的作用和使用方法见其上注释。
设计一个jsp页面来验证监听器是否起作用。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%
        application.setAttribute("name", "tom");
        application.setAttribute("name", "ben");
        application.removeAttribute("name");
        request.setAttribute("age", "22");
        request.setAttribute("age", "23");
        request.removeAttribute("age");
        session.setAttribute("city", "beijing");
        session.setAttribute("city", "shanghai");
        session.removeAttribute("city");
    %>

</body>
</html>

访问该页面查看控制台打印结果如下。

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

推荐阅读更多精彩内容