hocon使用快速入门,配合kotlin使用起来绝对比json和yml好用!

本文介绍了HOCON配置文件的使用,包括其语法优势、加载方式和与JSON的区别。通过第三方库config4k,实现了Kotlin对象与HOCON配置之间的转换,便于在Kotlin项目中使用。同时,指出了HOCON易于覆盖参数值、支持多行字符串等特性,以及在某些方面解析难度高于JSON的不足。

项目地址:https://github.com/lightbend/config
api地址:https://lightbend.github.io/config/latest/api/
文档中文翻译版:https://github.com/ustc-zzzz/HOCON-CN-Translation/blob/master/HOCON.md
官方包:// https://mvnrepository.com/artifact/com.typesafe/config
implementation group: ‘com.typesafe’, name: ‘config’, version: ‘1.4.0’
官方包:我们可以直接把配置文件默认加载,也可以根据文件名加载
默认文件名为:application.config …按特定文件名加载顺序进行加载

第三方包:可以抽离出来对象扩展了功能可以实现kotlin对象和config之间的对象转换
group:‘io.github.config4k’,name:‘config4k’,version:‘0.4.2’

使用hocon的优点
1.语法简单、灵活
2.允许从变量中取值,允许写注释,解决了json的痛点
3.相比json更低的信噪比,json有很多的多余字符 很无用
4.能够比较方便的覆盖参数值(方便书写或者debug)ps:可以在运行的时候直接通过参数修改配置参数的值,而不用跑过去修改配置文件。
5.支持多行字符串,json不支持多行字符串
6.在配置文件中支持一处引用另一处

使用hocon的缺点
比json难描述,也比json难解析

hocon中和json一样的地方
文件必须是合法的 UTF-8 格式
加引号的字符串格式和 JSON 中的字符串相同
值类型可以是:字符串、数值、对象、数组、布尔值、以及空(null)
允许的数字格式和 JSON 相同;在 JSON 中一些可能的浮点数值,如 NaN 等,是不允许出现的

注释的两种方式 // 和#
##第一种注释方式
//第二种注释方式


连接方式灵活
比如说“foo" {}和“foo":{}是一样的 或着直接不加连接好 则按照{}解析 
foo {}


元素划分
使用逗号、/n或着换行符,代表元素的划分,否则可能自连接为一个元素
在第一个值前或最后一个值后的空白将会被忽略。只有值 之间 的空白会被保留。
[1,2,3,4]四个元素
[1 2 3 4]一个元素 "1 2 3 4"


重复键的值合并问题
对于简单值,相同键的后出现的覆盖前面先出现的
a:32
a:42 
解析的时候 a为42

对于对象,会自动合并在一起
{
    foo : {a:42},
    foo : {b:43}
}
等价于
{
    foo:{a:42,b:43}
}

对象合并的过程中如果遇到null则会停止合并,然后进行覆盖

{
    foo : {a:42},0
    foo : null,
    foo:{b:43}
}
最终的结果:
    {
        foo:{b:43}
    }


关键字的解析问题
不加引号的非关键字都会被直接解析为字符串
truefoo 会被解析成 true 和一个字符串"foo"
footrue 会被解析成一个字符串"footrue"



多行字符串解析:
三对引号中间的所有字符都会被当作字符串处理,类似于scala,比如
"""foo""""将会被解析成foo".
也就是说三对引号之间的内容都会被解析成字符串 无论是否是特殊符号


值连结

对象中键值对的键或着值或着数组元素或者对象好可能表现为多个合在一起的值的组合,有三种连结方式
1.简单值的组合为字符串(关键字在里面也会被直接当成字符串 )
2.数组的组合为单个数组
3.对象的组合为合并后的单个对象
注意不同类型的数据之间不能进行值连接,否则会报错

“合并为字符串”
    
    
"合并为数组"


“合并对象”


对象的值连接蕾丝于继承 ,不仅可以获取变量的值,还可以对其进行修改

 human:{name:测试,sex:,add="中国"} 
 //覆盖里面的add
 person:${human}{add="测试地址"}
//扩充里面的属性 
 data-center-generic = { cluster-size = 6 }
data-center-east = ${data-center-generic} { name = "east" }




多样的路径表达方式
a: {
 b: {
  c: 3
  d: 4
 }
}

上路路径方式和下面是等级iAd额
a.b.c = 3
a.b.d = 4

a b c = 42 和“a b c"=42是等价的

因为路径表达式总是被转换成字符串,因此即使是拥有其他类型含义的单个值,
也会被转换成字符串类型。

true:42 等价于 “true" : 42
3:42 等价于 “3”:42
3.14:42 和“3” :{"14":42}等价



引用
${变量名} 常规引用的使用方式
animal.favorite : cat
key : ${animal.favorite} is my favorite animal
eq key :${animal.favorite}"is my favorite animal"

