什么时候Kotlin内省在Spring Boot应用程序的生命周期中可用?

时间:2018-01-17 17:35:45

标签: spring kotlin spring-mongodb kotlin-reflect

我遇到了一个令人惊讶的错误。我正在尝试使用存储库模式创建一个可以访问mongodb的应用程序。为了减少代码重复,我想为所有存储库创建一个公共基类。每个根聚合的存储库(例如下面的代码中的Person)将继承此RepositoryBase并继承所有常用功能。

data class Person(val name: String)

open class RepositoryBase<T: Any> (val template: ReactiveMongoTemplate, private val klass: KClass<T>)
{
    fun count(): Mono<Long> = template.count(Query(), klass.java)
}

@Repository
class PersonRepository(template: ReactiveMongoTemplate): RepositoryBase<Person>(template, Person::class)

@RunWith(SpringRunner::class)
@SpringBootTest
class DemoApplicationTests
{
    @Autowired var personRepository: PersonRepository? = null

    @Test
    fun contextLoads()
    {
        println(personRepository?.count()?.block()!!)
    }
}

然而,这似乎不起作用:

  

java.lang.IllegalArgumentException:指定为非null的参数是   null:方法kotlin.jvm.JvmClassMappingKt.getJavaClass,参数   $接收机

     

at kotlin.jvm.JvmClassMappingKt.getJavaClass(JvmClassMapping.kt)at at   com.example.demo.RepositoryBase.count(DemoApplicationTests.kt:18)   ...

似乎在调用Person::class时,内省功能未完全初始化,随后调用KClass.java,其定义为:

/**
 * Returns a Java [Class] instance corresponding to the given [KClass] instance.
 */
@Suppress("UPPER_BOUND_VIOLATED")
public val <T> KClass<T>.java: Class<T>
    @JvmName("getJavaClass")
    get() = (this as ClassBasedDeclarationContainer).jClass as Class<T>

导致null异常。

我想知道在Spring应用程序中使用内省是否有一些规则,或者这是否是Kotlin或Spring中的错误。

1 个答案:

答案 0 :(得分:3)

TL; DR;

它看起来像一个错误,但它不是 - 这是事情运作的结果。

说明

这里发生的事情是private val klass: KClass<T>null。看看代码,这实际上不可能发生,但确实如此。幕后发生的事情是Spring为PersonRepository创建了一个代理:

this = {com.example.demo.DemoApplicationTests@5173} 
 personRepository = {com.example.demo.PersonRepository$$EnhancerBySpringCGLIB$$42849208@5193} "com.example.demo.PersonRepository@1961d75a"
  CGLIB$BOUND = false
  CGLIB$CALLBACK_0 = {org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor@5208} 
  CGLIB$CALLBACK_1 = {org.springframework.aop.framework.CglibAopProxy$StaticUnadvisedInterceptor@5209} 
  CGLIB$CALLBACK_2 = {org.springframework.aop.framework.CglibAopProxy$SerializableNoOp@5210} 
  CGLIB$CALLBACK_3 = {org.springframework.aop.framework.CglibAopProxy$StaticDispatcher@5211} 
  CGLIB$CALLBACK_4 = {org.springframework.aop.framework.CglibAopProxy$AdvisedDispatcher@5212} 
  CGLIB$CALLBACK_5 = {org.springframework.aop.framework.CglibAopProxy$EqualsInterceptor@5213} 
  CGLIB$CALLBACK_6 = {org.springframework.aop.framework.CglibAopProxy$HashCodeInterceptor@5214} 
  template = null
  klass = null

如您所见,klassnull。这是一个重要的事实,因为您正在调用RepositoryBase.count()count()final,因此无法通过CGLIB代理。在count()内部您正在访问klass字段(此处未使用getter),因此调用使用代理实例中的未初始化字段而不是实际目标。使用getter方法会将调用路由到实际目标并检索字段。

解决方案

使您的方法不是final

open class RepositoryBase<T: Any> (val template: ReactiveMongoTemplate, private val klass: KClass<T>)
{
    open fun count(): …
}