跳到主要内容

Groovy 基础

Jenkins 的流水线(无论声明式还是脚本式)都是基于 Groovy 的语言。写 Jenkinsfile 并不需要成为 Groovy 专家,但掌握核心语法能帮你写出更简洁、可维护的流水线,并在排障时读懂报错。

本篇目标:掌握变量、字符串、集合、闭包、方法定义、异常处理等流水线高频用到的 Groovy 语法。


1. Groovy 与 Java 的关系​

Groovy 是运行在 JVM 上的动态语言,语法对 Java 几乎完全兼容,但更简洁:

JavaGroovy
String s = "hello";def s = "hello"
System.out.println(x)println x
for (int i=0; i<10; i++)for (i in 0..9) 或 10.times {...}
需要分号分号可省略
强类型动态类型(def)
你其实不需要学完整 Groovy

流水线里 80% 的 Groovy 是:变量、字符串拼接、if/else、集合遍历、闭包、方法调用。本篇只讲这些,够用即可。

2. 变量与类型​

// def 声明变量(动态类型)
def name = 'jenkins'
def count = 3
def enabled = true
def items = ['a', 'b', 'c'] // List
def config = [key: 'value'] // Map(等价 JSON 对象)

// 字符串
def s1 = '单引号:原样输出,不做插值' // 单引号字符串
def s2 = "双引号:支持插值 ${name}" // 插值要用双引号
def s3 = '''三引号:多行字符串
第二行
第三行'''

// 类型转换
def str = '42'
def num = str.toInteger()
def n = 3.14
println n.toInteger() // 3
单引号 vs 双引号

单引号字符串不解析 ${} 插值,双引号才解析。在流水线里拼接变量时用双引号:

echo "构建号: ${BUILD_NUMBER}" // ✅ 正确
echo '构建号: ${BUILD_NUMBER}' // ❌ 原样输出 ${BUILD_NUMBER}

3. 字符串常用操作​

def name = 'Jenkins Pipeline'

name.length() // 16
name.toUpperCase() // JENKINS PIPELINE
name.contains('Pipeline') // true
name.startsWith('Jen') // true
name.split(' ') // ['Jenkins', 'Pipeline']
name.replace('Pipeline', 'CI') // Jenkins CI

// 插值与拼接
def app = 'backend'
def version = '1.2.3'
def image = "registry.com/${app}:v${version}" // registry.com/backend:v1.2.3

// 多行字符串 + stripIndent
def script = '''line1
line2
line3'''.stripIndent()

4. 集合:List 与 Map​

4.1 List​

def envs = ['dev', 'staging', 'prod']

envs.size() // 3
envs[0] // dev
envs.add('test') // 追加
envs.contains('prod') // true

// 遍历
envs.each { e -> echo "环境: ${e}" }
envs.each { echo "环境: ${it}" } // it 是默认参数名

// 常见转换
def upper = envs.collect { it.toUpperCase() } // ['DEV','STAGING','PROD']
def prod = envs.find { it == 'prod' } // prod
def filtered = envs.findAll { it != 'test' } // 过滤

4.2 Map​

def cfg = [name: 'backend', replicas: 3, labels: ['app', 'web']]

cfg.name // backend(点访问)
cfg['replicas'] // 3
cfg.put('env', 'prod') // 添加键
cfg.containsKey('name') // true

// 遍历
cfg.each { key, value -> echo "${key}=${value}" }

// Map 转 JSON
import groovy.json.JsonOutput
def json = JsonOutput.toJson(cfg)
echo json
it 是闭包的默认参数

each { it -> ... } 可以省略参数名写成 each { ... },it 自动代表当前元素。这是 Groovy 最常见的写法。

5. 闭包(Closure)​

闭包是 Groovy 的灵魂,流水线里的 steps { sh '...' } 本质就是传闭包:

// 定义闭包:一段可以传递的代码块
def greet = { name -> "Hello, ${name}!" }
def greet2 = { "Hello, ${it}!" } // 单参数可省略声明

println greet('Jenkins') // Hello, Jenkins!

// 闭包作参数传递
def runStep = { cmd -> sh "${cmd}" }
runStep('mvn clean package')

