快速上手Groovy

文章目录

开始

深入理解Groovy已经介绍了一下Groovy最重要的内容。

本来不打算单独写Groovy基本内容的,但是写Jmeter脚本的内容补充说明Groovy的东西,发现东西还比较多,就单独拿出来讲讲。

这里只是选择了最常用的内容简单说明,主要就是为了快速上手,能编写简单的脚本,想要深入理解,请看:官方文档

常用语法

Groovy只是对Java的增强,当Java代码来写就可以。

增强了什么呢?

答案是脚本化,例如,可以像java一样指定类型,例如:

  1. int
  2. long
  3. double
  4. float
  5. boolean
  6. byte
  7. char
  8. String

也可以使用通通使用def关键字。

groovy 复制代码
// 指定类型
int a = 10

println a
// 弱类型定义,使用def关键字,自动判断b是什么类型
def b = 5

println b

// b变成了字符串类型
b = 'abc'
println(b)

// a就不能改变类型
// a = 'abc'

为什么def类型可以改变类型呢?

因为Groovy将他们通通编译为Object类型了。

字符串增强

Groovy中的字符串有下面几种:

  • 单引号:普通字符串,不插值,对应java中的String类型。
  • 双引号:可插值字符串,单行,GString类型,Groovy自己实现的org.codehaus.groovy.runtime.GStringImpl
  • 三个单引号:普通多行字符串,不插值,对应java中的String类型,多行,保留字符串中的格式:换行和缩进
  • 三个双引号:可插值多行字符串,插值,GString类型,多行,保留字符串中的格式:包括换行和缩进
Groovy 复制代码
def name = 'tim'
println("单引号-----")
println(name)

// hello tim
def hello = "hello $name"
println("双引号-----")
println(hello)

// $name会原样输出
def content = '''
line one
\n
line two
$name
'''
println("3个单引号-----")
println(content)

// 会计算${name.toUpperCase()}!表达式
def lines = """
line one
\n
line two
${name.toUpperCase()}!
"""
println("3个双引号-----")
println(lines)

列表与数组

class java.util.ArrayList

Groovy 复制代码
// Java的定义方式
def listA = new ArrayList()
println listA.class

//定义一个空的列表,直接可以当Java的ArrayList用
def listB = []
println listB.class

//定义一个非空的列表
def listC = [1,2,3,4]
println listC.class

注意上面的不是数组,数组是下面这样的:

Groovy 复制代码
// 使用as关键字定义数组
def arrayA = [1,2,3,4] as int[]
println arrayA.class

// 使用强类型的定义方式
int[] arrayB = [1,2,3]
println arrayB.class

对于列表和数组的操作,你能想到的基本都能直接用,Groovy基本都实现了,都在DefaultGroovyMethods类中。

如果觉得Groovy给的不够,还可以通过stream方法转为Stream,然后调用stream的各种方法。

groovy 复制代码
def lista = [-7,5,-3,9,4,0]
// 排序
lista.sort()
println lista


def listb = [-7,5,-3,9,4,0]

// 自定义排序规则,按绝对值大小排序
listb.sort {
    a,b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? 1 : -1
}

println list

def stringList = ['a','abc','hello','groovy']

// 按照字符串的长度大小排序
stringList.sort {
    it -> return it.size()
}

println stringList

// 使用find查找列表中的第一个偶数
def list = [-7,5,-3,9,4,0,6]
int result = list.find {
    temp -> return temp % 2 == 0
}

println result

// 使用findAll查找列表中所有小于0的数
def list = [-7,5,-3,9,4,0,6]
def result = list.findAll {
    temp -> return temp < 0
}

println result

// 使用count统计列表中偶数的个数
def list = [-7,5,-3,9,4,0,6]

int number = list.count {
    return it % 2 == 0
}

println number

Map

Groovy的Map字面量比较奇怪,看起来是数组,但是是列表,使用的是java.util.Map,实现类默认使用的是java.util.LinkedHashMap

但是Groovy提供了一个像JavaScript的操作,可以直接点(.)来访问Map的元素。

groovy 复制代码
def persons = [tom: '北京', tim: '上海', allen: '成都']
//class java.util.LinkedHashMap
println(persons.getClass())

// 北京
println persons['tom']
//上海
println persons.get('tim')
//null
println persons.wendy
// 成都
println persons.allen

