【问题标题】:How to inject dependencies in a ktor Application如何在 ktor 应用程序中注入依赖项
【发布时间】:2018-02-21 10:26:05
【问题描述】:

文档讨论了依赖注入,但并未真正说明它是如何完成的。

文档也没有完成,并且有一堆占位符: http://ktor.io/getting-started.html

我尝试以接受参数(这是我的依赖项)的方式创建我的主函数,但是当我调用 withTestApplication 时,它在测试端失败了。 我查看了应用程序代码,发现 Application 接受了一个配置对象,但我不知道如何更改该配置对象以在其中注入一些依赖项。

package org.jetbrains.ktor.application

/**
 * Represents configured and running web application, capable of handling requests
 */
class Application(val environment: ApplicationEnvironment) : ApplicationCallPipeline() {
    /**
     * Called by host when [Application] is terminated
     */
    fun dispose() {
        uninstallAllFeatures()
    }
}

/**
 * Convenience property to access log from application
 */
val Application.log get() = environment.log

在使用withTestApplication 的测试代码中,我有类似下面的内容:

@Test
internal fun myTest() = withTestApplication (Application::myMain)

如果我使用参数(我需要模拟和注入的参数)调用myMain,上述withTestApplication 将失败。

更新:

问题是,在我的请求处理中,我使用了一个连接到外部其他 Web 服务并执行一些请求的依赖类,我需要一种能够注入它的方法,因此在我的测试中我可以存根/模拟它并根据我的测试用例改变它的行为。

【问题讨论】:

    标签: java unit-testing dependency-injection kotlin ktor


    【解决方案1】:

    Ktor 没有内置的依赖注入机制。如果您需要使用 DI,则需要使用您喜欢的任何框架,例如 Guice。它看起来像这样:

    fun Application.module() {
      Guice.createInjector(MainModule(this))
    }
    
    // Main module, binds application and routes
    class MainModule(private val application: Application) : AbstractModule() {
        override fun configure() {
            bind(Application::class.java).toInstance(application)
            ... other bindings ...
        }
    }
    

    通过这种方式,您可以将应用程序组合委托给 Guice,并像任何其他应用程序一样构建它。例如。您可以像这样组成应用程序的不同部分:

    class Hello @Inject constructor(application: Application) {
      init {
        application.routing {
            get("/") {
                call.respondText("Hello")
            }
        }
      }
    }
    

    然后将其绑定到主模块中:

    bind(Hello::class.java).asEagerSingleton()
    

    asEagerSingleton 是必需的,这样 Guice 就会急切地创建它,因为没有其他服务会查询它。

    【讨论】:

    • 感谢您的响应,在我的情况下,我确实有一个与其他服务交互的类依赖项,我如何注入那个(所以当我编写测试时,我只是存根/模拟该依赖项并通过一个模拟实例而不是真实实例)
    【解决方案2】:

    Koin

    的简单示例

    1) 首先,定义我们的 prod 和 test 依赖项:

    val prodModule = module {
        single<IFirstService> { RealFirstService() }
        single<ISecondService> { RealSecondService() }
    }
    
    val testModule = module {
        single<IFirstService> { FakeFirstService() }
        single<ISecondService> { FakeSecondService() }
    }
    

    2) 然后在app启动前添加DI初始化:

    fun main(args: Array<String>) {
        startKoin(listOf(prodModule))
        embeddedServer(Netty, commandLineEnvironment(args)).start(true)
    }
    

    3) 在应用程序或路由中使用注入:

    fun Application.apiModule() {
        val firstService: IFirstService by inject()
        val secondService: ISecondService by inject()
        ...
        routing {
            someApi(inject(), inject())
        }
    }
    

    4) (可选) 对于测试,只需在运行测试之前在 testModule 中添加初始化:

    fun testApp(test: TestApplicationEngine.() -> Unit) {
        withTestApplication({
            ... // configure your test app here
    
            stopKoin() // Need to stop koin and restart after other tests
            startKoin(listOf(testModule)) // Init with test DI
    
            apiModule() // Run you application
        })
    }
    
    // And run tests
    @Test
    fun `get events`() = testApp {
        // do tests
    }
    

    就是这样!

    【讨论】:

      【解决方案3】:

      Kodein DI 具有良好的 Ktor 支持,请参阅corresponding documentation 了解更多详细信息。

      文档中的示例代码:

      fun main(args: Array<String>) {
          embeddedServer(Netty, port = 8080) {
              di { 
                  bind<Random>() with singleton { SecureRandom() } 
              }
      
              routing {
                  get("/") {
                      val random by di().instance<Random>() 
                      /* logic here */
                  }
              }
         }.start(true)
      }
      

      【讨论】:

        【解决方案4】:

        在对 koin kodein 和 daggers 进行了一些试验后,我们最终将 spring-context 与 ktor 结合使用。它就像一个魅力。

        第 1 步:在我们的 gradle 文件中:

        implementation(group = "org.springframework", name = "spring-context", version = "5.3.5")
        

        随意使用您喜欢的任何 spring-context 版本进行更改。

        第 2 步:我们定义了 @Components、@Configurations、@Beans 等,就像使用任何 Spring 应用程序一样。

        第 3 步:在我们的 main 方法中,我们有一点胶水代码来显式初始化 DI 上下文并使用它来初始化 ktor:

        val ctx = AnnotationConfigApplicationContext("YOUR.ROOT.PACKAGE.GOES.HERE")
        val someBean = ctx.getBean(SomeBean::class.java) // you can get any bean you need to use in your top-level glue code
        
        // ... go ahead with KTOR configuration as usual. You can access any bean using the ctx variable.
        

        当然,我们显式与 spring 上下文交互的胶水代码只执行一次。项目的其余部分由使用常规 spring-context 方式相互引用的组件组成。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-11
          • 2011-07-04
          • 2010-10-03
          • 2017-01-30
          • 1970-01-01
          相关资源
          最近更新 更多