0

I ran into a surprising bug. I am trying to make an application that would access mongodb using the repository pattern. In order to reduce code duplication I wanted to make a common base class for all repositories. The repository of each root aggregate (such as Person in code bellow), would inherit from this RepositoryBase and inherit all the common functionality.

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()!!)
    }
}

However, this does not seem to work:

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) ...

It seems that at the time Person::class is being called, the introspection capabilities are not fully initialized and the subsequent call to KClass.java, which is defined as:

/**
 * 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>

results in the null-exception.

I would like to know if there are some rules on using introspection in Spring applications or whether this is a bug either in Kotlin or in Spring.

4

1 回答 1

3

TL;博士;

它看起来像一个错误,但事实并非如此——它是事物运作方式的结果。

解释

这里发生的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(): …
}
于 2018-01-19T10:06:14.253 回答