如何设计Jenkins任务实现Maven构建、Jetty启动与JUnit测试并解决阻塞
这问题我太熟了!很多做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,执行顺序如下:
编译打包:
mvn clean compile package -DskipTests(跳过单元测试,因为我们要跑的是Jetty上的集成测试)
后台启动Jetty:
mvn jetty:run &这里的
&让命令在后台执行,不会阻塞后续步骤;加上pom里的daemon=true,Jetty会以守护进程运行,即使Jenkins的Shell会话结束也能暂时存活(但我们后面会主动停止)等待服务就绪:
别刚启动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执行JUnit集成测试:
假设你的集成测试用例放在src/test/java/com/yourcompany/integration下,或者用Maven的verify阶段执行集成测试:mvn verify -Dtest=IntegrationTest*(
-Dtest参数可以指定只跑集成测试类,避免重复跑单元测试)停止Jetty服务:
用Jetty插件的stop目标,通过之前配置的停止端口和密钥来停止:mvn jetty:stop
方案2:手动管理Jetty进程PID(适合自定义场景)
如果不想改pom.xml,也可以手动记录Jetty的PID,后续通过PID杀掉进程:
启动Jetty并记录PID:
mvn jetty:run > jetty.log 2>&1 & echo $! > jetty.pid(
$!是上一个后台进程的PID,写入jetty.pid文件保存)停止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




