1. 概述
Java 与 Kotlin 都是运行在 JVM 上的语言。虽然两者语法和设计哲学有所不同,但在 Java EE 容器中使用 Kotlin 时,会遇到一些典型的兼容性问题。
本文将介绍在 Java EE 环境中使用 Kotlin 时常见的挑战,并提供解决方案。通过一个简单的 CRUD 示例应用,演示如何在 Java EE 容器中顺利使用 Kotlin 编写企业级应用。
2. 面临的挑战
Kotlin 与 Java 最大的不同在于其语言设计范式。比如:
- Kotlin 类默认是
final
的,不能被继承。 - Kotlin 的数据类(data class)需要显式提供无参构造器以适配 JPA、Jackson 等框架。
- 依赖注入(DI)在 Kotlin 中初始化方式略有不同。
- 使用 Arquillian 进行集成测试时也存在兼容性问题。
这些问题虽然不是大问题,但需要我们有意识地进行适配,才能顺利构建 Kotlin + Java EE 应用。
✅ 提示:目前 Arquillian 已停止更新,与 JUnit5 和新版本 Wildfly 不兼容,使用时需要注意版本选择。
3. 依赖配置
我们首先在 pom.xml
中添加 Java EE 的依赖:
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>11.0.0-M1</version>
<scope>provided</scope>
</dependency>
由于我们仅在编译阶段使用该依赖,所以设置为 provided
。
4. JPA 实体类
我们定义一个 Student
数据类,同时作为 JPA 实体类和 DTO 使用:
@Entity
data class Student constructor (
@SequenceGenerator(name = "student_id_seq", sequenceName = "student_id_seq", allocationSize = 1)
@GeneratedValue(generator = "student_id_seq", strategy = GenerationType.SEQUENCE)
@Id
var id: Long?,
var firstName: String,
var lastName: String
) {
constructor() : this(null, "", "")
constructor(firstName: String, lastName: String) : this(null, firstName, lastName)
}
Kotlin 的 data class
会自动生成 equals()
, hashCode()
, toString()
等方法,非常适合用作数据容器。
⚠️ 注意:为了兼容 JPA 或 Jackson,必须显式定义无参构造函数。Kotlin 的主构造函数不能满足框架的实例化需求。
5. 业务服务类
我们使用 @Stateless
注解定义一个无状态 EJB,处理 CRUD 逻辑:
@Stateless
open class StudentService {
@PersistenceContext
private lateinit var entityManager: EntityManager
open fun create(student: Student) = entityManager.persist(student)
open fun read(id: Long): Student? = entityManager.find(Student::class.java, id)
open fun update(student: Student) = entityManager.merge(student)
open fun delete(id: Long) = entityManager.remove(read(id))
}
由于 Kotlin 默认类是 final
的,我们需要使用 open
关键字显式允许继承,以便容器创建代理类进行注入。
我们使用 lateinit
关键字延迟初始化 EntityManager
,这是 Kotlin 中处理依赖注入的常用方式。
6. REST 接口
我们定义一个 REST 接口类,暴露 CRUD 操作:
@ApplicationPath("/")
class ApplicationConfig : Application() {
override fun getClasses() = setOf(StudentResource::class.java)
}
接口资源类:
@Path("/student")
open class StudentResource {
@Inject
private lateinit var service: StudentService
@POST
open fun create(student: Student): Response {
service.create(student)
return Response.ok().build()
}
@GET
@Path("/{id}")
open fun read(@PathParam("id") id: Long): Response {
val student = service.read(id)
return Response.ok(student, MediaType.APPLICATION_JSON_TYPE).build()
}
@PUT
@Path("/{id}")
open fun update(@PathParam("id") id: Long, student: Student): Response {
service.update(student)
return Response.ok(student, MediaType.APPLICATION_JSON_TYPE).build()
}
@DELETE
@Path("/{id}")
open fun delete(@PathParam("id") id: Long): Response {
service.delete(id)
return Response.noContent().build()
}
}
与服务类类似,我们也需要将类和方法标记为 open
,以支持容器代理注入。
7. 使用 Arquillian 进行集成测试
Arquillian 是 Java EE 中常用的集成测试框架。虽然它已不再更新,但仍可用于旧项目。
我们使用 ShrinkWrap 打包部署测试 WAR 包:
@RunWith(Arquillian.class)
public class StudentResourceIntegrationTest {
@Deployment
public static WebArchive createDeployment() {
JavaArchive[] kotlinRuntime = Maven.configureResolver()
.workOffline()
.withMavenCentralRepo(true)
.withClassPathResolution(true)
.loadPomFromFile("pom.xml")
.resolve("org.jetbrains.kotlin:kotlin-stdlib")
.withTransitivity()
.as(JavaArchive.class);
return ShrinkWrap.create(WebArchive.class, "kotlin.war")
.addPackages(true, Filters.exclude(".*Test*"), "com.baeldung.jeekotlin")
.addAsLibraries(kotlinRuntime)
.addAsResource("META-INF/persistence.xml")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}
@Test
@RunAsClient
public void when_post__then_return_ok(@ArquillianResource URL url)
throws URISyntaxException, JsonProcessingException {
String student = new ObjectMapper()
.writeValueAsString(new Student("firstName", "lastName"));
WebTarget webTarget = ClientBuilder.newClient().target(url.toURI());
Response response = webTarget
.path("/student")
.request(MediaType.APPLICATION_JSON)
.post(Entity.json(student));
assertEquals(200, response.getStatus());
}
}
我们在测试中使用 @ArquillianResource
获取部署后的 URL,构造 HTTP 请求验证 REST 接口功能是否正常。
8. 总结
本文通过一个完整的 CRUD 示例,展示了如何在 Java EE 容器中使用 Kotlin 构建企业级应用。虽然 Kotlin 与 Java 在语法和设计上存在差异,但通过一些技巧(如 open
、lateinit
、显式无参构造函数等),可以很好地兼容 Java EE 的各种框架。
完整源码可在 GitHub 仓库 获取。