Java configuration tool: super detailed explanation of typesafe config usage documentation

1. Overview of typesafe config

1. Official website

https://github.com/lightbend/config/tree/v1.4.2#essential-information

2. Advantages

  • Pure java implementation, no other dependencies.
  • Three file formats are supported: Java properties, JSON, and a human-friendly superset of JSON.
  • Merge multiple files of all formats.
  • Can be loaded from a file, URL or classpath.
  • Good support for "nesting" (treating any subtree of a configuration as the entire configuration).
  • Users can override the configuration with Java system properties, java -Dmyapp.foo.bar=10.
  • Supports application.confconfiguration of the application and its frameworks and libraries from a single file such as
  • Parse duration and size settings, "512k" or "10 seconds".
  • Converts the type, so if you ask for a boolean and the value is the string "yes", or you ask for a float and the value is an integer, it will figure it out.
  • Immutable Configinstance based API for thread safety and easy reasoning about configuration transformations.
  • Extensive test coverage.

Notice! This library is limited to configuration files. If you want to load config from a database or elsewhere, you need to write some custom code. The library has good support for merging configurations, so if you build one from a custom source, it's easy to incorporate it.

3. JSON superset feature (HOCON)

The JSON superset is called "Human-Optimized Configuration Object Notation" or HOCON, and files use the suffix .conf. After processing a .conf file, the result is always a JSON tree.

  • Note: #or //.
  • Allows to omit {}surrounding root object
  • =Equivalent to:
  • Objects before are allowed to be omitted =or :e.g.:foo { a : 42 }
  • Commas are allowed to be omitted as long as there is a newline
  • Allow trailing commas after the last element of objects and arrays
  • Allow unquoted strings for keys and values
  • Unquoted keys can use dot notation for nested objects, foo.bar=42denotingfoo { bar : 42 }
  • Duplicate keys are allowed; later values ​​override earlier values, except for object value keys where two objects are recursively merged.
  • The include feature merges the root object from another file into the current object, so the keys from are merged into foo { include "bar.json" }the objectbar.jsonfoo
  • An include without a file extension includes any of the following:.conf, .json, .properties
  • You can include files, URLs, or classpath resources. Use include url("http://example.com")the or file()or classpath()syntax to coerce types, or just use to include "whatever"let the library do what you might want (note: url()/file()/classpath()the syntax is not supported in Play/Akka 2.0, only in later versions.)
  • replace: foo : ${a.b}set the key foo to the same value as the b field in the a object
  • Replace the concatenation into an unquoted string:foo : the quick ${colors.fox} jumped
  • If the substitutions are not resolved in the configuration itself, they fall back to environment variables, so ${HOME}will work as you'd expect. Also, most configurations incorporate system properties, so you can use ${user.home}.
  • Substitutions usually cause errors if the conversion fails, but there is a syntax ${?a.b}that allows configuration to be missing.
  • +=The syntax for appending elements to an array,path += "/bin"
  • Multiline strings with triple quotes in Python or Scala

(1) Example

// 1. json
{
    
    
    "foo" : {
    
    
        "bar" : 10,
        "baz" : 12
    }
}

// 2.删除根括号:
"foo" : {
    
    
    "bar" : 10,
    "baz" : 12
}
// 3.删除引号:
foo : {
    
    
    bar : 10,
    baz : 12
}
// 4.使用 = 并且省略 :
foo {
    
    
    bar = 10,
    baz = 12
}

// 5.删除逗号:
foo {
    
    
    bar = 10
    baz = 12
}
// 6.对无引号的键使用点符号:
foo.bar=10
foo.baz=12
// 将点符号字段放在一行中:
foo.bar=10, foo.baz=12

// 7.变量替换
standard-timeout = 10ms
foo.timeout = ${
    
    standard-timeout}
bar.timeout = ${
    
    standard-timeout}

// 8.继承
foo = {
    
     a : 42, c : 5 }
foo = {
    
     b : 43, c : 6 }
// 等同于:
foo = {
    
     a : 42, b : 43, c : 6 }
// 可以利用这一点进行“继承”:
data-center-generic = {
    
     cluster-size = 6 }
data-center-east = ${
    
    data-center-generic}
data-center-east = {
    
     name = "east" }
data-center-west = ${
    
    data-center-generic}
data-center-west = {
    
     name = "west", cluster-size = 8 }
// 使用include语句,您也可以将它分割到多个文件中。
// 如果您将两个对象放在一起(第一个对象的右括号与第二个对象的左括号在同一行),它们将被合并,因此编写上述“继承”示例的一种更简短的方式是:
data-center-generic = {
    
     cluster-size = 6 }
