在 Spring 引导应用程序的生命周期中,Kotlin 自省何时可用?
When does Kotlin introspection become available in the lifetime of a Spring Boot Application?
我运行陷入了一个令人惊讶的错误。我正在尝试制作一个可以使用 存储库模式 访问 mongodb 的应用程序。为了减少代码重复,我想为所有存储库创建一个公共基础 class。每个根聚合的存储库(例如下面代码中的 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: Parameter specified as non-null is
null: method kotlin.jvm.JvmClassMappingKt.getJavaClass, parameter
$receiver
at kotlin.jvm.JvmClassMappingKt.getJavaClass(JvmClassMapping.kt) 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>
导致空异常。
我想知道在 Spring 应用程序中是否有一些使用内省的规则,或者这是否是 Kotlin 或 Spring 中的错误。
TL;DR;
它看起来像一个错误,但它不是 - 这是事情如何运作的结果。
说明
这里发生的是 private val klass: KClass<T>
是 null
。看代码,这实际上不可能发生,但它确实发生了。在幕后发生的事情是 Spring 为 PersonRepository
:
创建了一个代理
this = {com.example.demo.DemoApplicationTests@5173}
personRepository = {com.example.demo.PersonRepository$$EnhancerBySpringCGLIB$849208@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
如您所见,klass
是 null
。这是一个重要的事实,因为您正在调用 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(): …
}
我运行陷入了一个令人惊讶的错误。我正在尝试制作一个可以使用 存储库模式 访问 mongodb 的应用程序。为了减少代码重复,我想为所有存储库创建一个公共基础 class。每个根聚合的存储库(例如下面代码中的 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: Parameter specified as non-null is null: method kotlin.jvm.JvmClassMappingKt.getJavaClass, parameter $receiver
at kotlin.jvm.JvmClassMappingKt.getJavaClass(JvmClassMapping.kt) 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>
导致空异常。
我想知道在 Spring 应用程序中是否有一些使用内省的规则,或者这是否是 Kotlin 或 Spring 中的错误。
TL;DR;
它看起来像一个错误,但它不是 - 这是事情如何运作的结果。
说明
这里发生的是 private val klass: KClass<T>
是 null
。看代码,这实际上不可能发生,但它确实发生了。在幕后发生的事情是 Spring 为 PersonRepository
:
this = {com.example.demo.DemoApplicationTests@5173}
personRepository = {com.example.demo.PersonRepository$$EnhancerBySpringCGLIB$849208@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
如您所见,klass
是 null
。这是一个重要的事实,因为您正在调用 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(): …
}