对于 foo : ${?bar} 来说,在 bar 未定义时,foo 这个键不会存在。对于 foo : ${?bar}${?baz} 来说,
如果 bar 和 baz 都 没有定义,那么 foo 这个键不会存在 ,自动忽略这个键

自引用:自己引用自己变量的值,然后对其进行更新覆盖
自引用事例:
a:${a}
a:${a}bc
path:${path}[/user/bin]

自引用的时候注意循环引用会报错诸如 a={ x : 42, y : ${a.x} } 的形式会在解析 ${a.x} 时试图解析不存在的 a。

数组使用键值分隔符

a+=b 会变成${?a}[b]
+=起到了在数组结尾追加元素的作用,如果a之前的值不是数组将会报错

在引用的时候如果第一次引用自己本身会报错比如:
foo : ${foo} 
但是如果
先foo : { a : 1 }
在foo : ${foo} 就不会报错


跨文件引用
使用incluede关键字来引用url或着文件,或着classpath路径下的资源
1.可以通过inclue 和随后的空白符以及单个被引号扩起来的字符串
2.通过url(),file(),classpath()等相应的被解析为url 文件名,或和classpath中的相关资源
3.required()括起来的上述情况之一

只有include后面跟一个空白符以及被引号引起来的字符串或着url(),file(),classpath()然后才会被解析为引用关键字,否则将会被解析为一个普通字符串
{foo include : 42}=={"foo include":42}
{foo: include}=={foo:"include"]
[include]==["include"]

跨文件引用中引用的文件必须包含一个对象,而不是数组,否则将会解析时报错

如果在引用对象的时候,此对象不存在,那么应该自动忽略,但是如果使用了required()则解析不到的文件,将会报错
例子:
include required("foo.conf")
include required(file("foo.conf"))

在加载文件的时候
如果不加文件类型的话,hoconf将会把所有符合前缀的文件都加载进来,然后合并在一起,json倒数第二加载,hoconf最后加载
例如:
include  "foo"
加载顺讯:
include "foo.properties"
inlcude "foo.json"
include "foo.conf"


测试demo
#test string 连接符 : eq =
dogName : wangcai
result:wangcai

#数组
place : [sanya,hainan,qingdao,null]
println(config.getString("dogName"))
println(config.getList("place"))
result:SimpleConfigList(["sanya","hainan","qingdao",null])

#对象
object : {a:hello world}
println(config.getObject("object"))
result:SimpleConfigObject({"a":"hello world"})

#重复键与对象合并
object:{
    "foo":{"a":42},
    "foo":{"b":43}
}
println(config.getObject("object"))
result: SimpleConfigObject({"a":"hello world","foo":{"a":42,"b":43}})

#如果存在中间存在null的时候自动合并终止
object2:{
     "foo":{"a":42},
     "foo":null,
     "foo":{"b":43},
}
//object 自动合并终止并进行覆盖
println(config.getObject("object2"))
result:SimpleConfigObject({"foo":{"b":43}})

#多行字符串
mulString : """各位
请看
我是一个
多行字符串
"""""
//多行字符串
println(config.getString("mulString"))
result:各位
请看
我是一个
多行字符串
""

#简单值连接
connection string : foo bar tar
connection array : [1,2,3,4] [5,6]
connection object: {a:123}{b:456}
//值连接
println(config.getString("connection string"))
println(config.getList("connection array"))
println(config.getObject("connection object"))
result:
foo bar tar
SimpleConfigList([1,2,3,4,5,6])
SimpleConfigObject({"a":123,"b":456})

#数组
a:[1,2,3,4]
#变量引用
a:${a}[5,6]
#这种和 :${}不一样,后者是往里面添加对象
a+=7
//数组 += 只针对数组
println(config.getList("a"))
result:SimpleConfigList([1,2,3,4,5,6,7])

#继承 不仅可以对其中的属性进行修改,还能对其扩充
data-center-generic = {cluster-size = 6}
data-center-east = ${data-center-generic}{name = "east"}

#我们常用的是用于文件路径集合
path = [/bin]
path = ${path}[/user/bin]

//继承
println(config.getObject("data-center-east"))
result:SimpleConfigObject({"cluster-size":6,"name":"east"})
println(config.getList("path"))
result:SimpleConfigList(["/bin","/user/bin"])
println(config.getList("b"))

#换行符和逗号是非常重要的 除了换行符和逗号之外的空白符号会被自动合并
b = [1 2 3 4] #this is an array with one element, the string "1 2 3 4 "
c = [1
2
3
4] #this is an array of four integers

println(config.getList("b"))
println(config.getList("c"))
result:
SimpleConfigList(["1 2 3 4"])
SimpleConfigList([1,2,3,4])


#数字中的.如果不经过处理是会被处理成路径表达式的 例如10.0foo
10.0foo : test
//10.0foo 只要键里面带有.就会被解析成对象的形式
println(config.getObject("10"))
result:
 SimpleConfigObject({"0foo":"test"})
 
 #跨文件引用