data-center-east = ${
    
    data-center-generic} {
    
     name = "east" }
data-center-west = ${
    
    data-center-generic} {
    
     name = "west", cluster-size = 8 }

// 9.可选的系统或环境变量覆盖 带有${?foo}如果没有找到替代,替代值就会消失
// 使用JVM参数-Dconfig.override_with_env_vars=true 即使没有指定显式替换,也可以使用环境变量覆盖任何配置值。
// 环境变量值将覆盖任何预先存在的值以及作为Java属性提供的任何值。
basedir = "/whatever/whatever"
basedir = ${
    
    ?FORCED_BASEDIR}

// 这个数组包含一个或两个值
path = [ "a", ${
    
    ?OPTIONAL_A} ]

// 10.在配置文件外设置数组值
// 从java属性或环境变量设置数组项的值需要在数组中为该值指定索引。因此,在HOCON中,您可以将多个值设置到一个数组中或追加到一个数组中:
## HOCON
items = ["a", "b"]
items += "c"
// 使用java属性,您可以指定确切的位置:
-Ditems.0="a" -Ditems.1="b"
// 以及环境变量:
export CONFIG_FORCE_items_0=a
export CONFIG_FORCE_items_1=b

// 11.带引号或不带引号的字符串当然也可以用替换来连接:注意:${}语法必须在引号之外!
tasks-url : ${
    
    base-url}/tasks
tasks-url : ${
    
    base-url}"tasks:colon-must-be-quoted"
// 串联可以引用同一字段的早期值:
path : "/bin"
path : ${
    
    path}":/usr/bin"
// 数组也可以连接起来:
path : [ "/bin" ]
path : ${
    
    path} [ "/usr/bin" ]
// 追加到数组有一个简写:
// equivalent to: path = ${?path} [ "/usr/bin" ]
path += "/usr/bin"

// 以下是等价的:
data-center-generic = {
    
     cluster-size = 6 }
data-center-east = ${
    
    data-center-generic}
data-center-east = {
    
     name = "east" }
// 与以上是等价的
data-center-generic = {
    
     cluster-size = 6 }
data-center-east = ${
    
    data-center-generic} {
    
     name = "east" }

4. Version overview

Versions 1.2.1 and earlier support Java 6, while newer versions (1.3.0 and later) support Java 8.

Citation package:

<dependency>
    <groupId>com.typesafe</groupId>
    <artifactId>config</artifactId>
    <version>1.4.2</version>
</dependency>

Or directly download the dependent jar package:https://repo1.maven.org/maven2/com/typesafe/config/

Changelog: https://github.com/lightbend/config/blob/main/NEWS.md

The format of the .conf configuration file: https://github.com/lightbend/config/blob/main/HOCON.md

2. Basic use of typesafe config

1. API example

import com.typesafe.config.ConfigFactory
// 加载配置
Config conf = ConfigFactory.load();
int bar1 = conf.getInt("foo.bar"); // 获取int值
Config foo = conf.getConfig("foo"); // 获取config
int bar2 = foo.getInt("bar"); // 从config获取int值

2. More examples

examples/There are more examples in the project's directory: https://github.com/lightbend/config/tree/main/examples.

In short, as the example shows:

  • Libraries should use the Config instance provided by the application (if any), or use ConfigFactory.load() if no special Config is provided. Libraries should put their defaults in a reference.conf on the classpath.
  • Can create the config the way they want (ConfigFactory.load() is the easiest) and then feed it to their library. A Config can be created using the parser methods in ConfigFactory, or built from any file format or data source you like using methods in ConfigValueFactory.

3. Invariance

Objects are immutable, so methods on Config that convert configurations return a new Config. Other types such as ConfigParseOptions, ConfigResolveOptions, ConfigObject, etc. It is also immutable.

You can see more in the API: https://lightbend.github.io/config/latest/api/

4. Schema and Validation

There is no schema language or anything like that. However, two tools are recommended:

  • Use the checkValid() method
  • Access your config through the settings class, each setting has a field, and instantiate it on startup (throws an exception immediately if any setting is missing)

In Scala, a settings class might look like:

class Settings(config: Config) {
    
    

    // validate vs. reference.conf
    config.checkValid(ConfigFactory.defaultReference(), "simple-lib")

    // non-lazy fields, we want all exceptions at construct time
    val foo = config.getString("simple-lib.foo")
    val bar = config.getInt("simple-lib.bar")
}

