一.背景
在上一片文章讲解了typesafe加载HOCON为实体类,那么为什么还要单独写一篇Kotlin加载的事例呢?java和kotlin不是都是依赖jvm么?是的理论上来说java能用的kotlin也是没有问题的,关键是我们在kotlin中使用实体类的方式和java中有所不同,而且typesafe转换实体类的接口并没有兼容kotlin的方式,也没有提供类似的接口,看下面我们同样使用java一样的方式转换实体类的时候的情况:
data class AppConfigKotlin (
val demo: Demo
) {
companion object {
fun loadConfKotlin01() {
val config = ConfigFactory.parseFile(File("./config/app.conf"))
val appConfig = ConfigBeanFactory.create(config, AppConfigKotlin::class.java)
val name = appConfig.demo.name
println("Name:$name")
}
}
}
data class Demo(
val name: String,
val age: Int,
val men: Boolean,
val address: List<String>,
val family: Map<String, Any>
)
结果报了如下异样:
com.typesafe.config.ConfigException$BadBean: AppConfigKotlin needs a public no-args constructor to be used as a bean
下面我们来看源码:
public static <T> T createInternal(Config config, Class<T> clazz) {
// ......
// Fill in the bean instance
T bean = clazz.newInstance();
for (PropertyDescriptor beanProp : beanProps) {
Method setter = beanProp.getWriteMethod();
Type parameterType = setter.getGenericParameterTypes()[0];
Class<?> parameterClass = setter.getParameterTypes()[0];
String configPropName = originalNames.get(beanProp.getName());
// Is the property key missing in the config?
if (configPropName == null) {
// If so, continue if the field is marked as @{link Optional}
if (isOptionalProperty(clazz, beanProp)) {
continue;
}
// Otherwise, raise a {@link Missing} exception right here
throw new ConfigException.Missing(beanProp.getName());
}
Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configPropName);
setter.invoke(bean, unwrapped);
}
return bean;
//......
}
在我们调用ConfigBeanFactory.create(config, AppConfigKotlin::class.java)将config转换为AppConfigKotlin数据类的时候,最后会执行到如上的代码,需要根据传入的.Class参数调用clazz.newInstance()实例化一个对象,然而我们kotlin是使用的data class,没有默认的那个构造函数,所以报出了这样的异常。看来直接使用与java相同的方式是不行了,那么有没有什么解决方案呢?下面来看看解决方案。
二.使用
1.引入Config4k.jar
Config4k是Kotlin的轻量级Typesafe Config包装器,提供了简单的扩展功能Config.extract<T>
以及Any.toConfig
在Config
Kotlin对象之间进行转换。当然之前的typesafe.jar也是需要的,这里是要新加的一个jar包。
maven
<dependency>
<groupId>io.github.config4k</groupId>
<artifactId>config4k</artifactId>
<version>0.4.2</version>
</dependency>
Gradle
compile group: 'io.github.config4k', name: 'config4k', version: '0.4.2'
2.实例代码
config file
在项目根路径新建config文件夹用来放我们的配置文件app.conf("./config/app.conf")
demo {
name = "Jerry"
age = 21
men = false
// list类型
address = [
"address4"
"address5"
"address6"
]
// map类型
family {
mather = "sister"
father = "brother"
}
}
加载为data class
data class AppConfigKotlin (
val demo: Demo
) {
companion object {
fun loadConfKotlin01() {
val config = ConfigFactory.parseFile(File("./config/app.conf"))
// 将config转换为data class
val appConfig = config.extract<AppConfigKotlin>()
val name = appConfig.demo.name
println("Name:$name")
}
}
}
data class Demo(
val name: String,
val age: Int,
val men: Boolean,
val address: List<String>,
val family: Map<String, Any>
)
结果:
Name:Jerry
BUILD SUCCESSFUL in 44s
其他功能
config4k还有其他的很多功能,比如支持enum,序列化等,更多的内容可以看config4k文档或者源码
enum class Size {
SMALL,
MEDIUM,
LARGE
}
val config = ConfigFactory.parseString("""key = SMALL""")
val small = config.extract<Size>("key")
println(small == Size.SMALL) // true
data class Person(val name: String, val age: Int)
val person = Person("foo", 20).toConfig("person")
println(person.root().render())