Kotlin 和 Spring Boot 一起使用的一些坑
从 16 年 12 月我们在 SpringBoot 的后端使用 Kotlin 开发以来,遇到了各种各样的坑。尽管 Jetbrains 宣称 Kotlin 对 Java 的互操作性是语言设计的一大优势,但由于 SpringBoot 和 Spring 严重依赖了 JVM 平台的各种特性,有时 Kotlin 并不能编译出足够符合行为的字节码,在一些依赖 Spring 特性的地方会遇到各种奇怪问题。
本文总结了使用 Kotlin 开发 SpringBoot 后端项目的过程中遇到的各种坑,其中有些可能逐渐被 Kotlin 官方文档提醒或解决。
首先介绍一个可以方便查看 Kotlin 编译后代码具体行为的方式,以 Intellij 为例,假设以下代码:
1 |
|
在 Intellij 中双击 Shift 键,选择 Show kotlin bytecode
,再选择 Decompile
可以查看编译后代码再逆回 Java 的样子,如上面这段 Data Class 会生成很多的方法,节选如下:
1 |
|
可以看到每个字段都生成的 Getter 和 Setter,并且 @get:id
可以将 annotation 直接加在 Getter 上。
这种方式可以非常具体的查看 Kotlin 编译器到底为我们生成了怎样的代码,对于熟悉 Java 打算试试 Kotlin 的人来说非常方便,不会被内部复杂的细节困扰。
No default constructor for entity (实体缺少默认构造)
从数据库中查询 entity 时,Hibernate 会首先调用默认构造(无参构造函数)初始化对象,之后将各个字段调用 Setter 设置进来。以开头的那段代码为例,使用 JpaRepository
查询出对象时,会报这个错误。查看一下 Java 代码可以发现构造函数只有一个,接受的是 name:string
字段。
开发时我们会希望将构造一个实体时需要的参数都放在构造函数中,增强静态检查能力,同时给每个对象都设置默认初值不够方便,因此无法手写一个无参构造给 Hibernate 调用。
此时可以使用 jpa-support 这个编译插件来为 @entity
注解的 class 生成无参构造。
此时再查看生成的代码,会看到在最下面多了个无参构造,没有做任何事情,但再次用 Hibernate 已经没问题了。
Could not locate setter method for property (找不到 Setter)
1 |
|
比如 name
字段我们只想在构造时设置,之后不能修改,在 kotlin 中自然的选择用 val,但运行时 hibernate 会提示找不到这个字段的 setter 方法。可以给 name 加上 annotation,在自己的代码中调用会报错,但 hibernate 反射调用却不会有问题。
1 | false) (updatable = |
Transactional 不生效
Kotlin 默认的类是 final 的,不可继承,Spring 也无法代理其中的方法,可以手动将某些类变成 open class,方法变成 open fun,也可以使用 spring-support,会自动把一些 annotation 注解的类变成 open class。
Lazy Fetch 不生效
实体类默认全部被生成了 final class,且 spring-support 插件没有将 @entity
注解的类变为 open class,需要手动应用 kotlin-allopen 并在 gradle 中配置对 @entity
注解的 allopen。
1 | apply plugin: "kotlin-allopen" |
类内方法不能被代理
1 |
|
在这个类中 queryPost 应该运行在事务中,但执行时会发生异常 could not initialize proxy - no Session
,没能开启事务。这是由于 spring-aop 是包裹你的方法,对于从 this
调用的方法不能代理掉。可以注入一个自己类的实例,调用该对象的方法。
1 |
|