美文网首页
从零手写实现 tomcat-12-listener 监听器

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

作者: 老马啸西风2020 | 来源:发表于2024-05-11 11:12 被阅读0次

    创作缘由

    平时使用 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

    相关文章

      网友评论

          本文标题:从零手写实现 tomcat-12-listener 监听器

          本文链接:https://www.haomeiwen.com/subject/bbcyfjtx.html