查看原文
其他

Kotlin 和 Java EE 系列之 —— 使用插件愉快

2017-06-09 OSC-协作翻译 开源中国


前文请戳:Kotlin 和 Java EE 系列之—— 如何让 Kotlin 类对 Java EE 友好

继续将 Kotlin 带入 JEE 环境的旅程!这次,我们会看到几个让 JavaBean 行为与标准 Java 类一致的插件。

这个系列的上一个部分,我们看到,Java 转换为 Kotlin 很容易,让 Kotlin 兼容 JEE 有很多额外的工作要做。这些都是手工操作,很容易出错,主要原因在于 JavaBean 规范和 Kotlin 的冲突。JavaBean 是一个上世纪撰写的老旧标准。它设想在 RAD 可视编辑器中操作组件是可行的。比如,用户会从工具栏拖动一个文本框到窗体,然后设置文本、颜色和其它属性。这个组件必须构造成没有初始化状态的,可以一步步的进行配置。

在非 GUI 环境中,这个概念有不少缺点:组件不知道何时配置完成,用户也不知道为了完成配置需要设置哪些属性。

在依赖注入(DI)框架中,这些属性会由框架自动填充。变量前简单地使用 @Inject 就能找到正确的 bean。虽然这个方案当时看起来很优雅,但它需要合适的对象构造。它同样让测试变得复杂,因为必须重新配置注入来使用模拟的 bean。

幸好 Kotlin 编译器支持插件,可以模拟合适的对象结构和合适的对象构造。插件可以改变类编译后的样子。它们可以自动向 Java EE 看齐,还允许其它一些很酷的技巧。

GitHub上有一个改进前的示例项目。这是上一个系列里面上一部分产生的结果。


“all-open” 编译器插件


服务类转换的一部分让类和所有非公共成员(包括方法和属性)开放(open)。忘掉在 Java EE 容器中初始化某个方法和服务失败的事情。

我们会使用“all-open”插件来删除所有的 open 语句。添加这个插件,然后在插件的配置部分列出需要 open 元素的注解。这种情况下,我们需要 JAX-RS 和数据组件,所以添加 @Path 和 @Stateless 注解:

现在可以从代码中删除所有 open 语句了。


“no-arg” and “jpa” 编译器插件


与稍后注入依赖相比,我更愿意通过构造函数注入。在构造函数调用后,实例就已经完成了初始化,不再需要后来更新;从源代码的视点来看对象可以是不可变的。这有给类良好的功能性方面的感觉。但是在 bean 需要无参数构造的情况下该怎么做?

“no-arg”编译器插件将会向编译的代码添加一个无参数的构造函数。在代码中将没有可见的无参数构造函数。它会强制用户以不可变的方式构造对象,不过框架会有他们自己的安全的构造器。它的配置方式与 all-open 插件相同。也存在针对 Spring 和 JPA 的预配置版本,JPA 会处理所有注解为 @Entity 或  @Embeddable的类。那些额外的配置是作为常规“no-arg”相同插件的一部分,所以不需要都添加,但是为了完整性,我都添加了:

现在我们可以删除所有的无参数构造函数,然后做一些有意思的事,比如让服务类成为不可变的。不过如果类拥有 @Injected 构造器,为什么它还需要一个参数?我对这样的需求感到困难。Java 不能为构造同一个对象调用两次构造函数,是吧?在使用无状态 Bean 的时候,似乎是在使用无参数构造函数的 bean 初始化时构造了一个实例,而所有其他的实例,每次的调用都是调用有含参数的构造函数构造并注入值的。

进入 KittenRestService 并修改类的起始部分,从:

到:

我们的 var 变成了 val,并移除了 lateinit,这个类会在构造的时候通过构造函数参数完成初始化。

重要!在某些情况下,lateinit 并不像预期那样运作;构造函数中的初始化看起来从来不会出错。尽量使用构造函数参数而不是 lateinit。

这里有一个额外的好处是可以避免 bean 在初始化过程中的问题:访问无参数构造函数构造对象的注入字段可能导致错误,因为值还未注入;这就是为什么需要 @PostConstruct 注解。典型的执行顺序是:

  1. 使用无参数构造函数构造对象。

  2. 注入值。

  3. 调用标记为 @PostConstruct 的方法

如果使用构造函数注入,@PostConstruct 就不需要了。管理中的 bean 的行为就像其它 Java 类一样,还更安全。还有个好外,测试时不再需要依赖的注入框架;我们只需要把依赖项传递给构造器就好。

KittenEntity 的无参数构造函数可以删除了。临时的默认值也不再需要了。


Jackson Kotlin 插件


这不是一个 Kotlin 编译器插件,而是 Jackson 插件。除了支持 Koltin 类型,它还能自动绑定解析的数据和构造器。由于不需要 setters,所以类可以是不可变的。像我们在上一篇文章中提到的那样,如果我们想在绑定中使用构造器,就必须为每个参数指定 JSON 属性名:

在 Java 8 以前,字节码不包含构造器参数的名称,所以框架不能进行自动绑定。Java 8 和 Kotlin 编译器添加了更多元数据在字节码中,框架可以用它们来进行映射。现在声明变短了:

给 Java 编译器指定一个特殊的参数也可以完成同样的事情,但这个参数默认是关闭的。

使用这个插件需要添加两个文件:build.grade 和 RestApplication.kt。它需要 “kotlin-reflect” 插件才能工作。

build.gradle: 添加 “jackson-datatype-kotlin” 和 “kotlin-reflect” 作为依赖项:

RestApplication.kt: 注册 KotlinModule:

注意,现在 Kotlin 不能重载 getters 函数;如果你在 RestApplication 把类和单例声明为 public val,编译器会抱怨(complain)。你必须把属性声明为 private 并通过重载提供 getters。

最后,项目看起来就像这样:https://github.com/zeljkot/fables-kotlin/tree/master/jee/plugins


小结


具有可配置规则的模块提供了“编码公约”特性,这不只是减少了输入,还减少了与 Java EE 框架的冲突。另外,我们可以强制 JavaBean 的行为和标准的 Java 类一样,这样它们的可读性更强,也更容易在一起运作。我们可以更多地以函数式风格来编写代码。

这个项目看起来好多了,但是仍然是 Kotlin 文件中的 Java 代码。本系列的下一个部分的内容中,关于 Java EE 的会少一些,更多是与语言相关的内容:所有类都会被转换为符合 Kotlin 风格的代码。



推荐阅读

NGINX 开发指南(Part 1)

Kotlin 和 Java EE 系列之—— 如何让 Kotlin 类对 Java EE 友好

倾力推荐,学习 Kotlin 的 20 个实用资源

TIOBE 6 月编程语言排行榜:Kotlin 突围进入 50 强

“放码过来”邀您亮“项”,一不小心就火了!

点击“阅读原文”查看更多精彩内容

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存