Kotlin + Guice Example
Join the DZone community and get the full member experience.
Join For FreeWhile Kotlin programming language prepares for the public early access program I want to share with you one an example how easly it can be used with existing Java codebases.
This short note does not intend to teach the reader how to use Kotlin but only to make him/her intersted to learn.
We will create small toy application using Kotlin and the charming Guice framework. The application is nothing more than the usual "Hello, World!" but it contains an application service and logging service and each service will have two incarnations - for testing and for production use.
Let us start with an abstract LoggingService. It will be an abstract class with only one abstract method to output the passed string. Most probably the code below does not require much explaination.
abstract class LoggingService() { abstract fun info(message: String?) }
Now we will define a simple implementation, which prints the given message to the standard output
class StdoutLoggingService() : LoggingService(){ override fun info(message: String?) = println("INFO: $message") }
override modifier tells the compiler that the method overrides some method of a superclass.
Note also string interpolation
Now we will create a little more sophisticated implementation, which we will use in production :)
class JdkLoggingService [Inject] (protected val jdkLogger: Logger) : LoggingService() { override fun info(message: String?) = jdkLogger.info(message) }
[Inject] annotation on constructor tells Guice how to create JdkLoggerService.
val on constructor parameter asks to create final property initialized by given value. var asks for non-final one and neither of them define simple constructor parameter
Now we are ready to define the abstract application service and both implementations of it for test and production use.
abstract class AppService(protected val logger: LoggingService, protected val mode: String) { fun run() { logger.info("Application started in '$mode' mode with ${logger.javaClass.getName()} logger") logger.info("Hello, World!") } } class RealAppService [Inject] (logger: LoggingService) : AppService(logger, "real") class TestAppService [Inject] (logger: LoggingService) : AppService(logger, "test")
Note how elegantly Kotlin allows us to define properties and pass values to superconstructor
Now we go to more interesting part - creating min-DSL for Guice. But let us start with how we want our application to look like.
fun main(args: Array<String>) { fun configureInjections(test: Boolean) = injector { +module { if(test) { bind<LoggingService>().toInstance (StdoutLoggingService()) bind<AppService>().to<TestAppService> () } else { bind<LoggingService>().to<JdkLoggingService>() bind<AppService>().to<RealAppService> () } } } configureInjections(test=false).getInstance<AppService> ().run() }
There are few things to notice here
- We use local function to configure injections. It is not necessary but allow some nice code grouping.
- Return type of function configureInjections is inferred from it's body (call to finction injector - root of our DSL which we will define soon)
- We call configureInjections using named arguments syntax. It is not necessary in our case but very convinient when we have many parameters and some of them are optional.
Now it is time to define simple parts of our DSL. It will be three extension functions for some Guice classes.
fun <T> Binder.bind() = bind(javaClass<T>()).sure() fun <T> AnnotatedBindingBuilder<in T>.to() = to(javaClass<T>()).sure() fun <T> Injector.getInstance() = getInstance(javaClass<T>())
- Please read about extension functions in Kotlin documentation
- Note in variance used in function 'to'. Please read about generics and variance in Kotlin documentation. It is really interesting
- javaClass<T>() call is Kotlin standard library for Java (remember that Kotlin can be compiled for other platforms like javascript as well) is equivalent to T.class in Java. The huge difference here is that in Kotlin it applicable not only to real classes but also to generic type parameters (thanks to runtime type information).
- Call to method sure() is Kotlin way (soon to be replaced by special language construct) to ensure non-nullability. As Java has not notation of nullable and non-nullable types we need to take some care on integration point. Please read amazing story of null-safety in Kotlin documentation.
Finally we are ready to most complex part - definition of method injector.
fun injector(config: ModuleCollector.() -> Any?) : Injector { val collector = ModuleCollector() collector.config() return Guice.createInjector(collector.collected).sure() }
It has one parameter of type ModuleCollector.()->Any? which means "extension function for ModuleCollector(we will define ModuleCollector few lines below), which does not accept any parameters and returns value of any type potentially nullable"
The implementation is very straight-forward
- we create ModuleCollector
- we use it as receiver to call given configuration function
- we ask Guice to create Injector for all modules configured (consult Guice documentation for details)
This is extremely powerful method of defining DSLs in Kotlin
As we saw above we call method injector with function block expression.
The fact that parameter is extension function makes all (modulo visibility rules) methods of ModuleCollector available inside function block.
The last part of our DSL is definition of ModuleCollector.
It contains internal list of collected modules and only two methods
- method module - uses variation of the same extension-function-as-parameter trick - we use extension function to implement method of anonimous class.
Please consult Kotlin documentation on object expressions
- method plus - overrides unary plus operation
Please consult Kotlin documentation on operator overloading.
It is very important to note that this method is both extension method and member of class ModuleCollector. It allows very good context catching on call site. In our case above we call this method inside extention function with ModuleCollectoror receiver and apply it to Module created by call to method Module.
class ModuleCollector() { val collected = ArrayList<Module> () fun module (config: Binder.()->Any?) : Module = object: Module { override fun configure(binder: Binder?) { binder.sure().config() } } fun Module.plus() { collected.add(this) } }
We are done. I hope that was interesting. This note is a very brief and non-detailed introduction on goodies of Kotlin. If this has motivated you to read more about Kotlin then my goal is achieved.
Thank you and till next time!
Opinions expressed by DZone contributors are their own.
Comments