Tomcat Maven插件:部署与卸载的架构设计

这段代码展示了 Apache Tomcat Maven Plugin(如 tomcat8-maven-plugin)中"部署"和"卸载"类目标的完整设计体系 。它通过多层抽象 + 组合复用 + 注解驱动的方式,实现了高度灵活、可配置、可扩展的远程 Tomcat 操作能力。

我们来逐层拆解理解:


🌐 整体结构概览

text 复制代码
AbstractWarCatalinaMojo
  ↓
AbstractDeployMojo                 ← 定义部署逻辑(war / context / both)
  ↓
AbstractDeployWarMojo              ← 专用于 WAR 文件部署
  ↓
DeployMojo                         ← @Mojo(name = "deploy") ------ 绑定到 package 阶段
DeployOnlyMojo                     ← @Mojo(name = "deploy-only") ------ 不绑定生命周期
  ↓
RedeployMojo                       ← 覆盖 isUpdate() → true(自动 undeploy 再 deploy)
RedeployOnlyMojo                   ← 同上,但不绑定生命周期

UndeployMojo                       ← 独立实现,用于卸载应用

✅ 所有目标都继承自 AbstractWarCatalinaMojo,因此天然支持:

  • 只处理 WAR 项目
  • 连接远程 Tomcat Manager
  • 多种认证方式
  • 日志与错误处理

🔍 核心类详解

1️⃣ AbstractDeployMojo ------ 部署策略的抽象基类

  • 核心功能 :根据 mode 参数决定部署方式:
    • war:只部署 WAR 文件(默认)
    • context:只部署 context.xml
    • both:同时部署 WAR + context.xml
