Самая последняя версия MacWire (0.4) (макрос Scala для генерации связующего кода для создания экземпляров класса, замены контейнера DI) поставляется с новыми утилитами, которые облегчают интеграцию с каркасами, которые требуют поиска экземпляров по классам . Примером такой структуры является Play! , который довольно быстро набирает популярность в мире веб-разработки.
Есть два способа узнать, как сделать внедрение без контейнера в Play! (и другие веб-фреймворки) с MacWire:
- читать этот блог
- попробуйте MacWire + Play Typesafe Activator
Или вы можете сделать оба;)
МОТИВАЦИЯ
Многие веб-фреймворки требуют некоторой формы общего поиска экземпляров, например, когда на экземпляры контроллера ссылаются из представлений. В традиционных DI-контейнерах, таких как Guice , это обеспечивается из коробки, так как это основной способ получения проводных объектов. Например, в Guice у нас есть объект, Injector
который переводит Class
объекты в экземпляры данного класса.
При использовании MacWire (или при «ручном» DI) мы делаем максимально возможный типобезопасный способ, просто ссылаясь на объекты как поля / методы в объекте-контейнере, поэтому нам обычно не нужны такие карты. Но для интеграции с веб-фреймворком это все еще необходимо.
ОБЩИЕ УТИЛИТЫ
MacWire 0.4 поставляется с двумя общими утилитами, полезными для поиска экземпляров по классам.
Первый — это макрос, valsByClass(object)
который генерирует (конечно же, во время компиляции) карту val
s в данном объекте с ключами их классов. Так, например:
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.
Основное изменение заключается в том, что контроллеры теперь будут не object
s, а class
es (обычно с непустыми списками параметров конструктора — зависимости). Теперь задача состоит в том, чтобы сказать 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
характеристике.
Обязательно ознакомьтесь с Активатором, если хотите увидеть рабочий пример в действии!
Адам