如何使用 Kotlin 进行 JEE EJB 依赖注入

How to do JEE EJB Dependency Injection with Kotlin

我已经将正式用 Java 编写的 JEE 应用程序中的 REST 资源转换为 Kotlin。该应用程序在 Wildfly 应用服务器中运行,使用 Weld 作为依赖注入框架。

这是我想出的最终实现:

@Path("/myResource")
open class MyResource {

  @Context
  private lateinit var context: SecurityContext

  open protected setSecurityContext(securityContext: SecurityContext) {
    this.context = securityContext
  }

  @POST
  @Path("/change")
  @Transactional
  @Consumes(MediaType.APPLICATION_JSON)
  open internal fun change(data: MyChangeData, @Context uriInfo: UriInfo): Response {
    // ...
  }
}

setter 用于测试目的。使用 Mockito 或其他可以设置私有字段的模拟框架,这不是必需的。

我在这个实现中遇到了一些问题:

  1. 我必须将 class 和所有方法更改为 open 以允许 CDI 容器为此 bean 创建代理。据我了解这个主题,没有其他方法可以让 Weld 在不允许 subclassing?
  2. 的情况下完成它的工作
  3. 通常,Kotlin 为具有给定修饰符 (public/private/protected) 的属性生成 setters 和 getters,并由私有字段支持。但是当使用 lateinit 时,生成的字段具有与 getters 和 setters 相同的可见性(Kotlin in Action,第 146 页)。我不明白这种特殊行为的背景。使用 public 属性 会导致 Weld 报告错误,即不允许 public 字段。我如何声明该字段应该是私有的,但 getter 和 setter 受保护(以在测试中初始化资源)?
  4. 上面的代码注释了什么?字段还是生成的方法?如果是字段:如何只注释setter?
  5. 因为除私有方法之外的所有方法都必须是 open 所有属性,但私有方法都被 Weld 容器拒绝:Kotlin 创建具有相同可见性的 Getters 和 Setters,并且容器尝试代理代理所有的 bean bean 的方法。这不起作用,因为生成的 getter 和 setter 不是 open。对于私有属性,根本没有问题,因为容器不代理私有方法。正如我所见,无法将 getter/setter 声明为 open 因此无法使用受保护的属性

编辑:添加了问题 4 并由于此问题将实现更改为私有 setter。

  1. 我也不知道背景,但你可以通过将代码更改为 smth 来解决这个问题

open class A { @Context private lateinit var _backing: SecurityContext open protected var field: SecurityContext get() = _backing set(value) { _backing = value } }

此外,您可以使用构造函数注入

  1. 您正在那里注释字段。要注释 getters/setter,您可以将 @get:@set: 添加到注释中。

我能找到的最佳解决方案是将 属性 声明为 open protected:

@Context
open protected lateinit var context: SecurityContext

这样容器可以覆盖它,并且来自 java 或 groovy 的测试将把 setter 视为包保护。

如果你只想注释 setter(我更喜欢但更冗长)你可以使用:

open protected lateinit var context: SecurityContext @Context set

或更好更短:

@set:Context
open protected lateinit var context: SecurityContext

不幸的是,这不适用于 Kotlin 测试,因为 protected 变量只能被 Kotlin 中的 subclasses 看到。在这里你必须写一个单独的访问器:

@Context
open protected lateinit var context: SecurityContext

open internal fun setTheSecurityContext(context: SecurityContext) ...

或者您可以使用辅助构造函数:

open class MyResource() {

constructor(context: SecurityContext): this() {
  this.context = context
}

请注意,主要的空构造函数必须仍然存在。

下面是我的问题的单独答案:

  1. 如果依赖注入框架使用代理 open 对于 class 和每个方法以及 属性(但 private 是必需的)。
  2. 没有。对于 lateinit,属性 与吸气剂和 setter 具有相同的可见性。如果要执行此操作,则必须将 属性 声明为可为空。但是你必须在每次访问时使用 !!,这很尴尬。
  3. 字段。如上所示,您可以编写 @Context set@set:Context 来注释 setter.
  4. 如 1 中所述,所有属性(私有属性除外)都必须声明为开放。

编辑:请注意私有属性也会导致问题(使用 Wildfly 测试)。我的建议是根本不要使用 private,而是使用 protected。

编辑 2:请注意,这些解决方法仅在 EJB(如 REST 资源)上是必需的。对于简单的 CDI Beans,这不是问题,直到它们被某个方面等代理为止。那么这个post也适用。