Kodein
然后添加依赖项:
• 使用Gradle 6+
dependencies {
implementation ‘org.kodein.di:kodein-di:7.4.0’
}
从6.3.0在JVM上,你必须把目标定JDK 1.8最低!
compileKotlin {
kotlinOptions.jvmTarget = “1.8”
}
在Kodein-DI中,绑定是在DI块中声明的。语法非常简单:
示例:DI容器的初始化
val kodein = DI {
bind<Random>() with provider { SecureRandom() }
bind<Database>() with singleton { SQLiteDatabase() }
}
Kodein-DI提供可管理的实现许多绑定:provider,singleton,factory,multiton,instance,和更多
• Provider binding : 每次加载都生成新的实例,无参, 传入() -> T
• Singleton binding : 单例 传入 () → T
• Eager singleton : 单例 创建了 Kodein 实例后立即实例化 传入 () -> T
• Factory binding : 每次加载都生成新的实例,需要参数,传入(A) -> T
• Multiton binding : 有参的单例,同样参数同样实例,传入 (A) -> T
• Tagged bindings : 通过 tag 来区分同类型不同的实例
,Kodein-DI允许您tag绑定:添加其他信息以对其进行标记。
在使用中如果使用了tag则在调用时候也要带上tag
示例:标记的绑定
val kodein = DI {
bind<Database>(tag = "local") with singleton { SQLiteDatabase() }
bind<Database>(tag = "remote") with provider { DatabaseProxy() }
}
分离
Kodein-DI允许您定义绑定模块,以后可以将其导入DI容器中:
示例:DI模块
val module = DI.Module {
bind<Database>(tag = "local") with singleton { SQLiteDatabase() }
bind<Database>(tag = "remote") with provider { DatabaseProxy() }
}
示例:导入DI模块
val di = DI {
import(module)
}
注入与检索
Kodein-DI支持两种不同的方法来允许业务单位访问其依赖关系:注入和检索。
• 注入依赖项时,将在构造时为该类提供其依赖项。
• 当依赖性检索,类是负责对获得自己的依赖。
从某种意义上来说,依赖注入更为纯净,因为注入的类将在构造过程中传递其依赖,因此对DI一无所知。但是,它比较麻烦,并且不提供很多功能。
相反,依赖检索更容易且功能齐全,但是它要求类了解DI。
注射
如果要注入类,则需要在构造时声明其依赖项:
示例:在构造上具有依赖关系的类
class Presenter(private val db: Database, private val rnd: Random) {
}
现在,您需要能够创建此类的新实例Presenter。使用Kodein-DI,这非常容易:
示例:创建一个注入类的对象
val presenter by di.newInstance { Presenter(instance(), instance()) }
对于Presenter构造函数的每个参数,您都可以简单地使用该instance()函数,而Kodein-DI实际上将传递正确的依赖关系。
Retrieval
使用检索时,该类需要可用于静态或通过参数访问DI实例。在这些示例中,我们将使用参数。
示例:一个检索其自身依赖关系的类
class Presenter(val di: DI) {
private val db: Database by di.instance()
private val rnd: Random by di.instance()
}
注意by关键字的用法。使用依赖关系检索时,会延迟检索属性。
您可以进一步使用该DIAware界面,该界面可以解锁许多功能:
示例:KodeinAware类 或是DIAware
class Presenter(override val di: DI): DIAware{
private val db: Database by instance()
private val rnd: Random by instance()
}
请注意,由于默认情况下所有内容都是惰性的,因此对类中DI对象的访问KodeinAware本身可以是惰性的:
示例:带有惰性DI的KodeinAware类
class Presenter(): KodeinAware {
override val di by lazy { getApplicationContext().di }
private val db: Database by instance()
private val rnd: Random by instance()
}
Retrieval:direct
如果您不想使用委托的属性,则可以使用Kodein-DI。大多数可用的功能DI可供DirectDI。 DirectDI使您可以直接获取新实例或依赖项。
然而,因为它是直接的,DirectDI也不要特点:
• 惰性:在调用时获取实例/提供者/工厂。
• 接收者意识:接收者是由Kotlin的委托属性机制定义的。
示例:使用DirectDI
val directDI = di.direct
val ds: Datasource = directDI.instance()
val controller = directDI.newInstance { MainController(instance(), instance(tag = "whatever")) }
示例:使用DirectDI
val di = DI.direct {
/* bindings */
}
传递依存关系
假设我们要Provider在绑定中声明。它具有自己的依赖性。依赖性的依赖性是传递性依赖性。处理这些依赖关系实际上非常容易。
如果使用注入,则可以完全相同的方式传递参数:
示例:将Presenter类与注入绑定
val di = DI {
bind<Presenter>() with singleton { Presenter(instance(), instance()) }
}
如果使用检索,只需传递di属性:
示例:将Presenter类与注入绑定
val di = DI {
bind<Presenter>() with singleton { Presenter(di) }
}
平台兼容性和通用性
Kodein-DI 7.4.0建立在Kotlin 1.4.31之上并与之兼容。
另外,对于JVM项目,它至少需要JDK 1.8。
Kodein-DI在Ktor上
安装
implementation(“org.kodein.di:kodein-di-framework-ktor-server-jvm:7.1.0”)
DIFeature
由于Ktor应用程序基于扩展,因此我们无法DIAware在其上使用该机制。因此,我们必须找到另一种优雅的方式来提供全局DI容器。那就是DIFeature(通过扩展功能Application.di())。
示例:Ktor应用程序声明,安装 DIFeature
fun main(args: Array<String>) {
embeddedServer(Netty, port = 8080) {
di {
bind<Random>() with singleton { SecureRandom() }
}
}.start(true)
}
示例:Ktor应用程序声明,安装DIFeature,然后从路由中检索它
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)
}
subDI为Routing/Route类提供了一个功能,仅在Route及其子项中可用
route("/login") {
subDI {
bind<CredentialsDao> with singleton { CredentialsDao() }
}
}
复制绑定
subDI(copy = Copy.All) {
/** new bindings / overrides **/
}
覆盖绑定
//覆盖Foo绑定
subDI {
bind<Foo>(overrides = true) with provider { Foo("explicitFoo") }
}
//覆盖subDI将是隐式的
subDI(allowSilentOverrides = true) {
bind<Foo> with provider { Foo("implicitFoo") }
}
subDI的扩展父实例
此功能仅限于Routing/ Route,可以像以下方式使用:
示例:从多个地方扩展
// https://ktor.io/servers/features/routing.html[Routing]
routing {
/* usage */
val subDI = subDI { /** new bindings / overrides **/ } //1扩展最近的DI实例,很可能是应用程序的实例
route("/books") {
/* usage */
subDI { /** new bindings / overrides **/ } //2扩展最近的DI实例,即在<1>中创建的实例
route("/author") {
/* usage */
subDI { /** new bindings / overrides **/ } //3扩展最近的DI实例,即在<2>中创建的实例
}
}
}
Ktor scopes
Session scopes
1.通过实现定义会话 DISession
示例:定义会话
data class UserSession(val user: User) : KodeinDISession {
override fun getSessionId() = user.id
}
创建实现的会话对象 KodeinDISession
实现功能 getSessionId()
2.定义范围依赖
示例:定义会话范围的依赖项
fun main(args: Array<String>) {
embeddedServer(Netty, port = 8000) {
install(Sessions) {
cookie<UserSession>("SESSION_FEATURE_SESSION_ID")
}
di {
bind<Random>() with scoped(SessionScope).singleton { SecureRandom() }
/* binding */
}
}.start(true)
}
安装Sessions功能
声明以表示的会话cookie UserSession
绑定Random对象的范围SessionScope
3.检索范围内的依赖项
示例:检索会话范围的依赖项
embeddedServer(Netty, port = 8000) {
/* configurations */
routing {
get("/random") {
val session = call.sessions.get<UserSession>() ?: error("no session found!")
val random by di().on(session).instance<Random>()
call.responText("Hello ${session.user.name}, your random number is ${random.nextInt()}")
}
}
}.start(true)
session从请求上下文中检索,否则将失败
Random从DI范围为以下对象的对象中检索对象session
4.只要不再使用会话,请清除作用域
示例:清除会话和作用域
get("/clear") {
call.sessions.clearSessionScope()// 清除会话并删除ScopeRegistry链接到该会话的链接
}
Call scope
val di = DI {
bind<Random>() with scoped(CallScope).singleton { SecureRandom() }
}
Random将为每个请求(HTTP或Websocket)创建一个对象,只要该请求存在,就将对其进行检索。
get {
val random by di().on(context).instance<Random>()
}
DI Controllers
安装
implementation(“org.kodein.di:kodein-di-framework-ktor-server-controller-jvm:7.4.0”)
在kodein-di-framework-ktor-server-controller-jvm已经拥有kodein-di-framework-ktor-server-jvm的传递依赖,所以你不需要声明两种。
class MyController(application: Application) : DIController {
override val di by di { application }
//implement 从 DIaware中override di
private val repository: DataRepository by instance("dao")
override fun Route.getRoutes() {
get("/version") {
val version: String by instance("version")
call.respondText(version)
}
}
}
1实现DIController并提供Application实例(来自构造函数)
2DI根据提供的内容覆盖容器Application
3DI像任何DIAwareclass一样使用您的容器
4覆盖功能Route.getRoutes并定义一些路线
5该路线将由 DIControllerFeature
6DI像任何DIAwareclass一样使用您的容器
扩展AbstractDIController
class MyController(application: Application) : AbstractDIController(application) {
private val repository: DataRepository by instance("dao")
override fun Routing.installRoutes() {
get("/version") {
val version: String by instance("version")
call.respondText(version)
}
}
}
1扩展AbstractDIController并提供Application实例(来自构造函数)
2DI像任何DIAwareclass一样使用您的容器
3覆盖功能Routing.installRoutes并定义一些路线
4该路线将由 DIControllerFeature
5DI像任何DIAwareclass一样使用您的容器
使用DIController或AbstractDIController取决于您的需求。
如果您不需要在控制器上使用继承,那么您可以从中受益AbstractDIController。
相反,如果要对控制器使用继承,则应自己实现DIController和覆盖DI容器。
public abstract class AbstractDIController(private val application: Application) : DIController {
public override val di: DI by di { application }
}
以使用Route.controller扩展功能。这些功能将自动将您定义的路由安装DIController到Ktor路由系统中
controller("/protected") { MySecondDIController(instance()) }
测试代码
val kodein = DI{
bind<Int>(tag = "demo") with provider { Random().nextInt(111) }
bind<Random>() with singleton { Random() }
bind<presenter2>() with singleton { presenter2(instance()) }
}
class presenter(kodein:DI){
public val rnd: Int by kodein.instance(tag = "demo")
}
class presenter2( private val rnd: Random) {
fun println(){
println(rnd.nextInt())
}
}
class presenter3(override val di: DI):DIAware{
public val rnd: Random by instance()
}
fun main(args: Array<String>) {
// println(presenter.rnd.nextInt(10))
println(presenter(kodein).rnd)
println(presenter(kodein).rnd)
val presenter2 by kodein.newInstance { presenter2(instance()) }
presenter2.println()
println(presenter3(kodein).rnd.nextInt())
val presenter3 by kodein.instance<presenter2>()
presenter3.println()
}