// 常用内建闭包用法
[1,2,3].each { println it }
[1,2,3].collect { it * 2 } // [2,4,6]
[1,2,3,4].findAll { it % 2 == 0 } // [2,4]

在流水线中,stage, steps, post 等块都接受闭包:

// 声明式:steps 块本质是闭包
steps {
echo 'hello'
sh 'pwd'
}

6. 条件与循环​

// if/else
def result = 'failed'
if (result == 'success') {
echo '成功'
} else if (result == 'unstable') {
echo '不稳定'
} else {
echo '失败'
}

// switch(支持通配)
switch (result) {
case 'success': echo '✅'; break
case ~/fail.*/: echo '❌'; break // 正则匹配
default: echo '未知'
}

// 循环
for (int i = 0; i < 3; i++) { echo "i=${i}" }
for (env in ['dev', 'prod']) { echo env }
10.times { echo it } // 0..9
空值判断

Groovy 的真值判断很宽松:null、空字符串、空 List 都视为 false。

if (params.IMAGE_TAG) { // 空字符串/null 时跳过
echo params.IMAGE_TAG
}

7. 方法与异常​

7.1 定义方法​

// 简单方法
def buildImage(String image, String tag) {
echo "构建 ${image}:${tag}"
sh "docker build -t ${image}:${tag} ."
}

buildImage('registry/app', "v${BUILD_NUMBER}")

// 默认参数
def notify(String msg, String channel = 'default') {
echo "[${channel}] ${msg}"
}
notify('构建完成')

7.2 异常处理​

def riskyDeploy() {
try {
sh 'kubectl apply -f deploy/'
echo '部署成功'
} catch (Exception e) {
echo "部署失败: ${e.message}"
sh 'kubectl rollout undo deployment/backend'
throw e // 重新抛出,让流水线失败
} finally {
echo '清理临时文件'
}
}
Groovy 的异常是运行时抛出的

流水线里 sh 命令返回非零退出码时,Groovy 会抛出异常(构建失败)。用 catchError(见第 7 篇)或 try/catch 捕获后,可以选择继续或回滚。

8. 在流水线中使用 Groovy​

8.1 声明式里的 script 块​

声明式流水线默认不能随意写 Groovy 逻辑,需用 script {} 包裹:

stage('动态处理') {
steps {
script {
def services = ['api', 'web', 'worker']
def tags = services.collect { "${it}:v${BUILD_NUMBER}" }
echo "本次发布的镜像: ${tags.join(', ')}"
}
}
}

8.2 环境变量与 Groovy 变量​

pipeline {
environment {
REGISTRY = 'registry.company.com'
}
stages {
stage('构建') {
steps {
script {
// env.XXX 读环境变量
def app = "service-${env.BRANCH_NAME ?: 'main'}"
def image = "${env.REGISTRY}/${app}:v${env.BUILD_NUMBER}"
echo image
}
}
}
}
}

8.3 常用 Groovy 技巧速查​

需求写法
空值兜底def tag = params.TAG ?: "v${BUILD_NUMBER}"
字符串判空if (!text) echo '空'
列表拼接为字符串[1,2,3].join('-')
数组转 Set[1,2,2,3].toSet()
正则匹配if (env.GIT_BRANCH ==~ /release-.*/)
随机/唯一UUID.randomUUID().toString()

9. 练习与验收​

练习 A

  1. 在声明式流水线的 script {} 块里,用 def 定义一个 List 和一个 Map,遍历打印
  2. 用单引号和双引号各拼接一个字符串,验证插值差异
  3. 写一个方法 sum(a, b),在 script 中调用

练习 B

  1. 用 collect + findAll 对一个环境列表做过滤转换
  2. 用 try/catch/finally 包裹一个会失败的 sh 命令,验证异常捕获
  3. 用 ?: 空值兜底给参数设置默认值

验收清单

  • 知道单引号字符串不做插值、双引号才做
  • 会用 def 声明变量,理解 List / Map 的基本操作
  • 会用闭包和 it、each / collect / findAll
  • 会在声明式流水线的 script {} 块中写 Groovy 逻辑
  • 会用 try/catch 处理异常,?: 做空值兜底
  • 能读懂 Groovy 报错并定位到流水线对应位置

下一篇:声明式流水线基础。