[英]Using play.api.cache.Cache with compile time dependency injection
I am trying to migrate my Play application from 2.3.9 to 2.4.3 and am using compile time dependency injection. 我正在尝试将我的Play应用程序从2.3.9迁移到2.4.3并使用编译时依赖注入。 I am getting an InstantiationException
when using the old global Cache api object ( play.api.cache.Cache
). 我在使用旧的全局Cache api对象( play.api.cache.Cache
)时收到InstantiationException
。 I have included EhCacheComponents
in my components (which provides a cache implementation) but it seems Play is trying to instantiate the abstract CacheApi
directly: 我在我的组件中包含了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]
I am aware that the recommendation is to use the new dependency injected components, but the documentation suggests this should still work, and I would like to get my application running without having to change it all in one go. 我知道建议是使用新的依赖注入组件,但是文档建议这应该仍然有效,并且我希望我的应用程序无需一次性更改即可运行。
Here is a simplified application which demonstrates the problem: 这是一个简化的应用程序,演示了这个问题:
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")
}
}
}
Configuration: 组态:
# key, langs, etc. removed
play.application.loader = "AppApplicationLoader"
play.modules.enabled += "play.api.cache.EhCacheModule"
How can I make this work? 我怎样才能做到这一点?
It is possible to do this by replacing the default injector and adding more components. 可以通过替换默认注入器并添加更多组件来实现此目的。 I suppose this is no longer compile-time DI (as dependencies are now being resolved up at runtime), but it works. 我想这不再是编译时的DI(因为依赖关系现在正在运行时解析),但它确实有效。
When extending BuiltInComponents
: 扩展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
}
Unfortunately you cannot reference super.injector
because it is a lazy val
, so you are forced to redefine what is already in BuiltInComponents
, which isn't great. 不幸的是你不能引用super.injector
因为它是一个lazy val
,所以你不得不重新定义BuiltInComponents
已有的BuiltInComponents
,这不是很好。 When upgrading Play in future it would be important to check that any new components added to the base definitions are copied to the new implementation. 在将来升级Play时,检查添加到基本定义的任何新组件是否都会复制到新实现中非常重要。
In my actual application I am using MacWire , so I have written a custom injector: 在我的实际应用程序中,我使用的是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: 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))
or using the default injector with MacWire as a fallback: 或使用MacWire的默认注入器作为后备:
override lazy val injector: Injector = new SimpleInjector(
new MacwireInjector(NewInstanceInjector, wiredInModule(this))
) + router + crypto + httpConfiguration
If you use Compile Time dependency injection, you should pass the dependencies via the parameters of your objects: 如果使用Compile Time依赖项注入,则应通过对象的参数传递依赖项:
class TestController(cache: CacheApi) extends Controller {
...
}
And pass the implementation in the app loader: 并在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)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.