Kotlin and Java EE: Part Two - Having Fun with Plugins

Kotlin and Java EE Part Two - Having Fun with Plugins

In the previous installment of the series, we saw that, while it is easy to convert Java to Kotlin, a lot of additional work must be done to make Kotlin Java EE compatible. It is manual work and it is error-prone, mostly due to friction between JavaBeans specification and Kotlin. JavaBeans is an old standard, literary from the last millennium. It was conceived to make component manipulation in RAD visual editors possible. For example, the user would drag and drop text field from the toolbar to the form, and then he would set the text, color, and other property. The component had to be constructed in uninitialized state and configured step-by-step.

In a non-GUI world, this concept has many drawbacks: component does not know when the configuration is finished, and the user does not know which properties must be set to complete the configuration.

With dependency injection (DI) frameworks, such properties would be automatically populated by the framework. Simple @Inject in front of the variable would find the correct bean. Although solution looked elegant at the time, it is a workaround for proper object construction. It also complicates testing as injection must be reconfigured to use mock beans.

Luckily, Kotlin compiler supports plugins, which can simulate proper object structure and proper object construction. Plugins can change the way how compiled classes look like. They will automatize the Java EE alignment and allow some other cool tricks.

Example project before improvements can be found on GitHub. It is a result of changes from the previous part of the series.

“all-open” Compiler Plugin

Part of service class conversion is making classes and all non-public members (both methods and properties) open. It is enough to forget one method and service will fail to initialize in Java EE container.

We will get rid of open statements sprinkled all over the place using the “all-open” plugin. Add the plugin, and enumerate annotations of the elements that should be open in the plugin’s configuration section. In this case, we want that for JAX-RS and data components, so add @Path and @Stateless annotations:

plugins {
    id "org.jetbrains.kotlin.jvm" version '1.1.1'
    id "org.jetbrains.kotlin.plugin.allopen" version '1.1.1'
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-allopen'
...

allOpen {
    annotation("javax.ws.rs.Path")
    annotation("javax.ejb.Stateless")
}

Now you can remove all open statements from the code.

“no-arg” and “jpa” Compiler Plugins

Rather than injecting the dependencies later, we will pass them to the constructor. After the constructor is called, the instance will be fully initialized and no further updates will be made; the object can be immutable from source code point of view. This will give a nice functional feel to the class. But how to do that when beans must have a parameterless constructor?

“no-arg” compiler plugin will add a parameterless constructor to the compiled code. There will be no visible parameterless constructor in source code. That will force users to construct the object in an immutable way. However, frameworks will have a secret constructor of their own. It can be configured in the same way as the all-open plugin. There are also preconfigured versions for Spring and JPA, where JPA works on all classes annotated as @Entity or @Embeddable. Those additional configurations are part of the same plugin as the regular no-arg one, so it is not necessary to add them both, but I will for the sake of the completeness:

plugins {
    ...
    id "org.jetbrains.kotlin.plugin.jpa" version '1.1.1'
    id "org.jetbrains.kotlin.plugin.noarg" version '1.1.1'
}

...

apply plugin: 'kotlin-jpa'
apply plugin: 'kotlin-noarg'
...

noArg {
    annotation("javax.ws.rs.Path")
    annotation("javax.ejb.Stateless")
}
...

Now we can remove all parameterless constructors, and do some interesting things like making service classes immutable. But if the class has an @Injected constructor, why does it need parameterless one, too? I was really puzzled by this requirement. Java cannot call two constructors on the same object, right? In the case of the stateless beans, it seems that one instance is constructed during initialization of the bean using parameterless constructor, and all other instances, one per call, are constructed with parametrized constructor with injected values.

Go to KittenRestService and change beginning of the class from:

@Path("kitten")
class KittenRestService {

    @Inject
    private lateinit var kittenBusinessService: KittenBusinessService

    ...

to

@Path("kitten")
class KittenRestService @Inject constructor(
        private val kittenBusinessService: KittenBusinessService) {

    ...

Our var became val, we got rid of lateinit, and class is fully initialized during construction through constructor parameters.

IMPORTANT! In some cases lateinit does not work as it should; constructor initialization seems to always work correctly. Prefer constructor parameters over lateinit.

An additional benefit is avoiding problems during bean initialization: accessing injected fields from the parameterless constructor will cause errors because values are not yet there; that is why @PostConstruct annotation was introduced. The typical order of execution is:

  1. Construct object with a parameterless constructor.
  2. Inject values.
  3. Call method marked as @PostConstruct.

With constructor injection, @PostConstruct is not needed any more. Managed beans behave like any other Java class, and become a bit safer. As a bonus, we do not need dependency injection framework for testing; we can simply pass dependencies to the constructor.

The parameterless constructor can be also removed from KittenEntity. Temporary default values are not needed any more.

jackson-module-kotlin

This is not a Kotlin compiler plugin, but rather a Jackson module. In addition to Kotlin types support, it provides an automatic binding between parsed data and constructor. Setters are not needed, so the class can become immutable. As we saw in the previous article, if we want to use the constructor for binding, we must specify the JSON property name of the each parameter:

data class KittenRest(
        @param:JsonProperty("name") override val name: String,
        @param:JsonProperty("cuteness") override val cuteness: Int
) : Kitten

Before Java 8, bytecode did not contain names of the constructor parameters, so frameworks could not bind them automatically. Java 8 and Kotlin compiler add more metadata to the bytecode, which can be used by frameworks to do the mapping. Now declaration can be shortened to:

data class KittenRest(
        override val name: String,
        override val cuteness: Int
) : Kitten

Same can be done with the special parameter of Java compiler, but it is not enabled by default.

This plug-in must be added to two files to be enabled: build.gradle and RestApplication.kt. It requires the kotlin-reflect plugin to work.

build.gradle: add “jackson-datatype-kotlin” and “kotlin-reflect” as dependencies:

dependencies {
    compile("com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version")
    compile("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"   
    ...
}

RestApplication.kt: register KotlinModule:

@ApplicationPath("api")
class RestApplication : Application() {

    private val classes = setOf(KittenRestService::class.java)

    private val singletons = setOf(MyContextResolver())

    override fun getClasses() = classes

    override fun getSingletons() = singletons
}

class MyContextResolver : ContextResolver<ObjectMapper> {

    val objectMapper = ObjectMapper()

    init {
        objectMapper.registerModule(KotlinModule())
    }

    override fun getContext(p0: Class<*>?) = objectMapper
}

Note that currently, Kotlin cannot override functions with getters; if you declare classes and singletons from RestApplication as public val, the compiler will complain. You must declare properties private and provide getters with override.

At the end, the project should look similar to this.

Conclusion

Modules with their configurable rules provide different “convention over coding” features, which does not mean just less typing, but also less fighting with Java EE frameworks. As a bonus, we can force JavaBeans to behave like standard Java classes, which will make them more reliable and easier to work with, and code in a more functional style.

The project looks better, but it is still Java code in Kotlin files. Next part of the series will be less about Java EE and more about language: all classes will be converted to the idiomatic Kotlin code!

Comments

Popular Posts

Intention of Kotlin's "also apply let run with"

Kotlin and Java EE: Part One - From Java to Kotlin