打包:外部化的应用程序配置


Ratpack有非常有用的方法在我们的应用程序中应用应用程序配置。我们可以从不同格式的文件中读取配置属性,如JSON、YAML和Java属性,并且可以从不同的位置读取文件,如类路径或文件系统。我们还可以通过命令行上的Java系统属性来设置配置属性,或者使用环境变量。

我们使用ratpack.config.ConfigData用静态of方法向我们的应用程序添加配置属性。我们为of方法来构建我们的配置。在这里,我们指定外部文件、位置和我们希望为应用程序包含的其他配置选项。如果在多个配置源中定义了相同的配置属性,Ratpack将应用最后发现的值。例如,通过这种方式,我们可以提供默认值,并且如果我们最后应用环境变量,允许它们被环境变量覆盖。

为了使用收集的值,我们使用get的方法ConfigData实例。我们可以将配置属性应用于配置类的属性,然后自动实例化该配置类。我们将它添加到注册表中,这样我们就可以在我们的应用程序中进一步使用配置属性。

在下面的Ratpack应用程序示例中,我们使用不同的方法来应用配置属性。它的灵感来自Spring Boot如何读取和应用外部化的配置属性。首先我们使用一个简单的Map使用默认值,然后在类路径中扫描具有以下名称的文件application.yml,application.jsonapplication.properties在类路径的根目录或config包裹。接下来,在相对于应用程序启动位置的文件系统上搜索相同的文件名。下一个Java系统属性从sample.应用于配置。最后,环境变量从SAMPLE_被解释为配置属性。

让我们从简单的配置类和属性开始,我们希望通过Ratpack的配置功能来设置:

// File: src/main/groovy/com/mrhaki/SampleConfig.groovy
package com.mrhaki

/**
 * Configuration properties for our application.
 */
class SampleConfig {

    /**
     * URL for external service to invoke with HTTP client.
     */
    String externalServiceUrl

    /**
     * URI to access the Mongo database.
     */
    String mongoUri

    /**
     * Indicate if we need to use a HTTP proxy.
     */
    boolean useProxy

    /**
     * Simple message
     */
    String message

}

接下来我们有一个非常简单的Ratpack应用程序。这里我们使用ConfigData.of有许多帮助器方法可以从不同的来源读入配置属性:

// File: src/ratpack/Ratpack.groovy
import com.google.common.io.Resources
import com.mrhaki.SampleConfig
import ratpack.config.ConfigData
import ratpack.config.ConfigDataBuilder

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack

ratpack {

    bindings {
        final ConfigData configData = ConfigData.of { builder ->
            // Set default value, can be overridden by
            // configuration further down the chain.
            // The map must have String values.
            builder.props(['app.useProxy': Boolean.TRUE.toString()])

            loadExternalConfiguration(builder)

            // Look for system properties starting with
            // sample. to set or override configuration properties.
            builder.sysProps('sample.')

            // Look for environment variables starting
            // with SAMPLE_ to set or override configuration properties.
            builder.env('SAMPLE_')

            builder.build()
        }

        // Assign all configuration properties from the /app node
        // to the properties in the SampleConfig class.
        bindInstance(SampleConfig, configData.get('/app', SampleConfig))
    }

    handlers {
        get('configprops') { SampleConfig config ->
            render(prettyPrint(toJson(config)))
        }
    }

}

private void loadExternalConfiguration(final ConfigDataBuilder configDataBuilder) {

    final List<String> configurationLocations =
            ['application.yml',
             'application.json',
             'application.properties',
             'config/application.yml',
             'config/application.json',
             'config/application.properties']

    configurationLocations.each { configurationLocation ->
        loadClasspathConfiguration(configDataBuilder, configurationLocation)
    }

    configurationLocations.each { configurationLocation ->
        loadFileSystemConfiguration(configDataBuilder, configurationLocation)
    }
}

private void loadClasspathConfiguration(
        final ConfigDataBuilder configDataBuilder,
        final String configurationName) {

    try {
        final URL configurationResource = Resources.getResource(configurationName)
        switch (configurationName) {
            case yaml():
                configDataBuilder.yaml(configurationResource)
                break
            case json():
                configDataBuilder.json(configurationResource)
                break
            case properties():
                configDataBuilder.props(configurationResource)
                break
            default:
                break
        }
    } catch (IllegalArgumentException ignore) {
        // Configuration not found.
    }

}

private void loadFileSystemConfiguration(
        final ConfigDataBuilder configDataBuilder,
        final String configurationFilename) {

    final Path configurationPath = Paths.get(configurationFilename)
    if (Files.exists(configurationPath)) {
        switch (configurationFilename) {
            case yaml():
                configDataBuilder.yaml(configurationPath)
                break
            case json():
                configDataBuilder.json(configurationPath)
                break
            case properties():
                configDataBuilder.props(configurationPath)
                break
            default:
                break
        }
    }
}

private def yaml() {
    return hasExtension('yml')
}

private def json() {
    return hasExtension('json')
}

private def properties() {
    return hasExtension('properties')
}

private def hasExtension(final String extension) {
    return { filename -> filename ==~ /.*\.${extension}$/ }
}

接下来,我们创建一些外部配置文件:

# File: src/ratpack/application.yml
---
app:
  mongoUri: mongodb://mongo:27017/test
# File: src/ratpack/application.properties
app.externalServiceUrl = http://remote:9000/api
app.message = Ratpack rules!

让我们运行应用程序,并查看configprops端点:

$ http localhost:5050/configprops
...
{
    "externalServiceUrl": "http://remote:9000/api",
    "useProxy": true,
    "message": "Ratpack rules!",
    "mongoUri": "mongodb://mongo:27017/test"
}

接下来,我们停止应用程序,用Java系统属性启动它-Dsample.app.useProxy=false和环境变量SAMPLE_APP__MESSAGE='Ratpack rocks!'。我们检查了configprops再次结束:

$ http localhost:5050/configprops
...
{
    "externalServiceUrl": "http://remote:9000/api",
    "useProxy": false,
    "message": "Ratpack rocks!",
    "mongoUri": "mongodb://mongo:27017/test"
}

用Ratpack 1.0.0编写。

这是第1000篇博客帖子。我在博客上写了很多不同的主题,从Apache Cocoon、Netbeans开始,接着是Groovy和Groovy相关的技术,比如Grails、Gradle。也是关于其他开发者主题,比如Asciidoctor等等。我希望你喜欢第一个1000,因为我还没有完成,我会继续写更多关于伟大技术的博客。