目录

    Phoenix监控平台技术解析(十四):SpringMVC Integrator 适配——Listener 机制集成方案

    上一篇我们拆解了 Spring Boot Starter 的自动配置机制——通过 @EnableMonitoring 注解、DeferredImportSelectorspring.factoriesImportAware 等一套精巧的组合拳,让 Spring Boot 应用只需一个注解就能接入 Phoenix 监控。但现实世界里,并非所有 Java Web 项目都跑在 Spring Boot 上。那些基于传统 Spring MVC + web.xml 的老项目,该如何集成 Phoenix?这一篇,我们来看 Phoenix 的另一条集成路径——phoenix-client-spring-mvc-integrator 模块,以及它背后的 Servlet Listener 机制。


    一、为什么需要一条“备用通道”?

    Spring Boot 的 Starter 机制固然优雅,但它有一个隐含的前提——你的项目必须是 Spring Boot 应用。

    在很多企业级系统中,尤其是维护了多年的老项目,技术栈往往是这样的:

    • Spring MVC 4.x / 5.x(不是 Spring Boot)
    • 部署在外部的 Tomcat / Jetty / WebLogic
    • 配置全靠 web.xml
    • 没有 @SpringBootApplication,没有 spring.factories,没有自动配置

    对这类项目来说,Spring Boot Starter 那一套完全用不上。但监控需求是一样的——它们同样需要心跳上报、JVM 采集、线程池监控、服务器信息采集。

    Phoenix 的解法是:提供一个独立的轻量模块 phoenix-client-spring-mvc-integrator,利用 Servlet 规范中最古老、最通用的扩展机制——ServletContextListener——来完成监控的初始化。

    不依赖 Spring Boot,不依赖自动配置,不依赖注解扫描——只要你的应用跑在 Servlet 容器里,就能接入。


    二、Servlet Listener:比 Spring 还早的“生命周期钩子”

    在聊 Phoenix 的实现之前,先花一点篇幅回顾 Servlet 规范中的 Listener 机制。这是理解整个集成方案的基础。

    2.1 Servlet 容器的启动流程

    当你把一个 WAR 包丢进 Tomcat 的 webapps 目录(或者在 IDE 中点击“Run”),Servlet 容器的启动过程大致如下:

    1. 解析 web.xml(或注解配置)
    2. 创建 ServletContext 对象
    3. 加载 <context-param> 参数到 ServletContext
    4. 实例化并调用所有 ServletContextListener.contextInitialized()  ← 这里
    5. 初始化 Filter
    6. 初始化 Servlet(如 DispatcherServlet)
    7. 开始接受 HTTP 请求
    

    注意第 4 步——ServletContextListenercontextInitialized() 方法在 Filter 和 Servlet 初始化之前就会被调用。 这意味着,在这个时间点执行的代码,可以保证在应用处理第一个 HTTP 请求之前就完成准备工作。

    对于监控客户端来说,这个时机恰到好处:我们需要在应用“正式营业”之前,就把心跳、采集、数据交换器等一切基础设施启动起来。

    2.2 ServletContextListener 接口

    ServletContextListener 是 Servlet 规范(从 Servlet 2.3 开始)定义的接口,只有两个方法:

    public interface ServletContextListener extends EventListener {
        // Servlet 上下文初始化完成时调用
        void contextInitialized(ServletContextEvent sce);
        // Servlet 上下文即将销毁时调用
        void contextDestroyed(ServletContextEvent sce);
    }
    

    它的设计理念和 Spring 的 ApplicationListener<ContextRefreshedEvent> 如出一辙——都是“生命周期钩子”。区别在于层级不同:ServletContextListener 工作在 Servlet 容器层面,比 Spring 容器更底层、更早触发。

    如果说 Spring Boot 的 @EnableMonitoring 是在“Spring 世界”里找了一个入口,那 ServletContextListener 就是在“Servlet 世界”里找了一个入口。两条路,殊途同归。

    2.3 context-param:Servlet 级别的“配置中心”

    web.xml 中的 <context-param> 元素用于定义全局上下文参数,这些参数在 Servlet 容器启动时被加载到 ServletContext 对象中,所有 Servlet、Filter、Listener 都可以通过 servletContext.getInitParameter("paramName") 读取。

    <context-param>
        <param-name>configLocation</param-name>
        <param-value>classpath:conf/monitoring.properties</param-value>
    </context-param>
    

    你可以把它理解为 Servlet 时代的“配置文件”——虽然没有 Spring Boot 的 application.yml 那么灵活,但对于传递一个配置文件路径来说绰绰有余。


    三、phoenix-client-spring-mvc-integrator 模块结构

    先看一眼这个模块的整体面貌:

    3.1 包结构

    phoenix-client-spring-mvc-integrator/
    ├── src/main/java/com/gitee/pifeng/monitoring/integrator/listener/
    │   ├── MonitoringPlugInitializeListener.java   // 核心:监控初始化监听器
    │   └── package-info.java                       // 包级别文档
    └── pom.xml
    

    没错,整个模块只有一个有效的 Java 类。这不是偷工减料,而是精准克制——对于传统 Spring MVC 项目的集成,真正需要做的事只有一件:在 Servlet 上下文初始化时启动监控。一个 Listener,足矣。

    3.2 Maven 依赖

    <dependencies>
        <!-- 监控客户端核心模块 -->
        <dependency>
            <groupId>com.gitee.pifeng</groupId>
            <artifactId>phoenix-client-core</artifactId>
        </dependency>
    
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- slf4j 日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- logback 日志实现 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    

    几个值得注意的点:

    依赖 phoenix-client-core 而非 phoenix-client-spring-boot-starter:这个模块和 Spring Boot 没有半毛钱关系。它直接依赖核心模块,获取 Monitor 入口类和配置加载能力。

    servlet-api 的 scope 是 provided:因为 Servlet API 由 Tomcat / Jetty 等容器在运行时提供,不需要也不应该打包进 JAR。如果打包进去,反而会和容器自带的版本冲突。

    没有任何 Spring 相关依赖:没有 spring-boot-autoconfigure,没有 spring-context,甚至连 spring-web 都没有。这意味着——这个模块可以用在任何 Servlet 3.0+ 的容器环境中,不限于 Spring MVC,哪怕是纯 Servlet + JSP 的项目也能用。

    这种极致的轻量化,让它成为了一个真正的“万能适配器”。


    四、MonitoringPlugInitializeListener:逐行拆解

    现在进入核心——MonitoringPlugInitializeListener 类的完整实现。

    4.1 完整源码

    @Slf4j
    public class MonitoringPlugInitializeListener implements ServletContextListener {
    
        @SneakyThrows
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            // 配置参数值
            String configLocation = sce.getServletContext()
                    .getInitParameter("configLocation");
            // 自定义了配置文件路径和名字
            if (StringUtils.isNotBlank(configLocation)) {
                String[] config = this.getConfigPathAndName(configLocation);
                Monitor.start(config[0], config[1]);
            } else {
                Monitor.start();
            }
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            log.info("Servlet上下文销毁!");
        }
    
        private String[] getConfigPathAndName(String configLocation) 
                throws BadListenerConfigException {
            // 异常信息
            String expMsg = "\r\n监控客户端初始化监听器配置有误,请参考如下配置:\r\n"
                    + "<context-param>\r\n"
                    + " <param-name>configLocation</param-name>\r\n"
                    + " <param-value>classpath:conf/monitoring.properties</param-value>\r\n"
                    + "</context-param>\r\n"
                    + "<listener>\r\n"
                    + " <listener-class>com.gitee.pifeng.monitoring.integrator"
                    + ".listener.MonitoringPlugInitializeListener</listener-class>\r\n"
                    + "</listener>\r\n";
            // 返回值
            String[] result = new String[2];
            if (!StringUtils.startsWith(configLocation, "classpath:") 
                    && !StringUtils.startsWith(configLocation, "filepath:")) {
                throw new BadListenerConfigException(expMsg);
            }
            String[] pathAndName = configLocation.split("/");
            // 配置文件路径
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < pathAndName.length - 1; i++) {
                builder.append(pathAndName[i]).append("/");
            }
            String path = builder.toString();
            // 配置文件名字
            String name = pathAndName[pathAndName.length - 1];
            result[0] = path;
            result[1] = name;
            return result;
        }
    }
    

    代码不长,但信息量不小。我们逐段分析。

    4.2 contextInitialized():启动监控的“触发器”

    @SneakyThrows
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        String configLocation = sce.getServletContext()
                .getInitParameter("configLocation");
        if (StringUtils.isNotBlank(configLocation)) {
            String[] config = this.getConfigPathAndName(configLocation);
            Monitor.start(config[0], config[1]);
        } else {
            Monitor.start();
        }
    }
    

    整个方法只做一件事:ServletContext 中读取 configLocation 参数,然后调用 Monitor.start() 启动监控。

    这里有两条分支:

    分支一:用户配置了 configLocation

    web.xml<context-param> 中读取到配置文件路径(如 classpath:conf/monitoring.properties),解析出路径和文件名,调用 Monitor.start(path, name)。这最终会走到 ConfigLoader.load() 方法,从指定路径加载 properties 配置文件。

    分支二:用户没有配置 configLocation

    直接调用无参的 Monitor.start(),使用默认的配置文件路径和文件名(classpath:monitoring.properties)。

    注意这里和 Spring Boot Starter 的一个关键区别:SpringMVC Integrator 只支持 properties 配置文件方式,不支持 application.yml 方式。 因为 application.yml 的解析依赖 Spring Boot 的 @ConfigurationProperties 绑定机制——而这个模块压根没有 Spring Boot。所以,这条集成路径走的始终是第九篇中讲过的“properties 文件”那条配置轨道。

    @SneakyThrows 是 Lombok 提供的注解,它会在编译期自动添加 try-catch 并重新抛出受检异常,免去了在方法签名上声明 throws 的繁琐。这里可能抛出的异常包括 BadListenerConfigException(配置格式错误)、NotFoundConfigFileException(找不到配置文件)等。

    4.3 getConfigPathAndName():路径解析的防御性设计

    private String[] getConfigPathAndName(String configLocation) 
            throws BadListenerConfigException {
        String expMsg = "\r\n监控客户端初始化监听器配置有误,请参考如下配置:\r\n"
                + "<context-param>\r\n"
                + " <param-name>configLocation</param-name>\r\n"
                + " <param-value>classpath:conf/monitoring.properties</param-value>\r\n"
                + "</context-param>\r\n"
                + "<listener>\r\n"
                + " <listener-class>com.gitee.pifeng.monitoring.integrator"
                + ".listener.MonitoringPlugInitializeListener</listener-class>\r\n"
                + "</listener>\r\n";
        
        String[] result = new String[2];
        if (!StringUtils.startsWith(configLocation, "classpath:") 
                && !StringUtils.startsWith(configLocation, "filepath:")) {
            throw new BadListenerConfigException(expMsg);
        }
        String[] pathAndName = configLocation.split("/");
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < pathAndName.length - 1; i++) {
            builder.append(pathAndName[i]).append("/");
        }
        String path = builder.toString();
        String name = pathAndName[pathAndName.length - 1];
        result[0] = path;
        result[1] = name;
        return result;
    }
    

    这个方法做了三件事:

    第一,校验路径前缀。 configLocation 必须以 classpath:filepath: 开头。classpath: 表示从类路径加载(JAR 包内或 classes 目录下),filepath: 表示从文件系统的绝对路径加载。如果用户写了一个不带前缀的路径(比如 conf/monitoring.properties),直接抛出 BadListenerConfigException

    第二,拆分路径和文件名。/ 为分隔符,最后一段是文件名,前面所有段拼接起来是路径。例如:

    configLocation 解析后的路径(path) 解析后的文件名(name)
    classpath:conf/monitoring.properties classpath:conf/ monitoring.properties
    filepath:/opt/config/monitoring.properties filepath:/opt/config/ monitoring.properties
    classpath:monitoring.properties classpath: monitoring.properties

    第三,错误提示的“说人话”设计。 异常消息不是一句冷冰冰的 “Invalid config location”,而是直接把正确的 web.xml 配置范例贴出来——包括 <context-param><listener> 的完整写法。对于一个可能在凌晨三点排查启动失败的运维工程师来说,这种“手把手教你改”的错误提示,比任何文档都管用。

    这和第十三篇中 MonitoringPlugAutoConfiguration.analysisConfigFilePath() 的设计如出一辙——那里也是检测到路径前缀不合法时,直接把 @EnableMonitoring 的正确用法贴出来。两个模块、两条集成路径,在错误处理的理念上保持了高度一致。

    4.4 contextDestroyed():为什么几乎什么都没做?

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("Servlet上下文销毁!");
    }
    

    你可能会好奇——Listener 既然能感知上下文销毁,为什么不在这里做监控资源的清理?

    答案是:不需要,因为 Monitor.start() 的内部已经通过 JVM 关闭钩子(ShutdownHook)注册了清理逻辑。 在第八篇中我们详细讲过,ShutdownHook.addShutdownHook() 会在 JVM 退出前自动执行发送下线数据包、优雅关闭线程池、关闭 HTTP 连接池、关闭 WebSocket 数据交换器等操作。

    JVM 关闭钩子和 Servlet 上下文销毁的执行时机略有不同:contextDestroyed() 在应用卸载时触发(比如 Tomcat 热部署时),而 ShutdownHook 在 JVM 进程结束时触发。但两者都能覆盖“应用停止”的场景。Phoenix 选择统一通过 ShutdownHook 处理,而不是在每条集成路径中重复实现清理逻辑——这是一种“关注点分离”的设计思路:集成层只负责“启动”,清理逻辑下沉到核心层统一管理。


    五、web.xml 配置实战

    理解了源码之后,来看看实际使用时的 web.xml 配置。

    5.1 最简配置(使用默认路径)

    如果你的 monitoring.properties 放在 classpath 根目录下,只需要注册 Listener,不需要 <context-param>

    <listener>
        <listener-class>
            com.gitee.pifeng.monitoring.integrator.listener.MonitoringPlugInitializeListener
        </listener-class>
    </listener>
    

    此时 contextInitialized() 方法读到的 configLocationnull,走无参 Monitor.start() 分支,默认加载 classpath:monitoring.properties

    5.2 自定义配置文件路径

    如果你的配置文件放在 classpath 的子目录中:

    <context-param>
        <param-name>configLocation</param-name>
        <param-value>classpath:conf/monitoring.properties</param-value>
    </context-param>
    <listener>
        <listener-class>
            com.gitee.pifeng.monitoring.integrator.listener.MonitoringPlugInitializeListener
        </listener-class>
    </listener>
    

    5.3 使用文件系统绝对路径

    如果你的配置文件放在服务器的某个目录下(而非 classpath 中):

    <context-param>
        <param-name>configLocation</param-name>
        <param-value>filepath:/opt/phoenix/config/monitoring.properties</param-value>
    </context-param>
    <listener>
        <listener-class>
            com.gitee.pifeng.monitoring.integrator.listener.MonitoringPlugInitializeListener
        </listener-class>
    </listener>
    

    filepath: 前缀适用于配置文件需要脱离 WAR 包独立管理的场景——比如同一个 WAR 包部署在多台服务器上,每台服务器的监控配置不同(实例名、服务端地址等),就可以把配置文件放在服务器本地目录中,通过 filepath: 指向它。


    六、异常体系:BadListenerConfigException

    configLocation 参数格式不合法时,getConfigPathAndName() 方法会抛出 BadListenerConfigException。看一下它的定义:

    public class BadListenerConfigException extends MonitoringUniversalException {
    
        private static final long serialVersionUID = -1672157366010494089L;
    
        public BadListenerConfigException() {
            super();
        }
    
        public BadListenerConfigException(String message) {
            super(message);
        }
    }
    

    它继承自 MonitoringUniversalException——Phoenix 异常体系中的“通用基类”。Phoenix 为不同的错误场景定义了语义明确的异常类:

    异常类 使用场景 归属模块
    BadListenerConfigException Listener 配置参数格式错误 SpringMVC Integrator
    BadAnnotateParamException @EnableMonitoring 注解参数格式错误 Spring Boot Starter
    NotFoundConfigFileException 找不到监控配置文件 Client Core
    NotFoundConfigParamException 缺少必要的配置参数 Client Core
    ErrorConfigParamException 配置参数值不合法 Client Core

    BadListenerConfigExceptionBadAnnotateParamException 是一对“平行异常”——一个服务于 Listener 集成路径,一个服务于注解集成路径。它们的职责相同(校验入口层的参数格式),只是触发场景不同。


    七、两条集成路径的全景对比

    到这里,Phoenix 客户端的两条集成路径都已经拆解完毕。把它们放在一起对比,能更清晰地理解各自的定位:

    7.1 架构对比

    路径一:Spring Boot Starter
    ═══════════════════════════════════════
    @EnableMonitoring
        → @Import(EnableMonitoringPlugSelector)
        → DeferredImportSelector
        → spring.factories
        → MonitoringPlugAutoConfiguration
        → ImportAware 读取注解参数
        → Monitor.start(monitoringProperties)  
           或 Monitor.start(path, name)
    
    路径二:SpringMVC Integrator
    ═══════════════════════════════════════
    web.xml
        → <listener> 注册 MonitoringPlugInitializeListener
        → <context-param> 传递 configLocation
        → contextInitialized()
        → getConfigPathAndName() 解析路径
        → Monitor.start(path, name) 
           或 Monitor.start()
    

    两条路径最终都汇聚到同一个终点——Monitor.start() 区别只在于“从哪里出发”和“怎么拿到配置”。

    7.2 特性对比

    对比维度 Spring Boot Starter SpringMVC Integrator
    适用场景 Spring Boot 应用 传统 Spring MVC / 纯 Servlet 应用
    集成方式 @EnableMonitoring 注解 web.xml 中注册 Listener
    配置来源 application.yml 或独立 properties 文件 仅支持独立 properties 文件
    依赖 spring-boot-autoconfigure servlet-api(provided)
    模块中的 Java 类数量 6 个(注解、选择器、配置类、属性类、后置处理器、工具类) 1 个(Listener)
    线程池自动纳管 支持(@MonitoringThreadPool + BeanPostProcessor 不支持
    启动时机 Spring 容器刷新阶段 Servlet 容器启动阶段(更早)
    对 Spring 的依赖 强依赖 Spring Boot 无依赖

    7.3 关于线程池自动纳管

    你可能注意到上表中的一个差异——SpringMVC Integrator 不支持 @MonitoringThreadPool 注解的自动线程池纳管。这是因为 MonitoringThreadPoolBeanPostProcessor 是 Spring Boot 的 BeanPostProcessor 机制,它依赖 Spring 容器的 Bean 生命周期管理。在没有 Spring Boot 的环境中,这个能力自然无法使用。

    但这并不意味着传统项目就不能监控线程池。在第十二篇中我们讲过,Phoenix 的线程池监控有两种注册方式:

    1. @MonitoringThreadPool 注解:由 BeanPostProcessor 自动替换(仅 Spring Boot)
    2. 构造器直接注册:直接使用 MonitoredThreadPoolExecutorMonitoredScheduledThreadPoolExecutor,构造时自动注册到 ThreadPoolManager

    对于传统 Spring MVC 项目,使用第二种方式——把 new ThreadPoolExecutor(...) 替换为 new MonitoredThreadPoolExecutor(...)——就能实现线程池监控。少了一层“自动”的便利,但核心能力完全等价。


    八、Monitor.start() 的三个重载:三条路径的汇聚点

    无论从哪条集成路径进入,最终都会调用 Monitor 类的 start() 方法。回顾一下它的三个重载:

    // 重载一:无参——使用默认路径加载 monitoring.properties
    public static MonitoringProperties start() {
        return run(null, null, null);
    }
    
    // 重载二:指定路径和文件名——加载指定位置的 properties 文件
    public static MonitoringProperties start(String configPath, String configName) {
        return run(configPath, configName, null);
    }
    
    // 重载三:直接传入配置对象——由 Spring Boot @ConfigurationProperties 绑定好的
    public static MonitoringProperties start(MonitoringProperties monitoringProperties) {
        run(null, null, monitoringProperties);
        return monitoringProperties;
    }
    

    SpringMVC Integrator 只会触发前两个重载:

    • 用户没配 configLocationMonitor.start()(重载一)
    • 用户配了 configLocationMonitor.start(path, name)(重载二)

    Spring Boot Starter 三个都可能触发:

    • usingMonitoringConfigFile = true 且路径为空 → 重载一
    • usingMonitoringConfigFile = true 且指定了路径 → 重载二
    • usingMonitoringConfigFile = false(默认)→ 重载三

    重载三是 Spring Boot 独享的“特权”——只有通过 @ConfigurationProperties 绑定出来的 MonitoringSpringBootProperties 对象,才能走这条路。这也解释了为什么 SpringMVC Integrator 不支持 application.yml 配置:因为没有 Spring Boot 的属性绑定机制,就无法构造出 MonitoringProperties 对象来调用重载三。

    三个重载最终都汇入同一个私有方法 run(),执行完全相同的启动序列:打印 Banner → 加载配置 → 验证许可证 → 初始化加解密 → 启动数据交换器 → 心跳 → 服务器采集 → JVM 采集 → 线程池采集 → 网络设备采集 → Arthas 挂载 → 注册关闭钩子。

    集成层的职责到“调用 Monitor.start()”为止,后续的一切由核心层统一处理。 这就是良好分层设计带来的好处——新增一条集成路径,不需要改动核心层的任何一行代码。


    九、设计思考:为什么不合并成一个模块?

    你可能会想:phoenix-client-spring-mvc-integrator 只有一个类,为什么不直接放进 phoenix-client-core 里?

    答案是依赖隔离

    phoenix-client-core 是纯 Java 模块,不依赖任何 Web 相关的 API。而 MonitoringPlugInitializeListener 依赖了 javax.servlet.ServletContextListener——这是 Servlet 规范的接口。

    如果把它放进 core 模块,那么所有使用 phoenix-client-core 的项目都会间接依赖 servlet-api。对于非 Web 应用(比如一个纯后台的定时任务进程),这个依赖是多余的,甚至可能引起冲突。

    同理,phoenix-client-spring-boot-starter 依赖了 spring-boot-autoconfigure,也不能放进 core。

    三个模块各自的依赖边界非常清晰:

    phoenix-client-core                → 纯 Java,无框架依赖
    phoenix-client-spring-boot-starter → 依赖 Spring Boot
    phoenix-client-spring-mvc-integrator → 依赖 Servlet API
    

    用户按需引入:Spring Boot 项目引 starter,传统项目引 integrator,非 Web 项目直接用 core。 没有人会被强迫引入不需要的依赖。这就是 Maven 多模块工程“按职责拆分”的典型实践。


    十、小结

    这一篇我们拆解了 Phoenix 客户端的第二条集成路径——phoenix-client-spring-mvc-integrator 模块。它的核心只有一个 MonitoringPlugInitializeListener,但麻雀虽小,五脏俱全。回顾几个关键要点:

    1. ServletContextListener 作为入口:利用 Servlet 规范中最古老、最通用的生命周期钩子,在 Servlet 上下文初始化完成时启动监控。不依赖 Spring Boot,不依赖注解扫描,兼容一切 Servlet 3.0+ 环境。

    2. context-param 传递配置路径:通过 web.xml<context-param> 传递 configLocation 参数,支持 classpath:filepath: 两种前缀。不传则使用默认路径,传错则给出完整的配置范例提示。

    3. 路径解析的防御性设计getConfigPathAndName() 方法以 / 为分隔符拆分路径和文件名,并在前缀不合法时抛出 BadListenerConfigException,异常消息直接包含正确的 web.xml 配置示例。

    4. 与 Spring Boot Starter 的“殊途同归”:两条集成路径最终都汇聚到 Monitor.start(),区别仅在于入口机制(注解 vs Listener)和配置来源(application.yml / properties 文件 vs 仅 properties 文件)。核心层的启动逻辑完全复用。

    5. 模块独立与依赖隔离:整个模块只依赖 phoenix-client-coreservlet-api(provided),不引入任何 Spring 依赖。core、starter、integrator 三个模块按依赖边界严格分离,用户按需引入。

    从 Spring Boot Starter 到 SpringMVC Integrator,Phoenix 为不同技术栈的项目提供了对应的集成方案。但无论走哪条路,最终启动的监控引擎是同一个——配置加载、心跳上报、数据采集、加解密、数据交换,一个都不少。好的 SDK 设计,就是让用户在不同的环境中,都能以最低的成本获得完全相同的能力。

    下一篇,我们将转向 Phoenix 客户端的业务层能力——同步与异步告警发送 API 的设计。敬请期待。


    项目地址
    https://gitee.com/pifeng/phoenix
    https://gitee.com/monitoring-platform/phoenix
    https://github.com/709343767/phoenix

    欢迎关注微信公众号获取更多技术干货
    微信公众号·披锋斩棘

    end
  1. 作者: 锋哥 (联系作者)
  2. 发表时间: 2026-04-08 16:15
  3. 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  4. 转载声明:如果是转载博主转载的文章,请附上原文链接
  5. 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  6. 评论

    站长头像 知录

    你一句春不晚,我就到了真江南!

    文章0
    浏览0

    文章分类

    标签云