5. Configuration file loading

The convenience method ConfigFactory.load() loads the following (listed first has higher priority):
1. System properties
2. application.conf (all resources with this name on the classpath)
3. application.json (classpath
4. application.properties (all resources with this name on the classpath)
5. reference.conf (all resources with this name on the classpath)

Can use ConfigFactory.load("myapp ") to load their own myapp.conf.

You can use the command line -Dconfig.file=path/to/config-fileto load configuration files:
1. config.resource: Specify the resource name instead of the base name, ie application.conf instead of application.
2. config.file: Specifies the file system path. Similarly, it should include the extension, not the base name.
3. config.url: Specify a URL.

These system properties specify application overrides. {conf, json, properties}, not addition. They only affect applications configured with the default ConfigFactory.load(). In the replacement configuration file, you can use include "application" to include the original default configuration file; after the include statement, you can continue to override some settings.

If you dynamically set config.resource , config.file or config.url inside your program (e.g. using System.setProperty() ), be aware that ConfigFactory has some internal cache and may not see the new value of the system property. Use ConfigFactory.invalidateCaches() to force a reload of system properties.

A note on resolving substitutions in reference.conf and application.conf:
the substitution syntax ${foo.bar} will be parsed twice. First, merge all reference.conf files, then parse the result. Second, all application.confs are on top of unresolved reference.conf, whose results are parsed again.
This means that the reference.conf stack must be self-contained; you cannot have application.conf supply the undefined value ${foo.bar}. However, a variable referenced by reference.conf can be overridden as long as the reference.conf itself also defines that variable.

6. Merge configuration tree

Any two configuration objects can be merged with an associated operation called withFallback, eg merged = firstConfig.withFallback(secondConfig).

The withFallback operation is used in libraries to merge duplicate keys in the same file, and to merge multiple files. ConfigFactory.load() uses this to stack system properties on application.conf on reference.conf.

You can also use withFallback to incorporate some hardcoded value, or "promote" a subtree to the root of the configuration; assuming you have something like this:

foo=42
dev.foo=57
prod.foo=10

Then you can write code like this:

Config devConfig = originalConfig
                     .getConfig("dev")
                     .withFallback(originalConfig)

There are many ways to use withFallback.

7. Handle default values

Many other configuration APIs allow you to provide a default value for the getter method, like so:

boolean getBoolean(String path, boolean fallback)

If there is no such configuration, an exception will be thrown through the get method.

8. Understand Config and ConfigObject

A Config treats a JSON-equivalent data structure as a first-level mapping from paths to values. So if your JSON looks like this:

  "foo" : {
    
    
    "bar" : 42
    "baz" : 43
  }

With the Config interface, you can write conf.getInt("foo.bar") . The foo.bar strings are called path expressions (HOCON.md has syntax details for these expressions). Iterating over this Config, you'll get two entries; "foo.bar": 42 and "foo.baz": 43. When iterating over Config you won't find nested Configs (because everything is flattened).

When viewing the JSON tree as Config, null values ​​are considered missing. Iterating over a Config will skip null values.

You can also look at Config like most JSON APIs, through the configuration object interface. This interface represents an object node in a JSON tree. ConfigObject instances appear in a multilevel tree, and keys don't have any syntax (they're just strings, not path expressions). As a ConfigObject, you'll get an entry "foo": { "bar": 42, "baz": 43}, where the value in "foo" is another nested ConfigObject.

In ConfigObject, null values ​​are visible (unlike missing values), just like in JSON.

ConfigObject is a subtype of ConfigValue, where other subtypes are other JSON types (List, String, Number, Boolean, Null).

Config and ConfigObject are two ways of looking at the same internal data structure, and you can freely convert between them with Config.root() and ConfigObject.toConfig().

Since version 1.3.0, if you have a Java object that follows the JavaBean convention (zero-argument constructor, getters, and setters), you can access it from Config.

Use ConfigBeanFactory.create(config.getConfig("subtree-that-matches-bean"), MyBean.class) to do this.

From creating a beanConfig automatically validates that the configuration matches the bean's implicit schema. Bean fields can be primitive types, typed lists such as List<Integer>, java.time.Duration, ConfigMemorySize, or even raw Config, ConfigObject, or ConfigValue (if you want to manually handle specific values).

9, understanding ConfigFactory

Contains static methods for creating Config instances.

Including loading Config from a file, loading Config from a string, loading from a Map, and so on.

おすすめ

転載: blog.csdn.net/A_art_xiang/article/details/132335671