文档讨论了依赖注入,但并没有真正展示它是如何完成的。
文档也没有完成,并且有很多占位符: http://ktor.io/getting-started.html
我试图以接受参数(这是我的依赖)的方式创建我的main函数,但是当我调用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)
如果我使用参数(我需要模拟和注入的参数)调用withTestApplication
,上述myMain
将失败。
更新
问题是,在我的请求处理中,我正在使用连接到外部其他Web服务的依赖类并执行一些请求,我需要一种方法能够注入这个,所以在我的测试中我可以存根/模拟它并根据我的测试用例改变其行为。
答案 0 :(得分:7)
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会热切地创建它,因为没有其他服务会查询它。
答案 1 :(得分:1)
使用 Koin
的简单示例1)首先,定义我们的prod和test依赖项:
val prodModule = module {
single<IFirstService> { RealFirstService() }
single<ISecondService> { RealSecondService() }
}
val testModule = module {
single<IFirstService> { FakeFirstService() }
single<ISecondService> { FakeSecondService() }
}
2)然后在应用启动前添加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
}
仅此而已!
答案 2 :(得分:0)
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)
}
答案 3 :(得分:0)
在对 koin kodein 和 daggers 进行了一些试验后,我们最终将 spring-context 与 ktor 结合使用。它就像一个魅力。
第 1 步:在我们的 gradle 文件中:
implementation(group = "org.springframework", name = "spring-context", version = "5.3.5")
随意使用您喜欢的任何弹簧上下文版本进行更改。
第 2 步:我们像定义任何 Spring 应用程序一样定义了 @Component
、@Configuration
、@Bean
等。
第 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 方式相互引用的组件组成。