考虑以下存储库接口声明:
interface KotlinUserRepository : Repository<User, String> {
fun findById(username: String): User
fun search(username: String) = findById(username)
}
我正在声明一个默认接口方法
search(…)
,默认调用 findById(…)
。
启动我的应用程序失败并显示:
org.springframework.data.mapping.PropertyReferenceException: No property Search found for type User!
如何将 Kotlin 默认方法与 Spring Data 存储库接口结合使用并防止
PropertyReferenceException
?
Kotlin 1.1/1.2 首先将默认方法编译为抽象接口方法。无法在 Spring 数据存储库接口中使用 Kotlin 的默认方法。
Kotlin 允许使用 Java 运行时版本 1.6 的默认接口方法。 Java 1.8 引入了 JVM 级默认接口方法。这导致 Kotlin 使用与 Java 不同的方法来编译默认接口方法。
KotlinUserRepository
中的代码编译为:
interface KotlinUserRepository extends Repository {
User findById(String username);
User search(String username);
@Metadata(…)
public static final class DefaultImpls {
public static User search(KotlinUserRepository $this, String username) {
Intrinsics.checkParameterIsNotNull(username, "username");
return $this.findById(username);
}
}
}
方法
search(…)
编译为抽象接口方法。实现位编译为类DefaultImpls
,它反映了默认方法签名。想要实现 KotlinUserRepository
的类需要实现 search(…)
。在纯 Kotlin 环境中使用该接口将使 Kotlin 编译器创建实现位。
Spring 数据存储库与底层代理一起使用。存储库中的每个方法都必须是:
在这种情况下,
search(…)
不会根据您实现 Java 接口的方式由任何自定义代码实现。 Spring Data 尝试派生查询并将 search(…)
视为 User
域类的属性。查找失败并抛出 PropertyReferenceException
。
这是一个已知的限制。
正如 Ben 指出的,您现在(Kotlin 1.2.40+)可以使用
@JvmDefault
。
interface BatchRepository : PagingAndSortingRepository<Batch, Long> {
fun getAllByOrderByPriorityAscNameAsc(): List<Batch>
@JvmDefault
fun getForAdmin() = getAllByOrderByPriorityAscNameAsc()
}
您需要使用如下方式在 build.gradle 中启用该选项:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
freeCompilerArgs = ['-Xenable-jvm-default']
}
}
我刚刚在 Kotlin 1.2.41 上测试了它,它可以工作。
FWIW Kotlin 扩展方法 在这里对我来说效果很好,1 个 .kt 文件:
interface FooRepository : JpaRepository<FooDb, UUID>
object FooRepositoryExtensions {
fun FooRepository.doFoo(something: String): FooDb {
// do whatever you want here, the 'FooRepository' instance is available via `this`
}
最近发布的 Kotlin 1.2.40 现在支持一项实验性功能,可以通过
@JvmDefault
注释和设置功能标志将 Kotlin 默认方法编译为 Java 8 默认方法:Xenable-jvm-default
https://blog.jetbrains.com/kotlin/2018/04/kotlin-1-2-40-is-out/#more-5922
我还没有尝试过,但理论上你的例子应该像这样工作:
interface KotlinUserRepository : Repository<User, String> {
fun findById(username: String): User
@JvmDefault
fun search(username: String) = findById(username)
}
@JvmDefault
现已弃用。工作正常:
build.gradle.kts
kotlinOptions {
freeCompilerArgs += listOf("-Xjsr305=strict", "-Xjvm-default=all")
}
和
@JvmDefaultWithCompatibility
interface KotlinRepository<User> : JpaRepository<User, Int?> {
fun getExisted(id: Int): User = findById(id).orElseThrow {
NotFoundException("Entity with id=$id not found") }
您需要接口上方的jvm选项
-Xjvm-default=all
和注释@JvmDefaultWithCompatibility
。