从零手写实现 tomcat-12-listener 监听器

创作缘由

平时使用 tomcat 等 web 服务器不可谓不多,但是一直一知半解。

于是想着自己实现一个简单版本,学习一下 tomcat 的精髓。

系列教程

从零手写实现 apache Tomcat-01-入门介绍

从零手写实现 apache Tomcat-02-web.xml 入门详细介绍

从零手写实现 tomcat-03-基本的 socket 实现

从零手写实现 tomcat-04-请求和响应的抽象

从零手写实现 tomcat-05-servlet 处理支持

从零手写实现 tomcat-06-servlet bio/thread/nio/netty 池化处理

从零手写实现 tomcat-07-war 如何解析处理三方的 war 包?

从零手写实现 tomcat-08-tomcat 如何与 springboot 集成?

从零手写实现 tomcat-09-servlet 处理类

从零手写实现 tomcat-10-static resource 静态资源文件

从零手写实现 tomcat-11-filter 过滤器

从零手写实现 tomcat-12-listener 监听器

前言

还记得我们最初 web.xml 中的 listener 吗?

<!-- Listener 配置 -->
<listener>
    <listener-class>com.github.houbb.minicat.support.listener.foo.MyServletContextAttrListener</listener-class>
</listener>
<listener>
    <listener-class>com.github.houbb.minicat.support.listener.foo.MyServletContextListener</listener-class>
</listener>
<listener>
    <listener-class>com.github.houbb.minicat.support.listener.foo.MyServletReadListener</listener-class>
</listener>
<listener>
    <listener-class>com.github.houbb.minicat.support.listener.foo.MyServletWriteListener</listener-class>
</listener>
<listener>
    <listener-class>com.github.houbb.minicat.support.listener.foo.MyServletRequestListener</listener-class>
</listener>
<listener>
    <listener-class>com.github.houbb.minicat.support.listener.foo.MyServletRequestAttrListener</listener-class>
</listener>

listener 作用是什么?我们又该如何解析实现呢?

listener 是什么?

想象一下,你在参加一个大型的派对,这个派对有很多的环节,比如音乐响起、有人开始跳舞、蛋糕被推出来等等。

在这个派对中,有一些特别的人,他们对派对中的某些事情特别感兴趣,比如摄影师可能对“有人开始跳舞”这个事件感兴趣,而派对的策划者可能对“蛋糕被推出来”感兴趣。

这些人会在派对中“监听”这些特定的事件,一旦这些事件发生,他们就会采取一些行动,比如摄影师开始拍照,策划者开始唱生日歌。

在Tomcat中,Listener也是类似的。它们是一些特殊的类,对Tomcat应用生命周期中的某些事件感兴趣。

当这些事件发生时,Listener会收到通知,并执行一些操作。

这些操作通常是一些全局性的或初始化的工作,比如:

  1. 记录日志:当应用启动或关闭时,一个日志Listener可能会记录这些事件,以便于跟踪应用的状态。

  2. 设置资源:在应用启动时,一个配置Listener可能会初始化一些资源,比如数据库连接池或线程池。

  3. 清理资源:当应用关闭时,一个清理Listener可能会释放之前创建的资源,确保不会有内存泄漏。

  4. Session管理:一个SessionListener可能会监听用户的会话事件,比如用户登录或退出,然后更新用户的Session状态。

  5. 应用状态监控:一个状态Listener可能会监控应用的运行状态,比如请求数、错误率等,并在发现异常时触发报警。

当然,这个使我们作为客户(参会者)的视角。

那如果是晚会的主办方呢?

tomcat 如何处理 listener 的?

Tomcat就像是一个大型的晚会现场,而Listener就是那些在晚会中扮演着特定角色的“幕后英雄”。