#夸文件引用要遵循特定的格式 include “文件名/url/classpath中的相关资源”或着使用url()file()classpath()括起来的#加引号的字符串
#如果inclue并不是以特定格式来使用的 那么include仅仅会被解析为正常的字符串
includeDemo : {
foo include : 42,
 key : include,
Include : 42} #eq {"foo include":42}
println(config.getObject("includeDemo"))
result:
SimpleConfigObject({"Include":42,"foo include":42,"key":"include"})

#文件引用
fileReference : {
a:{include "foo.conf"}
a:{x:42}
a:{shuzu:[23,42,1]}
}
//夸文件引用的解析
println(config.getObject("fileReference"))
result:
    SimpleConfigObject({"a":{"shuzu":[23,42,1],"x":42}})
    
#默认情况下当我们引用不到文件时会自动进行忽略,但是如果我们声明为强制要求文件的话,那么当我们引用不到文件的时候,将会报错#在我们受用的使用required关键字 合法的声明格式为 include required("foo.conf")

file:{include "urlfile.conf"}
#file2:{include required("file2.conf")}
如果我们urlfile.conf 和file2.conf两个文件都没有,但是如果把2注释掉,我们的程序就不回报错,如果不把2注释掉就会报错

#格式类型 夸文件引用声明中的文件格式可以忽略,但是加载顺序会有规定。
#例如 include "foo" 如果未加后缀名,那么只要符合条件的文件会被全部加载然后合并到一起,hocon格式的文件总是最后解析,#json格式的文件作为倒数第二个文件解析。
#例如: include "foo" 可能和 include "foo.properties" include "foo.json" include "foo.conf"等价





第三方包

当我们引入第三方jar包的时候包名为:
group:'io.github.config4k',name:'config4k',version:'0.4.2'
它拓展了原生hocon的功能,能够使我们把conf文件与kotlin对象形成映射关系
conf文件内容:
    emo {
  name = "tom"
  age = 18
  men = true
  # list类型
  // list类型
  address = [
    "address1"
    "address2"
    "address3"
  ]
 #map类型
  family {
    mather = "mather"
    father = "father"
  }
}



kotlin代码:
定义数据类
//定义好抽象出的数据类
data class Demo(
    //按照key解析进去
    val name:String,
    val age :Int,
    val men:Boolean,
    val address: List<String>,
    val family:Map<String,Any>
)

抽离出对象
data class AppConfigKotlin(

    val demo:Demo
){
    companion object{
        fun loadConfKotlin():AppConfigKotlin{
            val config = ConfigFactory.parseFile(File("hocon.conf"))
            val appconfig = config.extract<AppConfigKotlin>()
            return appconfig
        }
    }
}

打印出内容
fun main(){
    val loadConfKotlin = AppConfigKotlin.loadConfKotlin()
    println(loadConfKotlin.demo.address)
}
result:
     [address1, address2, address3]


官方包
如果我们不指定特定的文件名则,在累路径下按照以下顺序加载
//官方配置,不写文件名,默认加载顺序,按照优先级 system properties
//application.conf (all resources on classpath with this name)
//application.json (all resources on classpath with this name)
//application.properties (all resources on classpath with this name)
//reference.conf (all resources on classpath with this name)

比较有用的内容:
    1.允许我们实现先检查conf文件里是否有指定的key的对象
    config.checkValid(ConfigFactory.defaultReference(), "simple-lib")
    如果没有simple-lib为key的对象将直接报错
    
    2.如果我们想要更改conf文件的配置可以直接在编码中更改而不必去配置文件更改
    //我们可以覆盖配置文件里的内容但是不回修改配置文件,并且一定要在加载配置文件之前进行设置
System.setProperty("simple-lib.whatever", "This value comes from a system property")
这样会覆盖我们配置文件里的相同key的内容

demo

System.setProperty("simple-lib.whatever", "This value comes from a system property")
val config1 = ConfigFactory.load("complex1.conf")
demoConfigInSimpleLib(config1)

result:
The setting 'simple-lib.foo' is: This value comes from complex-app's complex1.conf
The setting 'simple-lib.hello' is: This value comes from complex-app's complex1.conf
The setting 'simple-lib.whatever' is: This value comes from a system property


fun demoConfigInSimpleLib(config: Config) {
    val context = SimpleLibContext(config)
    context.printSetting("simple-lib.foo")
    context.printSetting("simple-lib.hello")
    context.printSetting("simple-lib.whatever")
}

class SimpleLibContext @JvmOverloads constructor(private val config: Config = ConfigFactory.load()) {
    // this is the amazing functionality provided by simple-lib
    fun printSetting(path: String) {
        println("The setting '" + path + "' is: " + config.getString(path))
    }
    }
    
    
    conf内容:
    complex-app {
    something="This value comes from complex-app's complex1.conf"
}


simple-lib.foo="This value comes from complex-app's complex1.conf"
simple-lib.whatever = "This value comes from complex-app's complex1.conf"
simple-lib {
    hello = "This value comes from complex-app's complex1.conf"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值