java 复制代码
@Override
    public void invokeManager()
        throws MojoExecutionException, TomcatManagerException, IOException
    {
        if ( "war".equals( mode ) )
        {
            deployWar();
        }
        else if ( "context".equals( mode ) )
        {
            deployContext();
        }
        else if ( "both".equals( mode ) )
        {
            deployWarAndContext();
        }
        else
        {
            throw new MojoExecutionException( messagesProvider.getMessage( "AbstractDeployMojo.unknownMode", mode ) );
        }
    }

	/**
     * Deploys the WAR to Tomcat.
     *
     * @throws org.apache.maven.plugin.MojoExecutionException
     *                             if there was a problem locating the WAR
     * @throws org.apache.tomcat.maven.common.deployer.TomcatManagerException
     *                             if the Tomcat manager request fails
     * @throws java.io.IOException if an i/o error occurs
     */
    protected void deployWar()
        throws MojoExecutionException, TomcatManagerException, IOException
    {
        validateWarFile();

        getLog().info( messagesProvider.getMessage( "AbstractDeployMojo.deployingWar", getDeployedURL() ) );

        URL warURL = getWarFile().toURL();
        log( getManager().deploy( getPath(), warURL, isUpdate(), getTag() ).getHttpResponseBody() );
    }

    /**
     * Deploys the context XML file to Tomcat.
     *
     * @throws org.apache.maven.plugin.MojoExecutionException
     *                             if there was a problem locating the context XML file
     * @throws org.apache.tomcat.maven.common.deployer.TomcatManagerException
     *                             if the Tomcat manager request fails
     * @throws java.io.IOException if an i/o error occurs
     */
    protected void deployContext()
        throws MojoExecutionException, TomcatManagerException, IOException
    {
        validateContextFile();

        getLog().info( messagesProvider.getMessage( "AbstractDeployMojo.deployingContext", getDeployedURL() ) );

        URL contextURL = getContextFile().toURL();
        log( getManager().deployContext( getPath(), contextURL, isUpdate(), getTag() ).getHttpResponseBody() );
    }

    /**
     * Deploys the WAR and context XML file to Tomcat.
     *
     * @throws org.apache.maven.plugin.MojoExecutionException
     *                             if there was a problem locating either the WAR or the context XML file
     * @throws org.apache.tomcat.maven.common.deployer.TomcatManagerException
     *                             if the Tomcat manager request fails
     * @throws java.io.IOException if an i/o error occurs
     */
    protected void deployWarAndContext()
        throws MojoExecutionException, TomcatManagerException, IOException
    {
        validateWarFile();
        validateContextFile();

        getLog().info( messagesProvider.getMessage( "AbstractDeployMojo.deployingWarContext", getDeployedURL() ) );

        URL warURL = getWarFile().toURL();
        URL contextURL = getContextFile().toURL();

        TomcatManagerResponse tomcatResponse = getManager().deployContext( getPath(), contextURL, warURL, isUpdate(), getTag() );

        checkTomcatResponse( tomcatResponse );

        log( tomcatResponse.getHttpResponseBody() );
    }
  • 关键方法

    java 复制代码
    protected abstract File getWarFile();      // 子类提供 WAR 路径
    protected abstract void validateWarFile(); // 子类校验 WAR 是否存在
  • 参数

    • mode:部署模式
    • contextFile:上下文文件路径(默认 ${project.build.directory}/.../META-INF/context.xml
    • update:是否先 undeploy 再 deploy(对应 Tomcat 的 ?update=true
    • tag:用于标记部署版本(高级功能)

💡 它不关心 WAR 是文件还是目录,把具体实现留给子类。


2️⃣ AbstractDeployWarMojo ------ WAR 文件部署的具体实现

  • 覆盖父类方法

    java 复制代码
    @Override
    protected File getWarFile() {
        return warFile; // 来自参数,默认是 target/${artifactId}.war
    }
    
    @Override
    protected void validateWarFile() {
        if (!warFile.exists()) throw ...;
    }
  • 优化 deployWar()

    • 调用 getManager().deploy(..., warFile, ..., warFile.length())
    • 显式传入文件大小 → 支持更高效的 HTTP 上传(避免先读入内存)
java 复制代码
/**
     * {@inheritDoc}
     */
    @Override
    protected void deployWar()
        throws MojoExecutionException, TomcatManagerException, IOException
    {
        validateWarFile();

        getLog().info( messagesProvider.getMessage( "AbstractDeployMojo.deployingWar", getDeployedURL() ) );

        TomcatManagerResponse tomcatManagerResponse =
            getManager().deploy( getPath(), warFile, isUpdate(), getTag(), warFile.length() );

        checkTomcatResponse( tomcatManagerResponse );

        getLog().info( "tomcatManager status code:" + tomcatManagerResponse.getStatusCode() + ", ReasonPhrase:"
                           + tomcatManagerResponse.getReasonPhrase() );

        log( tomcatManagerResponse.getHttpResponseBody() );
    }

✅ 这是 最常用 的部署方式:直接上传一个 .war 文件到 Tomcat。


3️⃣ DeployMojo vs DeployOnlyMojo ------ 生命周期绑定差异

注解 行为
DeployMojo @Execute(phase = LifecyclePhase.PACKAGE) 执行前自动触发 package 阶段(确保 WAR 已生成)
DeployOnlyMojo @Execute 不触发任何生命周期,假定 WAR 已存在
使用场景:
bash 复制代码
# 自动打包并部署
mvn tomcat8:deploy

# 假设 WAR 已存在,直接部署(CI/CD 中常用)
mvn tomcat8:deploy-only

🔹 @Execute 是 Maven 插件的"前置任务"机制。


4️⃣ RedeployMojo / RedeployOnlyMojo ------ 便捷别名

  • 核心 trick :重写 isUpdate() 返回 true

    java 复制代码
    @Override
    protected boolean isUpdate() {
        return true; // 等价于 -Dmaven.tomcat.update=true
    }
  • 效果 :Tomcat 会先 undeploy 同 path 的应用,再 deploy 新的 → 避免"already exists"错误

✅ 用户无需记参数,直接用 redeploy 更直观。


5️⃣ UndeployMojo ------ 独立卸载逻辑

  • 简单直接 :调用 manager.undeploy(path)

  • 容错设计

    java 复制代码
    if (failOnError) throw exception;
    else log.warn("Failed to undeploy, but continuing...");
  • 适用场景:清理环境、滚动部署前移除旧版本


🧩 设计亮点总结

特性 说明
分层抽象 从"是否 WAR 项目" → "如何连接 Tomcat" → "部署什么" → "如何部署",职责清晰
策略模式 mode=war/context/both 允许不同部署策略
模板方法 invokeManager() 调用子类实现的 deployXxx()
生命周期集成 @Execute(phase = PACKAGE) 自动构建 WAR
用户友好 提供 deploy / redeploy / deploy-only 等语义化目标
安全容错 failOnError 控制卸载失败是否中断构建

🛠️ 实际使用示例

场景 1:标准部署(自动打包)

bash 复制代码
mvn tomcat8:deploy \
  -Dtomcat.username=admin \
  -Dtomcat.password=secret \
  -Dmaven.tomcat.url=http://prod-server:8080/manager/text

场景 2:重新部署(覆盖已有应用)

bash 复制代码
mvn tomcat8:redeploy
# 等价于 mvn tomcat8:deploy -Dmaven.tomcat.update=true

场景 3:仅卸载(不中断构建)

bash 复制代码
mvn tomcat8:undeploy -Dmaven.tomcat.failOnError=false

✅ 总结一句话

这套代码通过"抽象基类 + 具体实现 + 注解元数据"的组合,将 Tomcat 部署/卸载操作封装成一组语义清晰、配置灵活、安全可靠的 Maven 目标(goals),既满足日常开发需求,也适配自动化部署场景。

它是 Maven 插件设计的最佳实践范例:高内聚、低耦合、易扩展、用户友好。

相关推荐
逝水如流年轻往返染尘8 分钟前
JAVA中的抽象类
java·开发语言
hx8622733 分钟前
Java MySQL 连接
java·mysql·adb
lpfasd12335 分钟前
Kubernetes (K8s) 底层早已不再直接使用 Docker 引擎了
java·docker·kubernetes
aq553560038 分钟前
SpringBoot有几种获取Request对象的方法
java·spring boot·后端
lierenvip1 小时前
Spring Boot中Tomcat配置
spring boot·tomcat·firefox
Detachym1 小时前
InsightFlow 服务配置优化与部署实践
java·spring boot·tomcat·maven·状态模式·jar
y = xⁿ2 小时前
【LeetCodehot100】T23:合并k个升序链表
java·数据结构·链表
流水武qin2 小时前
SpringAI多模态的基本使用
java·spring boot·spring·ai
共享家95272 小时前
Java入门(多态)
java·开发语言
毕设源码-赖学姐2 小时前
【开题答辩全过程】以 基于Java的婚礼策划平台的设计与实现为例,包含答辩的问题和答案
java·开发语言