这些角色可能包括音响师、灯光师、摄影师等,他们虽然不直接参与晚会的表演,但他们的工作对于晚会的成功举办至关重要。

  1. 注册监听器:在晚会开始之前,组织者会安排好所有的工作人员,并告诉他们晚会中需要注意的关键时刻。在Tomcat中,这就像是在web.xml配置文件中声明你的应用需要哪些Listener,或者通过注解的方式告诉Tomcat。

  2. 监听事件:晚会进行中,音响师、灯光师和摄影师会密切关注晚会的进展,等待特定的信号或时刻。在Tomcat中,Listener会监听应用生命周期中的各种事件,比如应用启动、会话创建、请求结束等。

  3. 接收通知:当晚会中发生了工作人员需要关注的事情,比如音乐响起、有人开始跳舞等,组织者或现场导演会通过无线电或其他通讯方式通知他们。在Tomcat中,当特定的事件发生时,Tomcat容器会通知所有注册的Listener。

  4. 执行操作:收到通知后,工作人员会立即执行他们的任务。比如,音乐响起时,音响师会调整音量,灯光师会变换灯光效果,摄影师会开始拍摄。在Tomcat中,Listener收到事件通知后,也会执行相应的操作,比如记录日志、初始化资源、清理会话等。

  5. 事件处理完毕:工作人员完成他们的任务后,晚会可以继续顺利进行。在Tomcat中,Listener处理完事件后,请求的处理或应用的运行也会继续进行。

工作流程:

晚会开始(Tomcat应用启动)
    |
    V
+------------+       音乐响起(事件触发)       +------------+
|  音响师    |  --------------------------------->|  调整音量  |
+------------+                                   +------------+
    |
    V
+------------+       有人开始跳舞(事件触发)    +------------+
|  灯光师    |  --------------------------------->|  变换灯光  |
+------------+                                   +------------+
    |
    V
+------------+       有人拍照(事件触发)        +------------+
|  摄影师    |  --------------------------------->|  开始拍照  |
+------------+                                   +------------+
    |
晚会结束(Tomcat应用关闭)
    |
    V
+------------+       晚会结束(事件触发)       +------------+
|  清洁工    |  --------------------------------->|  清理现场  |
+------------+                                   +------------+

tomcat 有哪些 listener 类型?

tomcat 中的类型非常多,这里简单介绍介绍一下:

Tomcat中的Listener类型,可以比作是晚会中的不同工作人员,每种工作人员负责监听和处理不同类型的事件。

以下是一些主要的Listener类型,以及它们在Tomcat中的作用:

  1. ServletContextListener

    • 想象成晚会的策划者,他们关心整个晚会的整体流程。在Tomcat中,这个监听器用于跟踪Web应用的生命周期,比如应用启动和关闭时的事件。
  2. HttpSessionListener

    • 可以看作是负责宾客签到处的工作人员,他们关注宾客的到达和离开。在Tomcat中,这个监听器监听用户的会话创建和销毁事件。
  3. ServletContextAttributeListener

    • 这类似于晚会中的道具管理员,他们对晚会中使用的道具(在这里是应用属性)非常敏感。这个监听器用于监听应用范围内属性的添加、删除和替换事件。
  4. HttpSessionAttributeListener

    • 想象成是晚会中的个人助理,他们关注特定宾客的需求变化。这个监听器用于监听特定用户会话中属性的变化。
  5. ServletRequestListener

    • 可以比作是晚会入口的接待员,他们对每一位宾客的到来和离开都很留心。在Tomcat中,这个监听器监听请求的初始化和销毁事件。
  6. ServletRequestAttributeListener

    • 类似于晚会中的服务生,他们注意到宾客请求的每一个小细节。这个监听器用于监听请求属性的变化。
  7. MessageDestinationListener

    • 想象成是晚会中的信息台,他们监听和传播晚会中的重要消息。在Tomcat中,这个监听器用于监听JMS消息队列或主题的消息。
  8. LifecycleListener

    • 可以看作是晚会的舞台监督,他们对晚会的每一个环节都了如指掌。在Tomcat中,这个监听器用于监听Tomcat容器的生命周期事件。
  9. EngineListenerHostListenerContextListener

    • 这些可以想象成是晚会中的不同管理层级,从整体的晚会经理到负责特定区域的负责人。在Tomcat中,这些监听器分别监听Tomcat引擎、虚拟主机和Web应用的事件。

每种Listener都像是Tomcat晚会中的一个角色,它们在Tomcat应用的不同阶段发挥着重要的作用,帮助开发者在应用的不同生命周期阶段执行特定的任务,从而让整个应用运行得更加顺畅和高效。

