Статьи

Kotlin + Guice Пример

Хотя язык программирования Kotlin готовится к общедоступной программе раннего доступа, я хочу поделиться с вами одним примером того, как легко его можно использовать с существующими базами кода Java.

Эта короткая заметка не предназначена для того, чтобы научить читателя, как использовать Kotlin, но только для того, чтобы он / она заинтересовался обучением.

Мы создадим небольшое игрушечное приложение, используя Kotlin и очаровательный фреймворк Guice.   Приложение не более чем обычное «Hello, World!» но он содержит службу приложений и службу регистрации, и каждая служба будет иметь два воплощения — для тестирования и для производственного использования.

Давайте начнем с абстрактного LoggingService. Это будет абстрактный класс только с одним абстрактным методом для вывода переданной строки. Скорее всего, приведенный ниже код не требует особых пояснений.

abstract class LoggingService() {
    abstract fun info(message: String?)
}

Теперь мы определим простую реализацию, которая выводит данное сообщение в стандартный вывод

class StdoutLoggingService() : LoggingService(){
    override fun info(message: String?) = println("INFO: $message")
}

Модификатор override сообщает компилятору, что метод переопределяет некоторый метод суперкласса.

Обратите внимание также на строку интерполяции


Теперь мы создадим немного более сложную реализацию, которую мы будем использовать в производстве ?

class JdkLoggingService [Inject] (protected val jdkLogger: Logger) : LoggingService() {
    override fun info(message: String?)  = jdkLogger.info(message)
}

[Inject] аннотация на конструкторе сообщает Guice, как создать JdkLoggerService.

val в параметре конструктора просит создать конечное свойство, инициализированное данным значением. var запрашивает не окончательный, и ни один из них не определяет простой параметр конструктора


Теперь мы готовы определить абстрактный сервис приложения и обе его реализации для тестирования и производственного использования.

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")

Обратите внимание, как элегантно Kotlin позволяет нам определять свойства и передавать значения суперконструктору 

Теперь перейдем к более интересной части — созданию min-DSL для Guice. Но давайте начнем с того, как мы хотим, чтобы наше приложение выглядело.

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()
}

Здесь есть несколько вещей, на которые стоит обратить внимание

  1. Мы используем локальную функцию для настройки инъекций. В этом нет необходимости, но разрешите группировку кода.
  2. Возвращаемый тип функции configureInjection выводится из ее тела (вызов инжектора finction — корня нашего DSL, который мы вскоре определим)
  3. Мы вызываем configureInjection, используя синтаксис именованных аргументов. Это не обязательно в нашем случае, но очень удобно, когда у нас много параметров, и некоторые из них являются необязательными.


Теперь пришло время определить простые части нашего DSL.
Это будет три функции расширения для некоторых классов Guice.

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>())
  1. Пожалуйста, прочитайте о функциях расширения в документации Kotlin
  2. Заметка в дисперсии, используемая в функции «to». Пожалуйста, прочитайте о дженериках и отклонениях в документации Kotlin. Это действительно интересно
  3. Вызов javaClass <T> () — это стандартная библиотека Kotlin для Java (помните, что Kotlin можно скомпилировать и для других платформ, таких как javascript), эквивалентна T.class в Java. Огромная разница заключается в том, что в Kotlin он применим не только к реальным классам, но и к параметрам универсального типа (благодаря информации о типах во время выполнения).
  4. Вызов метода sure () является способом Kotlin (который вскоре будет заменен специальной языковой конструкцией), чтобы обеспечить ненулевое значение. Поскольку в Java нет обозначений обнуляемых и ненулевых типов, нам нужно позаботиться о точке интеграции. Пожалуйста, прочитайте удивительную историю нулевой безопасности в документации Kotlin.

Наконец мы готовы к самой сложной части — определению метода инжектора.

fun injector(config: ModuleCollector.() -> Any?) : Injector {
    val collector = ModuleCollector()
    collector.config()
    return Guice.createInjector(collector.collected).sure()
}

У него есть один параметр типа ModuleCollector. () -> Любой? что означает «функцию расширения для ModuleCollector (мы определим ModuleCollector несколькими строками ниже), которая не принимает никаких параметров и возвращает значение любого типа, который может быть потенциально обнуляемым»

Реализация очень проста

  1. мы создаем ModuleCollector
  2. мы используем его как приемник для вызова данной функции конфигурации
  3. мы просим Guice создать Injector для всех настроенных модулей ( подробности см. в документации Guice

Это чрезвычайно мощный метод определения DSL в Kotlin

Как мы видели выше, мы вызываем метод инжектор с выражением функционального блока.

Тот факт, что параметр является функцией расширения, делает все (по модулю правил видимости) методы ModuleCollector доступными внутри функционального блока.

Последняя часть нашего DSL — это определение ModuleCollector.

Содержит внутренний список собранных модулей и только два метода

  • метод module — использует вариацию одного и того же трюка «функция-расширение-параметр» — мы используем функцию расширения для реализации метода анонимного класса.

Пожалуйста, обратитесь к документации Kotlin по объектным выражениям

  • метод плюс — переопределяет унарную операцию плюс

Пожалуйста, обратитесь к документации Kotlin по перегрузке оператора .

Очень важно отметить, что этот метод является и методом расширения, и членом класса ModuleCollector. Это позволяет очень хорошо ловить контекст на сайте вызова. В нашем случае выше мы вызываем этот метод внутри функции расширения с приемником ModuleCollectoror и применяем его к модулю, созданному путем вызова метода 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)
    }
}

Мы сделали. Я надеюсь, что это было интересно. Эта заметка является очень кратким и не подробным введением о вкусностях Kotlin. Если это побудило вас прочитать больше о Kotlin, тогда моя цель достигнута.

Спасибо и до следующего раза!