Самая последняя версия MacWire (0.4) (макрос Scala для генерации связующего кода для создания экземпляров класса, замены контейнера DI) поставляется с новыми утилитами, которые облегчают интеграцию с каркасами, которые требуют поиска экземпляров по классам . Примером такой структуры является Play! , который довольно быстро набирает популярность в мире веб-разработки.
Есть два способа узнать, как сделать внедрение без контейнера в Play! (и другие веб-фреймворки) с MacWire:
- читать этот блог
- попробуйте MacWire + Play Typesafe Activator
Или вы можете сделать оба;)
МОТИВАЦИЯ
Многие веб-фреймворки требуют некоторой формы общего поиска экземпляров, например, когда на экземпляры контроллера ссылаются из представлений. В традиционных DI-контейнерах, таких как Guice , это обеспечивается из коробки, так как это основной способ получения проводных объектов. Например, в Guice у нас есть объект, Injector который переводит Classобъекты в экземпляры данного класса.
При использовании MacWire (или при «ручном» DI) мы делаем максимально возможный типобезопасный способ, просто ссылаясь на объекты как поля / методы в объекте-контейнере, поэтому нам обычно не нужны такие карты. Но для интеграции с веб-фреймворком это все еще необходимо.
ОБЩИЕ УТИЛИТЫ
MacWire 0.4 поставляется с двумя общими утилитами, полезными для поиска экземпляров по классам.
Первый — это макрос, valsByClass(object) который генерирует (конечно же, во время компиляции) карту vals в данном объекте с ключами их классов. Так, например:
object Example1 {
val aString = "Hello World!"
val userAuthenticator = new UserAuthenticator()
// here we are using the core MacWire feature: the wire macro
val userFinder = wire[UserFinder]
}
val theMap = valsByClass(Example1)
переведет следующий код:
val theMap = Map[Class[_], AnyRef]( classOf[String] -> Example1.aString, classOf[UserAuthenticator] -> Example1.userAuthenticator, classOf[UserFinder] -> Example1.userFinder )
Вторая утилита InstanceLookupрасширяет эту карту, чтобы разрешить поиск по подклассам / признакам. Например, скажем, у нас есть Database черта и реализация, MySQLDatabaseкоторая заканчивается на карте vals-by-class под Class[MySQLDatabase] ключом.
Тем не менее, мы все равно хотели бы получить экземпляр при запросе Database. Мы можем достичь этого, обернув карту InstanceLookup:
trait Database
class MySQLDatabase extends Database
object App {
val database = new MySQLDatabase()
}
val instanceLookup = InstanceLookup(valsByClass(App))
require(instanceLookup(classOf[Database]) == App.database)
ИГРАТЬ ИНТЕГРАЦИЯ
Имея эти утилиты, мы можем перейти к интеграции MacWire с Play.
Основное изменение заключается в том, что контроллеры теперь будут не objects, а classes (обычно с непустыми списками параметров конструктора — зависимости). Теперь задача состоит в том, чтобы сказать Play, как получить значения контроллера?
Допустим, у нас есть следующий контроллер и класс обслуживания:
package controllers
import play.api.mvc._
import services.HelloWorldProvider
class MainController(helloWorldProvider: HelloWorldProvider) extends Controller {
def index = Action {
Ok(views.html.index(helloWorldProvider.text))
}
}
package services
class HelloWorldProvider {
def text = "Hello World!"
}
Здесь контроллер ( MainController) имеет одну зависимость, HelloWorldProvider сервис.
Теперь, когда у нас есть готовый пример, мы можем приступить к интеграции. Есть три простых шага. Во-первых, нам нужно связать зависимости. Мы можем использовать wire[] макрос или просто использовать ручной DI:
trait OurApplication {
lazy val helloWorldProvider = new HelloWorldProvider
lazy val mainController = new MainController(helloWorldProvider)
}
Во-вторых, нам нужно указать в файле маршрутов, что мы хотим предоставить экземпляры контроллеров самостоятельно, вместо того, чтобы позволить Play создавать экземпляры контроллеров. Это делается путем добавления @ символа перед именем контроллера:
// File: conf/routes GET / @controllers.MainController.index()
Наконец, нам нужно создать Global объект в основном пакете. Это главная точка интеграции; здесь мы используем карты поиска экземпляров по классам:
import com.softwaremill.macwire.{InstanceLookup, Macwire}
import play.api.GlobalSettings
object Global extends GlobalSettings with Macwire {
val instanceLookup = InstanceLookup(valsByClass(new OurApplication {}))
override def getControllerInstance[A](controllerClass: Class[A]) =
instanceLookup.lookupSingleOrThrow(controllerClass)
}
Вот и все! Отныне экземпляры контроллера будут предоставляться с нашей карты, используя проводку, которую мы определили в OurApplication характеристике.
Обязательно ознакомьтесь с Активатором, если хотите увидеть рабочий пример в действии!
Адам