自己实现

接下来,到我们自己来秀了~

接口定义

这里就不定义了,直接复用 servlet 的标准 api

接口实现

有各种 listener,我们实现一个简单的作为例子:

package com.github.houbb.minicat.support.listener.foo;

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyServletContextListener implements ServletContextListener {

    private static final Log logger = LogFactory.getLog(MyServletContextListener.class);

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        logger.info("MyServletContextListener contextInitialized");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        logger.info("MyServletContextListener contextDestroyed");
    }

}

应用启动解析

listener 管理类

定义一个 listener 的管理类

package com.github.houbb.minicat.support.listener.manager;

import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.minicat.exception.MiniCatException;
import com.github.houbb.minicat.support.servlet.manager.DefaultServletManager;

import javax.servlet.listener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * listener 管理
 *
 * @since 0.6.0
 * @author 老马啸西风
 */
public class DefaultlistenerManager implements IlistenerManager {

    //... 省略基础属性

    @Override
    public void register(String url, listener listener) {
        logger.info("[MiniCat] register listener, url={}, listener={}", url, listener.getClass().getName());

        listenerMap.put(url, listener);
    }

    @Override
    public listener getlistener(String url) {
        return listenerMap.get(url);
    }

    @Override
    public List<listener> getMatchlisteners(String url) {
        List<listener> resultList = new ArrayList<>();

        for(Map.Entry<String, listener> entry : listenerMap.entrySet()) {
            String urlPattern = entry.getKey();
            if(url.matches(urlPattern)) {
                resultList.add(entry.getValue());
            }
        }

        return resultList;
    }

}

register 的时机

以本地的 web.xml 解析为例

/**
 * 处理 web 文件
 */
protected void processWebXml() {
    try {
        SAXReader reader = new SAXReader();
        Document document = reader.read(webXmlFile);
        Element root = document.getRootElement();

        // ...

        //2. 处理 listener
        final IlistenerManager listenerManager = this.miniCatContextConfig.getlistenerManager();
        processWeblistener(root, listenerManager);

        // ...
    } catch (Exception e) {
        throw new MiniCatException(e);
    }
}

解析对应的 web.xml 标签内容,注册对应信息:

protected void handlelistenerConfigMap(Map<String, String> listenerClassNameMap, Map<String, String> urlPatternMap, final IlistenerManager listenerManager) {
    try {
        for (Map.Entry<String, String> urlPatternEntry : urlPatternMap.entrySet()) {
            String listenerName = urlPatternEntry.getKey();
            String urlPattern = urlPatternEntry.getValue();
            String className = listenerClassNameMap.get(listenerName);
            if (StringUtil.isEmpty(className)) {
                throw new MiniCatException("className not found for listenerName: " + listenerName);
            }
            Class servletClazz = Class.forName(className);
            listener httpServlet = (listener) servletClazz.newInstance();
            // 构建
            String fullUrlPattern = buildFullUrlPattern(urlPattern);
            listenerManager.register(fullUrlPattern, httpServlet);
        }
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        throw new MiniCatException(e);
    }
}

这样就可以后面调用了。

调用的时机

我们什么时候调用呢?

当然是一个请求地址过来了,看是否有匹配的 listener,然后进行处理。

@Override
public void dispatch(final IMiniCatRequest request,
                     final IMiniCatResponse response,
                     final MiniCatContextConfig config) {
    final IServletManager servletManager = config.getServletManager();
    // 判断文件是否存在
    String requestUrl = request.getUrl();
    //before
    List<listener> listenerList = config.getlistenerManager().getMatchlisteners(requestUrl);
    // 获取请求分发
    final IRequestDispatcher requestDispatcher = getRequestDispatcher(requestUrl);
    // 请求前
    listenerList.forEach(listener -> {
        try {
            listener.dolistener(request, response, null);
        } catch (IOException | ServletException e) {
            throw new RuntimeException(e);
        }
    });
    // 正式分发
    requestDispatcher.dispatch(request, response, config);
}

这样,一个基础的 listener 功能就实现了。

开源地址

 /\_/\  
( o.o ) 
 > ^ <

mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)

开源地址:https://github.com/houbb/minicat

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

推荐阅读更多精彩内容