【问题标题】:Play Framework without dependency injection?播放没有依赖注入的框架?
【发布时间】:2020-01-13 20:01:53
【问题描述】:

不解释为什么,假设有人想要一个老式的 Play Framework Web 服务,不想使用依赖注入,也不想依赖 Google 的 Guice。 Play 2.8.x 还可以吗?

api documentation 以及当前的 Play 示例将其推荐为“典型”HomeController.scala:

package controllers
import javax.inject._
import play.api.mvc._
class HomeController @Inject() (val controllerComponents: ControllerComponents) extends BaseController {
  def index = Action {
    Ok("It works!")
  }
}

我想要的代码是相同的,但没有 @Inject()(类似于我上次在 2016 年使用 Play 2.4.0 时)?以前我的代码是这样的:

package controllers
import play.api.mvc.{Action, AnyContent, Controller}
object TestController {
  def index:Action[AnyContent] = Action {
    Ok("It used to work.")
  }
}

控制台:

[info] Compiling 1 Scala source to /Volumes/.../play-scala-seed/target/scala-2.13/classes ...
[error] p.a.h.DefaultHttpErrorHandler - 

! @7ef69nl6l - Internal server error, for (GET) [/test/] ->

play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:

1) Could not find a suitable constructor in controllers.TestController. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at controllers.TestController.class(TestController.scala:3)
  while locating controllers.TestController
    for the 4th parameter of router.Routes.<init>(Routes.scala:33)
  at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:137):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)

1 error]
    at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:210)
    at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:141)
    at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:296)
    at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:186)
    at akka.stream.impl.fusing.MapAsync$$anon$30.onPush(Ops.scala:1261)
    at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
    at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:423)
    at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:624)
    at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:501)
    at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:599)
Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors:

1) Could not find a suitable constructor in controllers.TestController. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at controllers.TestController.class(TestController.scala:3)
  while locating controllers.TestController
    for the 4th parameter of router.Routes.<init>(Routes.scala:33)
  at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:137):
Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)

1 error
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:543)
    at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:159)
    at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:106)
    at com.google.inject.Guice.createInjector(Guice.java:87)
    at com.google.inject.Guice.createInjector(Guice.java:78)
    at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:200)
    at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:155)
    at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
    at play.core.server.DevServerStart$$anon$1.$anonfun$reload$3(DevServerStart.scala:189)
    at play.utils.Threads$.withContextClassLoader(Threads.scala:21)

有没有一个简单的解决办法来保持老派——不用去here

我承认但不完全理解https://www.playframework.com/documentation/2.4.x/Migration24。我认为我的问题与在 2.7 中删除的静态路由有关。

【问题讨论】:

    标签: scala playframework


    【解决方案1】:

    我的声誉不允许我通过Mario Galicanswer 发表评论,但您可以使用BuiltInComponentsFromContext 提供的“正确”(非测试)controllerComponents 轻松修改他的示例.

    整个例子看起来像

    class HomeController(override protected val controllerComponents: ControllerComponents)
      extends BaseController {
      def index = Action { Ok("It works!") }
    }
    
    class MyApplicationLoader extends ApplicationLoader {
      def load(context: ApplicationLoader.Context): Application = {
        new BuiltInComponentsFromContext(context) {
          lazy val homeController = HomeController(controllerComponents)
          override lazy val router: Router = Routes(httpErrorHandler, homeController)
        }.application
      }
    }
    

    【讨论】:

    • java中有没有等价的例子?
    • 我无法在评论中回复我的回复,并且不确定在其他答案中标记人是否有效,但请参阅 stackoverflow.com/a/60345559/1640281 @kujosHeist 。
    • 谢谢。我给了你的代表一个提升和认可,也感谢@Mario Galic。感谢您的帮助。
    • @TomasMilata 我的理解是 OP 不想在控制器中注入任何东西,因此我向 HomeController 提供了空构造函数并且没有注入 CCs。因此,人为设计的stubControllerComponents 示例。确实,您的答案是正确的方法,因此请向我 +1。
    【解决方案2】:

    回答@kujosHeist 关于Java 中一个等效示例的评论。这似乎对我有用(遵循Play docs):

    package controllers;
    
    import play.mvc.Controller;
    import play.mvc.Result;
    
    public class HomeController extends Controller {
        public Result index() {
            return ok("It works!");
        }
    }
    
    public class MyApplicationLoader implements ApplicationLoader {
    
        @Override
        public Application load(Context context) {
            return new MyComponents(context).application();
        }
    }
    
    
    class MyComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents, AssetsComponents {
    
        public MyComponents(ApplicationLoader.Context context) {
            super(context);
        }
    
        @Override
        public Router router() {
            HomeController homeController = new HomeController();
            Assets assets = new Assets(scalaHttpErrorHandler(), assetsMetadata());
            return new router.Routes(scalaHttpErrorHandler(), homeController, assets).asJava();
        }
    
    }
    
    

    您可能想要定义诸如错误处理程序不同,但这可能是大致的结构。

    【讨论】:

      【解决方案3】:

      确实StaticRoutesGenerator 一直是removed,它需要将控制器作为单例对象。也许使用compile time dependency injection,例如here,可能会让你更接近你以前的习惯,但是ControllerComponents仍然需要注入。从技术上讲,将play-test 放在Compile 类路径上并像这样使用stubControllerComponents 可能会做一些不明智的事情

      class HomeController extends BaseController {
        def index = Action { Ok("It works!") }
      
        override protected def controllerComponents: ControllerComponents = 
           play.api.test.Helpers.stubControllerComponents()
      }
      

      以及对应的最小ApplicationLoader

      class MyApplicationLoader extends ApplicationLoader {
        def load(context: ApplicationLoader.Context): Application = {
          new BuiltInComponentsFromContext(context) {
            override def httpFilters: Seq[EssentialFilter] = Nil
            lazy val homeController = new _root_.controllers.HomeController
            lazy val router: Router = new _root_.router.Routes(httpErrorHandler, homeController)
          }.application
        }
      }
      

      这样HomeController虽然仍然是一个类,但现在完全是硬连线的,并且在ApplicationLoader中只创建了它的一个实例。

      就我个人而言,我建议不要使用这种恶作剧,并相信 arguments 为什么 Play 会放弃单例,例如可测试性、thread-safety 等。

      【讨论】:

      • 谢谢。这很有趣——及时向前跳过——听到 Play 似乎在唱不同的曲调,讲述控制器作为对象的优点。我可能不得不过来。
      猜你喜欢
      • 2016-07-25
      • 1970-01-01
      • 1970-01-01
      • 2013-09-14
      • 2021-12-27
      • 2015-10-11
      • 2014-09-06
      • 2015-07-18
      • 1970-01-01
      相关资源
      最近更新 更多