// 添加元素
persons.bob = '重庆'
//[tom:北京, tim:上海, allen:成都, bob:重庆]
println persons

persons.complex = [a: 1, b: 2]
//[tom:北京, tim:上海, allen:成都, bob:重庆, complex:[a:1, b:2]]
println persons

Map的方法:

groovy 复制代码
def students = ['tom':20,'tim':25,'allen':60,'bob':30]

// 使用对象
students.each {
    def student -> println "key is:" + student.key + "  value is:" + student.value
}

// 带索引
students.eachWithIndex { def student, int index ->
    println "key=${student.key}  value=${student.value} index=${index}"
}


// 使用key value参数
students.each {
    key, value -> println "key=${key},value=${value}"
}

// 查找第一个
def result = students.find {
    student -> return student.value > 60
}

// 查找所有
def result = students.findAll {
    student -> return student.value > 60
}

// 统计
def number = students.count {
    student -> student.value > 60
}

// 查找名字
def names = students.findAll {
    student -> student.value > 60
}.collect {
    return it.key
}

range

Groovy提供了一个像Python一样的range操作(...)

groovy 复制代码
// 范围的定义[0,10]
def range = 1..10

//第一个元素的值
println range[0]

 //是否包含7
println range.contains(7)

//范围的起始值
println range.from

//范围的结束值
println range.to

println (0..5).collect() == [0, 1, 2, 3, 4, 5]

// 相当于左闭右开区间[0,5)
println (0..<5).collect() == [0, 1, 2, 3, 4]

// Range实际上是List接口的实现
println (0..5) instanceof List
println (0..5).size() == 6

//也可以是字符类型
println ('a'..'d').collect() == ['a','b','c','d']


for (x in 1..10) {
    println x
}

('a'..'z').each {
    println it
}

def age = 25;
switch (age) {
    case 0..17:
        println '未成年'
        break
    case 18..30:
        println '青年'
        break
    case 31..50:
        println '中年'
        break
    default:
        println '老年'
}

正则表达式增强

Groovy的正则简写

groovy添加了对正则表达式的快捷操作:

  1. //类似于JavaScript的正则方式,/正则表达式/可以不用转义
  2. ~regexString:等价于Pattern.compile(regexString)
  3. content =~ regexString : 等价于Pattern pattern = Pattern.compile(regexString);Matcher matcher = pattern.matcher(content);
  4. content ==~ regexString : 等价于Pattern pattern = Pattern.compile(regexString);Matcher matcher = pattern.matcher(content);boolean matches = matcher.matches();
groovy 复制代码
// regexString只是一个普通字符串,使用//是可以不用转义
def regexString = /\d{3}-\d{4}-\d{4}/
//class java.lang.String
println(regexString.getClass())

// 使用~相当于对字符串执行了Pattern.compile()操作,返回的是一个Pattern对象
def p2 = ~'he*llo'
// 相当于Java中执行了:Pattern.compile("he*llo")
//class java.util.regex.Pattern
println(p2.getClass())


def str = "手机号是:138-1234-5678"
// str =~ regexString 相当于 pattern.matcher(str)
def matcher = str =~ /\d{3}-\d{4}-\d{4}/
// 相当于Java中执行了下面2步
//Pattern pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}");
//Matcher matcher = pattern.matcher("手机号是:138-1234-5678");
//class java.util.regex.Matcher
println(matcher.getClass())


def result = "hello" ==~ "he*llo"
// 相当于Java中执行了下面3步
//Pattern pattern = Pattern.compile("he*llo");
//Matcher matcher = pattern.matcher("hello");
//boolean matches = matcher.matches();
//class java.lang.Boolean
println result.getClass()
print result

正则转义

/regex/是一个很棒的设计,可以避免转义的问题,转义问题很常见,但是有一些会比较隐蔽。

例如,按|分割字符串:"007|13746|2050|2107N|0|01000111000";

很容易就写成了下面这样:

java 复制代码
@Test
public void split(){
    String originalString = "007|13746|2050|2107N|0|01000111000";
    String[] parts = originalString.split("|");
    // [0, 0, 7, |, 1, 3, 7, 4, 6, |, 2, 0, 5, 0, |, 2, 1, 0, 7, N, |, 0, |, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0]
    System.out.println(Arrays.toString(parts));
}

