【问题标题】:Avoiding inline with Kotlin generics避免与 Kotlin 泛型内联
【发布时间】:2019-01-21 02:41:49
【问题描述】:

我正在尝试构建一个通用类来处理 Kotlin 和 Java 中的配置注入。

基本上,我希望一个类绑定到一个数据类和一个资源文件的路径,该文件应该包含数据类实例的可轻松写入的反序列化。

到目前为止,我想出的是:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

class ResourceLoader()

inline fun <reified T : Any> loadObject(resourcePath: String): T {
    // Load resource file
    val resource = ResourceLoader::class.java.classLoader.getResource(resourcePath)
    val resourceContent = resource.readText()

    // Return deserialized object
    return jacksonObjectMapper().readValue(resourceContent)
}


abstract class ResourceBound<T : Any>(val resourcePath: String) {
    inline fun <reified U : T> getResource(): U {
        return loadObject(this.resourcePath)
    }
}

有了这个,我可以将一个测试类绑定到一个资源文件的存在,如果文件缺少格式错误,它会在光荣的异常中失败,如下所示:

data class ServiceConfig(val endpoint: String, val apiKey: String)

class TestClassLoadingConfig() : ResourceBound<ServiceConfig>("TestConfig.json") {
    @Test
    fun testThis() {
        val config: ServiceConfig = this.getResource()
        val client = ServiceClient(config.endpoint, config.apiKey)
        ...
    }
}

唯一的问题是它只适用于 Kotlin,因为 inline 与 java 不兼容。那么我该如何解决呢?

作为奖励,我想摆脱显式类型声明,所以 val config: ServiceConfig = this.getResource() 可以只是 val config = this.getResource()

【问题讨论】:

  • 您可以改用未经检查的演员表,例如只是写return jacksonObjectMapper().readValue(resourceContent) as T
  • 如果我使用 Kotlin 化的 jacksonObjectMapper(),我会得到 Cannot use 'T' as reified type parameter. Use a class instead.。如果我改为尝试ObjectMapper().readValue(resource, object : TypeReference&lt;T&gt;() {}) as T,则会出现运行时异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to dk.whatever...
  • 抱歉,我的错误...在取消/编组时,您需要一个具体的类型...这也是您使用 TypeReference&lt;T&gt;() {} 提供的...至少我是这么认为的...如果你只是传递类型本身(例如dk.whatever.....class),那么它可以工作吗?
  • 不。我现在正在尝试用 Java 编写代码,我会看看它是否可以在 Kotlin 中工作。
  • 这看起来越来越像杰克逊的限制。我的 Java 实现也导致 ClassCastException

标签: java generics kotlin jackson jackson-databind


【解决方案1】:

我最近偶然发现了同样的问题,并通过传递TypeReference 作为第二个参数来解决它。

我的基本配置类:

package config

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule

open class BaseConfig<T>(configFile: String, typeReference: TypeReference<T>) {

    val config: T

    init {
        val mapper = ObjectMapper(YAMLFactory())
        mapper.registerModule(KotlinModule())

        val inputStream = this.javaClass.classLoader
            .getResourceAsStream(configFile)
        config = mapper.readValue(inputStream, typeReference)
    }
}

示例数据类:

package config.dto

data class MessageDto(
    val `example messages`: List<String>
)

示例对象:

package config

import com.fasterxml.jackson.core.type.TypeReference
import config.dto.MessageDto

object Message: BaseConfig<MessageDto>("messages.yml", object: TypeReference<MessageDto>() {}) {
    val exampleMessages: List<String> = config.`example messages`
}

示例 yaml:

example messages:
  - "An example message"
  - "Another example message"

它并不像我希望的那样简洁,但它确实有效,它为我提供了一种将配置文件中的内容映射到任何数据类的灵活方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    • 2015-07-11
    • 2017-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多