Groovy 基础
Jenkins 的流水线(无论声明式还是脚本式)都是基于 Groovy 的语言。写 Jenkinsfile 并不需要成为 Groovy 专家,但掌握核心语法能帮你写出更简洁、可维护的流水线,并在排障时读懂报错。
本篇目标:掌握变量、字符串、集合、闭包、方法定义、异常处理等流水线高频用到的 Groovy 语法。
1. Groovy 与 Java 的关系
Groovy 是运行在 JVM 上的动态语言,语法对 Java 几乎完全兼容,但更简洁:
| Java | Groovy |
|---|---|
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
- 在声明式流水线的
script {}块里,用def定义一个 List 和一个 Map,遍历打印 - 用单引号和双引号各拼接一个字符串,验证插值差异
- 写一个方法
sum(a, b),在script中调用
练习 B
- 用
collect+findAll对一个环境列表做过滤转换 - 用
try/catch/finally包裹一个会失败的sh命令,验证异常捕获 - 用
?:空值兜底给参数设置默认值
验收清单
- 知道单引号字符串不做插值、双引号才做
- 会用
def声明变量,理解 List / Map 的基本操作 - 会用闭包和
it、each/collect/findAll - 会在声明式流水线的
script {}块中写 Groovy 逻辑 - 会用
try/catch处理异常,?:做空值兜底 - 能读懂 Groovy 报错并定位到流水线对应位置
下一篇:声明式流水线基础。