但实际输出和预期结果不一样,因为|是元字符,表示或的意思,所以需要转义。

java 复制代码
@Test
public void split(){
    String originalString = "007|13746|2050|2107N|0|01000111000";
    String[] parts = originalString.split("\\|");
    // [007, 13746, 2050, 2107N, 0, 01000111000]
    System.out.println(Arrays.toString(parts));
}

下面是一些需要转义的元字符:

字符 名称 正则含义 转义写法
\ 反斜杠 转义字符本身 \\\\
` ` 竖线 逻辑"或"
. 点号 匹配任意字符 \\.
* 星号 前一个字符0次或多次 \\*
+ 加号 前一个字符1次或多次 \\+
? 问号 前一个字符0次或1次 \\?
^ 插入符 字符串开始位置 \\^
$ 美元符 字符串结束位置 \\$
() 圆括号 分组捕获 \\(\\)
[] 方括号 字符类 \\[\\]
{} 花括号 量词范围 \\{\\}

我们可以看到,Java中的正则转义非常麻烦,一个\本身,需要4个\。

Java的Pattern提供了一个quote方法,帮我自动转义:

java 复制代码
@Test
public void split(){
    String originalString = "007|13746|2050|2107N|0|01000111000";
    String quote = Pattern.quote("|");
    // \Q|\E
    System.out.println(quote);
    String[] parts = originalString.split(quote);
    // [007, 13746, 2050, 2107N, 0, 01000111000]
    System.out.println(Arrays.toString(parts));
}

Groovy正则增强

有了Matcher,就可以使用Java的方式操作了,当然,Groovy还有很多语法糖和增强:

例如,可以直接收集到列表中:

groovy 复制代码
def str = "张三:13812345678,李四:13987654321,王五:13711112222"
def matcher = str =~ /\d{11}/

def phones = matcher.collect()
//[13812345678, 13987654321, 13711112222]
println phones

结合findAll之类的方法:

groovy 复制代码
def list = ["abc123", "def456", "ghi", "jdk789"]

def filtered = list.findAll { it =~ /\d/ }
//[abc123, def456, jdk789]
println filtered

很神奇是吧,it=~/\d/明明是个Matcher,findAll需要一个bool值,怎么就没有报错呢。

因为闭包被包装成了BooleanClosureWrapper test = new BooleanClosureWrapper(closure);

最终调用了StringGroovyMethods的asBoolean方法:

Groovy 复制代码
public static boolean asBoolean(final Matcher matcher) {
    if (matcher != null) {
        RegexSupport.setLastMatcher(matcher);
        return matcher.find(0); //GROOVY-8855
    }
    return false;
}

常用正则操作

分组匹配获取

groovy 复制代码
def str = "订单号:ORDER_12345,金额:99元,订单号:ORDER_8888,金额:88元"
def matcher = str =~ /ORDER_(\d+)/

while (matcher.find()) {
    println "匹配的订单号:${matcher.group()}"
    println "订单号数字:${matcher.group(1)}"
    println "-" * 20
}

// 匹配的订单号:ORDER_12345
// 订单号数字:12345
// --------------------
// 匹配的订单号:ORDER_8888
// 订单号数字:8888
// --------------------

替换第1个、全部、匹配

Groovy 复制代码
def str = "张三123,李四456,王五789"
//替换所有
// 张三,李四,王五
def result = str.replaceAll(/\d+/, "")
println result

// 替换第一个
// 张三***,李四456,王五789
result = str.replaceFirst(/\d+/, "***")
println result

// 替换匹配,注意单引号,Groovy中的双引号中的$有特殊作用
str = "ORDER_12345, ORDER_67890"
result = str.replaceAll(/ORDER_(\d+)/, '订单$1')
// 如果要使用双引号,要转义
//result = str.replaceAll(/ORDER_(\d+)/, "订单\$1")
// 订单12345, 订单67890,引用了替换都的数字分组
println result

字符串分割

groovy 复制代码
def str = "张三,李四;王五|赵六"
def arr = str.split(/[,;|]/)
//[张三, 李四, 王五, 赵六]
println arr

文件

读取所有内容

groovy 复制代码
def readFile = new File("F:\\tmp.json");
// 使用getText()方法读取文件内容
println  readFile.getText();

