Play Framework 2.5中object注入Configuration遇空指针及trait适配问题
咱们先搞清楚你遇到的空指针问题根源:Play的依赖注入容器(比如默认的Guice)只负责管理类的实例,而Scala的object是JVM加载时就自动初始化的单例,DI容器根本不会去处理它的@Inject注解字段,所以你的config变量一直是null,调用portNumber自然就报NPE了。原来的Play.configuration是全局静态引用,Play 2.5之后确实废弃了,因为它不符合依赖注入的设计理念。
下面给你两种解决方案,以及关于Trait的疑问解答:
一、最佳实践:将Object改为注入式类
这是Play官方推荐的方式,完全符合依赖注入的设计,也避免了全局静态引用的问题:
import play.api.Configuration import javax.inject._ // 用@Singleton确保和原来的Object一样是单例 @Singleton class Test @Inject()(val config: Configuration) { def portNumber = config.getInt("server.port") }
之后在需要使用这个类的地方(比如Controller、Service),通过构造注入获取实例:
import play.api.mvc._ import javax.inject._ class MyController @Inject()(test: Test, cc: ControllerComponents) extends AbstractController(cc) { def getPort = Action { Ok(test.portNumber.getOrElse(9000).toString) } }
这种方式的好处是代码解耦,方便单元测试(可以Mock Configuration注入),也完全符合Play 2.5+的设计规范。
二、临时过渡方案:手动获取依赖(不推荐)
如果你的旧代码里有大量地方引用了这个object,一时半会儿改不完,可以用Play的Injector手动获取依赖,但这只是权宜之计:
import play.api.Configuration import play.api.inject.Injector import play.api.Play object Test { // 用lazy延迟初始化,确保Play已经启动后再获取Injector private lazy val injector: Injector = Play.current.injector private lazy val config: Configuration = injector.instanceOf[Configuration] def portNumber = config.getInt("server.port") }
⚠️ 注意:这种方式会让代码耦合度变高,测试起来也麻烦,尽量还是逐步迁移到类注入的方式。
三、关于Trait的替代方案
Trait本身不能被DI容器直接实例化,但你可以把Trait作为抽象接口,然后写一个DI管理的实现类,这样既能保留Trait的抽象性,又能用上依赖注入:
// 定义Trait trait TestTrait { def portNumber: Option[Int] } // 实现类用DI注入Configuration @Singleton class TestImpl @Inject()(config: Configuration) extends TestTrait { override def portNumber = config.getInt("server.port") }
之后在需要使用的地方注入TestTrait,Guice会自动绑定到TestImpl(如果没有自定义绑定规则的话):
class MyController @Inject()(test: TestTrait, cc: ControllerComponents) extends AbstractController(cc) { // ... 使用test.portNumber }
如果想让Trait直接获取依赖,其实和之前的Object问题类似——Trait无法被DI容器管理,还是得手动获取,反而不如用实现类的方式清晰。
内容的提问来源于stack exchange,提问作者med10

