/** * Marker interface for custom injector keys, useful to encode object type into the key. * * @tparam T type of object returned by this key */ trait InjectorKey[T] /** * The injector - main interface used to bind code to this injector and access stored objects directly */ trait Injector { /** * Bind this injector to current thread. * @param func code that will be bound to this injector * @tparam T return type * @return value, returned by func */ def let[T](func: => T): T def getInstance[T](classTag: ClassTag[T]): Option[T] def getInstance[T](key: Any, classTag: ClassTag[T]): Option[T] /** * Initialize all known dependencies eagerly. Good for production mode and validation of dependencies. */ def eagerInit(): Unit } object Injector { private[depend] val context = new ThreadLocal[Injector]() /** * Get object instance by type. Fails if this type cannot be resolved to single instance * * @param classTag class tag * @tparam T type of value to return * @return value */ def inject[T](implicit classTag: ClassTag[T]): T = injector.getInstance(classTag).getOrElse { throw new InjectorException("No instance registered for " + classTag) } /** * Get object instance by type and some type-assisted key. Useful if you have multiple instances of the same type. * * @param key key, used to identify object * @param classTag class tag * @tparam T type of value to return * @return value */ def inject[T](key: InjectorKey[T])(implicit classTag: ClassTag[T]): T = injector.getInstance(key, classTag).getOrElse { throw new InjectorException("No instance registered for " + classTag + ", key=" + key) } /** * Get object instance by type and some key. Useful if you have multiple instances of the same type. * * @param key key, used to identify object * @param classTag class tag * @tparam T type of value to return * @return value */ def inject[T](key: Any)(implicit classTag: ClassTag[T]): T = injector.getInstance(key, classTag).getOrElse { throw new InjectorException("No instance registered for " + classTag + ", key=" + key) } /** * Return current injector * @return current injector or exception */ def injector: Injector = { val r = context.get() if (r == null) { throw new IllegalStateException("There is no injector in current context. Forgot to run Injector.let for this thread?") } r } }
class InjectorTest extends FunSuite { import Injector.inject test("basic query by type") { val i = Injector.newModule() .bind[String]("hello") .injector() i.let { assert(inject[String] == "hello") //no object of type Date intercept[InjectorException] { inject[Date] } //we have no keys intercept[InjectorException] { inject[String]("hello") } } } test("objects are singletons") { class A val i = Injector.newModule() .bind[Date](new Date()) .bind[A]("key")(new A) .injector() i.let { assert(inject[Date] eq inject[Date]) assert(inject[A]("key") eq inject[A]("key")) } } test("query by key works") { val i = Injector.newModule() .bind[Date]("key1")(new Date(1)) .bind[Date]("key2")(new Date(2)) .injector() i.let { assert(inject[Date]("key1") == new Date(1)) assert(inject[Date]("key2") == new Date(2)) intercept[InjectorException] { inject[String]("key1") } intercept[InjectorException] { inject[Date]("key3") } } } test("object cannot be bound twice") { intercept[InjectorException] { Injector.newModule() .bind[Date](new Date(0)) .bind[Date](new Date(1)) } } test("Binding with dependencies works") { class A class B case class C(a: A, b: B) val i = Injector.newModule() .bind[C](C(inject[A], inject[B])) .bind[A](new A) .bind[B](new B) .injector() i.let { assert(inject[C].a eq inject[A]) assert(inject[C].b eq inject[B]) } } test("Binding with cyclic dependencies does not work") { case class A(c: C) class B case class C(a: A, b: B) val i = Injector.newModule() .bind[C]{ C(inject[A], inject[B]) } .bind[A](A(inject[C])) .bind[B](new B) .injector() i.let { intercept[InjectorException] { inject[C] } } } test("modularization") { class A class B case class C(a: A, b: B) val m1 = Injector.newModule().bind[C](C(inject[A], inject[B])) val m2 = Injector.newModule() .bind[A](new A) .bind[B](new B) m1.injector().let { intercept[InjectorException] { inject[C] } } (m1 + m2).injector().let { inject[C] } } test("It is possible to inject primitive type") { val i = Injector.newModule() .bind[Int](42) .injector() i.let { assert(inject[Int] == 42) } } }
Source: https://habr.com/ru/post/278469/
All Articles