// 使用text属性读取文件内容
println(readFile.text)

按行处理

groovy 复制代码
new File('F:\\Test.java').eachLine { line ->
    println line
}

println("----------------------------")

def lines = new File('F:\\Test.java').readLines()
lines.each { line ->
    println line
}

写文件

groovy 复制代码
def file = new File("F://tmp//example.txt")
file.withWriter { writer ->
    writer.writeLine("Hello, Groovy!")
    writer.writeLine("这是第二行文本。")
}

追加

groovy 复制代码
def file = new File("F://tmp//example.txt")
file.withWriterAppend { writer ->
    writer.writeLine("追加的文本内容")
}

json

groovy 复制代码
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
def updateTestJson(){
    def jsonFile=new File("src/main/resources/test.json")
    def jsonStr=jsonFile.text

    def slurper = new JsonSlurper()
    def jsonOutput=new JsonOutput()
    
    //字符串转json对象
    def jsonObj=slurper.parseText(jsonStr)
    jsonObj.name="tim"
    
    //对象转字符串
    def finalJsonStr=jsonOutput.toJson(jsonObj)
    
    jsonFile.withWriter ('utf-8'){writer->
        writer.write finalJsonStr
    }
}

jackson

不习惯Groovy的自带的json,可以使用jackson,Jmeter默认就添加Jackson依赖:

Groovy 复制代码
import com.fasterxml.jackson.annotation.JsonFormat
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.type.CollectionType

class User {
    /**
     * id
     * 不JSON序列化id
     */
    @JsonIgnore
    private Integer id;
    /**
     * 昵称
     */
    private String name;
    /**
     * 密码
     * 序列化testPwd属性为pwd
     */
    @JsonProperty("pwd")
    private String testPwd;

    /**
     * 注册时间
     * 格式化日期属性
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date time;

    Integer getId() {
        return id
    }

    void setId(Integer id) {
        this.id = id
    }

    String getName() {
        return name
    }

    void setName(String name) {
        this.name = name
    }

    String getTestPwd() {
        return testPwd
    }

    void setTestPwd(String testPwd) {
        this.testPwd = testPwd
    }

    Date getTime() {
        return time
    }

    void setTime(Date time) {
        this.time = time
    }
}

def filename = "F:\\tmp\\ok-user.json";

static def write(String name) throws IOException {
    LinkedList<User> users = new LinkedList<>();
    for (int i = 0; i < 20; i++) {
        User user = new User();
        user.setId(i);
        user.setName("测试" + i);
        user.setTestPwd("123456_" + i);
        user.setTime(new Date());
        users.add(user);
    }

    ObjectMapper objectMapper = new ObjectMapper();
    File resultFile = new File(name);
    objectMapper.writeValue(resultFile, users);
}

static def read(String name) {
    ObjectMapper objectMapper = new ObjectMapper();
    CollectionType collectionType = objectMapper.getTypeFactory()
            .constructCollectionType(List.class, User.class);
    File file = new File(name);
    List<User> users = objectMapper.readValue(file, collectionType);
    for (User user : users) {
        System.out.println("Name: " + user.getName() + ", Id: " + user.getId()
                + ", Pwd: " + user.getTestPwd() + ", Time: " + user.getTime());
    }
}

write(filename)

read(filename)
相关推荐
hgz07101 天前
Nginx负载均衡策略详解与Session一致性解决方案
java·jmeter
write19941 天前
jmeter环境配置
jmeter
trayvontang1 天前
Jmeter脚本原理与实例
jmeter
hgz07102 天前
企业级Nginx反向代理与负载均衡实战
java·jmeter
无名小卒Rain2 天前
Jmeter性能测试-用JSON表达式获取关联参数
jmeter·压力测试·性能测试
无名小卒Rain2 天前
Jmeter性能测试-用正则表达式取关联参数:单个正则模版和多个正则模版
jmeter·压力测试·性能测试
带刺的坐椅3 天前
Liquor(Java 脚本)替代 Groovy 作脚本引擎的可行性分析
java·groovy·liquor
write19944 天前
02 jmeter常用组件
jmeter
天才测试猿4 天前
Jmeter 命令行压测&生成HTML测试报告
自动化测试·软件测试·python·测试工具·jmeter·职场和发展·jenkins