1.ConfigSlurper

ConfigSlurper是一个实用程序类,用于读取以 Groovy 脚本形式定义的配置文件。就像 Java*.properties文件的情况一样,ConfigSlurper允许使用点表示法。但此外,它允许闭包范围的配置值和任意对象类型。

  1. // 点符号的使用
  2. // 使用闭包范围作为点符号的替代品
  3. def config = new ConfigSlurper().parse('''
  4. app.date = new Date()
  5. app.age = 42
  6. app {
  7. name = "Test${42}"
  8. }
  9. ''')
  10. assert config.app.date instanceof Date
  11. assert config.app.age == 42
  12. assert config.app.name == 'Test42'

从上面的示例中可以看出,该parse方法可用于检索groovy.util.ConfigObject实例。这 ConfigObject是一个专门的java.util.Map实现,它要么返回配置的值,要么返回一个新ConfigObject实例,但从不null

  1. def config = new ConfigSlurper().parse('''
  2. app.date = new Date()
  3. app.age = 42
  4. app.name = "Test${42}"
  5. ''')
  6. // config.test尚未指定
  7. // 它在被调用时返回一个ConfigObject
  8. assert config.test != null

如果点是配置变量名称的一部分,则可以使用单引号或双引号对其进行转义。

  1. def config = new ConfigSlurper().parse('''
  2. app."person.age" = 42
  3. ''')
  4. assert config.app."person.age" == 42

此外,ConfigSlurper还支持environments. 该environments方法可用于移交一个闭包实例,该实例本身可能由几个部分组成。假设我们想为开发环境创建一个特定的配置值。创建ConfigSlurper实例时,我们可以使用ConfigSlurper(String)构造函数来指定目标环境。

  1. def config = new ConfigSlurper('development').parse('''
  2. environments {
  3. development {
  4. app.port = 8080
  5. }
  6. test {
  7. app.port = 8082
  8. }
  9. production {
  10. app.port = 80
  11. }
  12. }
  13. ''')
  14. assert config.app.port == 8080

:::info ConfigSlurper环境不限于任何特定 的环境名称。它仅取决于 ConfigSlurper客户端代码支持和相应解释的值。 ::: 该environments方法是内置的,但该registerConditionalBlock方法可用于注册除名称之外的其他方法名称environments

  1. def slurper = new ConfigSlurper()
  2. // 一旦新块被注册ConfigSlurper就可以解析它。
  3. slurper.registerConditionalBlock('myProject', 'developers')
  4. def config = slurper.parse('''
  5. sendMail = true
  6. myProject {
  7. developers {
  8. sendMail = false
  9. }
  10. }
  11. ''')
  12. assert !config.sendMail

出于Java集成目的,可以使用toProperties方法将ConfigObject转换为java.util.Properties对象,该对象可能存储在*.properties文本文件。但是请注意,在将配置值添加到新创建的Properties实例中时,配置值会转换为String实例。

  1. def config = new ConfigSlurper().parse('''
  2. app.date = new Date()
  3. app.age = 42
  4. app {
  5. name = "Test${42}"
  6. }
  7. ''')
  8. def properties = config.toProperties()
  9. assert properties."app.date" instanceof String
  10. assert properties."app.age" == '42'
  11. assert properties."app.name" == 'Test42'

2.展开

Expando类可用于创建可动态扩展的对象。尽管它的名字叫ExpandoMetaClass,但它下面并没有使用ExpandoMetaClass。每个Expando对象表示一个独立的、动态构建的实例,可以在运行时使用属性(或方法)进行扩展。

  1. def expando = new Expando()
  2. expando.name = 'John'
  3. assert expando.name == 'John'

当动态属性注册Closure代码块时,会出现一种特殊情况。一旦注册,就可以像方法调用一样调用它。

  1. def expando = new Expando()
  2. expando.toString = { -> 'John' }
  3. expando.say = { String s -> "John says: ${s}" }
  4. assert expando as String == 'John'
  5. assert expando.say('Hi') == 'John says: Hi'

3.可观察的List、Map和Set

Groovy附带了可观察List、Map和Set。当添加、删除或更改元素时,这些集合中的每一个都会触发java.beans.PropertyChangeEvent事件。请注意,PropertyChangeEvent不仅表示某个事件已经发生,而且还包含有关属性名称和某个属性已更改为的旧/新值的信息。
根据发生的更改类型,可观察集合可能会触发更专门的PropertyChangeEvent类型。例如,将元素添加到可观察列表会触发ObservableList.ElementAddedEvent事件。

  1. // 声明一个PropertyChangeEventListener正在捕获触发事件的
  2. def event
  3. def listener = {
  4. // ObservableList.ElementEvent及其后代类型与此侦听器相关
  5. if (it instanceof ObservableList.ElementEvent) {
  6. event = it
  7. }
  8. } as PropertyChangeListener
  9. // 注册监听器
  10. def observable = [1, 2, 3] as ObservableList
  11. // 从给定列表创建一个ObservableList
  12. observable.addPropertyChangeListener(listener)
  13. // 触发ObservableList.ElementAddedEvent事件
  14. observable.add 42
  15. assert event instanceof ObservableList.ElementAddedEvent
  16. def elementAddedEvent = event as ObservableList.ElementAddedEvent
  17. assert elementAddedEvent.changeType == ObservableList.ChangeType.ADDED
  18. assert elementAddedEvent.index == 3
  19. assert elementAddedEvent.oldValue == null
  20. assert elementAddedEvent.newValue == 42

:::info 请注意,添加元素实际上会导致触发两个事件。第一个是类型ObservableList.ElementAddedEvent,第二个是PropertyChangeEvent通知听众关于属性变化的长度size。 :::

ObservableList.ElementClearedEvent事件类型是另一个有趣的类型。每当删除多个元素时,例如在调用 时clear(),它都会保存从列表中删除的元素。

  1. def event
  2. def listener = {
  3. if (it instanceof ObservableList.ElementEvent) {
  4. event = it
  5. }
  6. } as PropertyChangeListener
  7. def observable = [1, 2, 3] as ObservableList
  8. observable.addPropertyChangeListener(listener)
  9. observable.clear()
  10. assert event instanceof ObservableList.ElementClearedEvent
  11. def elementClearedEvent = event as ObservableList.ElementClearedEvent
  12. assert elementClearedEvent.values == [1, 2, 3]
  13. assert observable.size() == 0

要了解所有支持的事件类型,建议读者查看 JavaDoc 文档或使用中的可观察集合的源代码。
ObservableMap并带有与我们在本节ObservableSet中看到的相同的概念。ObservableList