我正在尝试将我的Play应用程序从2.3.9迁移到2.4.3并使用编译时依赖注入。使用旧的全局Cache api对象(InstantiationException
)时,我得到play.api.cache.Cache
。我在我的组件中包含了EhCacheComponents
(它提供了一个缓存实现)但似乎Play试图直接实例化抽象CacheApi
:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[InstantiationException: play.api.cache.CacheApi]]
<snip>
Caused by: java.lang.InstantiationException: play.api.cache.CacheApi
at java.lang.Class.newInstance(Class.java:427) ~[na:1.8.0_51]
at play.api.inject.NewInstanceInjector$.instanceOf(Injector.scala:49) ~[play_2.11-2.4.3.jar:2.4.3]
at play.api.inject.SimpleInjector$$anonfun$instanceOf$1.apply(Injector.scala:85) ~[play_2.11-2.4.3.jar:2.4.3]
我知道建议使用新的依赖注入组件,但是文档建议这应该仍然有效,并且我希望我的应用程序无需一次性更改即可运行。
这是一个简化的应用程序,它演示了这个问题:
class AppApplicationLoader extends ApplicationLoader {
def load(context : play.api.ApplicationLoader.Context) : play.api.Application = {
Logger.configure(context.environment)
new AppComponents(context).application
}
}
class AppComponents(context : play.api.ApplicationLoader.Context) extends BuiltInComponentsFromContext(context) with EhCacheComponents {
lazy val assets = new controllers.Assets(httpErrorHandler)
lazy val router: Router = new Routes(httpErrorHandler, assets, new controllers.TestController())
}
-
package controllers
class TestController extends Controller {
def test = Action {
Cache.getAs[String]("hello") map { result =>
Ok(result)
} getOrElse {
Ok("not found")
}
}
}
配置:
# key, langs, etc. removed
play.application.loader = "AppApplicationLoader"
play.modules.enabled += "play.api.cache.EhCacheModule"
我该如何做到这一点?
答案 0 :(得分:2)
可以通过替换默认注入器并添加更多组件来实现此目的。我想这不再是编译时的DI(因为依赖关系现在正在运行时解决),但它确实有效。
扩展BuiltInComponents
时:
trait AppComponents(context: Context) extends BuiltInComponents
with I18nComponents
with EhCacheComponents {
// other dependencies (e.g. router, assets) here
//need to add any other components here that you want to reference via the global APIs -
//e.g. csrfConfig from CSRFComponents
override lazy val injector: Injector = new SimpleInjector(
NewInstanceInjector
) + router + crypto + httpConfiguration + defaultCacheApi + messagesApi
}
很遗憾,您无法引用super.injector
,因为它是lazy val
,因此您不得不重新定义BuiltInComponents
中已有的内容,这并不是很好。在将来升级Play时,检查添加到基本定义的任何新组件是否都会复制到新实现中非常重要。
在我的实际应用程序中,我正在使用MacWire,因此我编写了一个自定义注入器:
class MacwireInjector(fallback: Injector, wired: Wired) extends Injector {
/**
* Get an instance of the given class from the injector.
*/
def instanceOf[T](implicit ct: ClassTag[T]) = instanceOf(ct.runtimeClass.asInstanceOf[Class[T]])
/**
* Get an instance of the given class from the injector.
*/
def instanceOf[T](clazz: Class[T]) = wired.lookup(clazz) match {
case instance :: Nil => instance
case Nil => fallback.instanceOf(clazz)
case set => throw new RuntimeException(s"Multiple instances returned for $clazz: $set")
}
/**
* Get an instance bound to the given binding key.
*/
def instanceOf[T](key: BindingKey[T]) = instanceOf(key.clazz)
}
BuiltInComponents:
// probably need to do this otherwise MacWire finds two candidates from EhCacheComponents
lazy val cacheApi = defaultCacheApi
override lazy val injector: Injector = new MacwireInjector(NewInstanceInjector, wiredInModule(this))
或使用MacWire的默认注入器作为后备:
override lazy val injector: Injector = new SimpleInjector(
new MacwireInjector(NewInstanceInjector, wiredInModule(this))
) + router + crypto + httpConfiguration
答案 1 :(得分:1)
如果使用Compile Time依赖注入,则应通过对象的参数传递依赖项:
class TestController(cache: CacheApi) extends Controller {
...
}
并在app loader中传递实现:
class AppComponents(context : play.api.ApplicationLoader.Context) extends BuiltInComponentsFromContext(context) with EhCacheComponents {
lazy val assets = new controllers.Assets(httpErrorHandler)
lazy val controller = new controllers.TestController(defaultCacheApi)
lazy val router: Router = new Routes(httpErrorHandler, assets, controller)
}