You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何设计Jenkins任务实现Maven构建、Jetty启动与JUnit测试并解决阻塞

最优Jenkins任务设计:解决Jetty阻塞+REST服务集成测试问题

这问题我太熟了!很多做Java持续集成的同学第一次搭Jetty集成测试流水线时,都会踩这个“启动Jetty后脚本卡住”的坑——本质就是Jetty默认前台运行,会阻塞后续命令执行。下面给你一套完整的最优解决方案,从Jenkins任务结构到具体命令配置都讲清楚:

整体流程设计

一个标准的Jenkins任务应该分这几个阶段,环环相扣:

  • 拉取代码(Git/SVN)
  • Maven编译打包项目
  • 后台启动Jetty服务(核心:避免阻塞)
  • 等待Jetty上的REST服务完全就绪
  • 执行针对REST服务的JUnit集成测试
  • 停止Jetty服务(无论测试成功/失败都要做)
  • 清理工作空间(可选)

核心问题解决:让Jetty后台运行并可控停止

方案1:用Maven Jetty插件的stop机制(推荐,最可靠)

这是官方推荐的方式,不需要手动管PID,通过配置停止端口和密钥来控制Jetty启停。

第一步:在项目pom.xml中配置Jetty插件

添加以下配置,指定服务端口、停止端口和停止密钥:

<build>
  <plugins>
    <plugin>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-maven-plugin</artifactId>
      <version>9.4.51.v20230217</version> <!-- 用你项目兼容的版本 -->
      <configuration>
        <httpConnector>
          <port>8080</port> <!-- 服务运行端口 -->
        </httpConnector>
        <stopPort>8081</stopPort> <!-- 专门用来停止Jetty的端口 -->
        <stopKey>my-jetty-stop-key</stopKey> <!-- 自定义停止密钥,避免误操作 -->
        <stopWait>5</stopWait> <!-- 等待Jetty停止的超时时间 -->
        <daemon>true</daemon> <!-- 让Jetty以守护进程模式运行,后台执行 -->
      </configuration>
    </plugin>
  </plugins>
</build>

第二步:Jenkins任务中的具体命令

不管你用自由风格项目还是Pipeline,执行顺序如下:

  1. 编译打包

    mvn clean compile package -DskipTests
    

    (跳过单元测试,因为我们要跑的是Jetty上的集成测试)

  2. 后台启动Jetty

    mvn jetty:run &
    

    这里的&让命令在后台执行,不会阻塞后续步骤;加上pom里的daemon=true,Jetty会以守护进程运行,即使Jenkins的Shell会话结束也能暂时存活(但我们后面会主动停止)

  3. 等待服务就绪
    别刚启动Jetty就跑测试!一定要等REST服务完全启动。写个小脚本循环检查服务的健康接口(如果没有健康接口,就检查某个核心REST接口的返回):

    # 循环检查,直到服务返回200状态码,最多等30秒
    MAX_WAIT=30
    WAIT_COUNT=0
    while ! curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/health | grep -q "200"; do
      sleep 2
      WAIT_COUNT=$((WAIT_COUNT+2))
      if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
        echo "Jetty服务启动超时"
        exit 1
      fi
    done
    
  4. 执行JUnit集成测试
    假设你的集成测试用例放在src/test/java/com/yourcompany/integration下,或者用Maven的verify阶段执行集成测试:

    mvn verify -Dtest=IntegrationTest*
    

    -Dtest参数可以指定只跑集成测试类,避免重复跑单元测试)

  5. 停止Jetty服务
    用Jetty插件的stop目标,通过之前配置的停止端口和密钥来停止:

    mvn jetty:stop
    

方案2:手动管理Jetty进程PID(适合自定义场景)

如果不想改pom.xml,也可以手动记录Jetty的PID,后续通过PID杀掉进程:

  1. 启动Jetty并记录PID:

    mvn jetty:run > jetty.log 2>&1 &
    echo $! > jetty.pid
    

    $!是上一个后台进程的PID,写入jetty.pid文件保存)

  2. 停止Jetty时读取PID并杀掉:

    if [ -f jetty.pid ]; then
      kill $(cat jetty.pid)
      rm jetty.pid
    fi
    

    这种方式要注意:如果Jenkins任务异常中断,PID文件可能残留,导致下次任务出错,所以最好在任务开始时清理旧的PID文件。

Jenkins Pipeline最佳实践(推荐用Pipeline,更灵活可控)

如果用Jenkins Pipeline,把整个流程写成Jenkinsfile,还能通过post块确保无论测试成功还是失败,都会停止Jetty:

pipeline {
  agent any
  stages {
    stage('拉取代码') {
      steps {
        git url: 'https://your-git-repo-url.git', branch: 'main'
      }
    }
    stage('Maven编译打包') {
      steps {
        sh 'mvn clean compile package -DskipTests'
      }
    }
    stage('启动Jetty服务') {
      steps {
        sh 'mvn jetty:run &'
      }
    }
    stage('等待服务就绪') {
      steps {
        sh '''
          MAX_WAIT=30
          WAIT_COUNT=0
          while ! curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/health | grep -q "200"; do
            sleep 2
            WAIT_COUNT=$((WAIT_COUNT+2))
            if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
              echo "Jetty启动超时"
              exit 1
            fi
          done
        '''
      }
    }
    stage('执行集成测试') {
      steps {
        sh 'mvn verify -Dtest=IntegrationTest*'
      }
    }
  }
  post {
    always { // 无论成功/失败/中断,都执行这个步骤
      sh 'mvn jetty:stop'
      echo '已停止Jetty服务'
    }
  }
}

额外注意事项

  • 端口冲突:如果Jenkins是多节点运行,最好用随机端口(可以通过Maven参数-Djetty.port=随机数指定),避免多个任务占用同一个端口。
  • 日志收集:把Jetty的日志输出到文件,方便排查问题,比如mvn jetty:run > jetty.log 2>&1 &
  • 测试隔离:确保每个Jenkins任务都在独立的环境中运行,避免残留进程影响下一次任务。

内容的提问来源于stack exchange,提问作者kladderradatsch

火山引擎 最新活动