没有依赖注入的Play Framework?

Play Framework without dependency injection?

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

api documentation 连同当前的 Play 示例将其推荐为 "typical" 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()(类似于我上次使用 Play 2.4.0 是在 2016 年)?以前我的代码是这样的:

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)

1 error]
    at play.core.server.DevServerStart$$anon.reload(DevServerStart.scala:210)
    at play.core.server.DevServerStart$$anon.get(DevServerStart.scala:141)
    at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:296)
    at play.core.server.AkkaHttpServer.$anonfun$createServerBinding(AkkaHttpServer.scala:186)
    at akka.stream.impl.fusing.MapAsync$$anon.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)

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.$anonfun$reload(DevServerStart.scala:189)
    at play.utils.Threads$.withContextClassLoader(Threads.scala:21)

是否有一个简单的解决方法来保持老派——而不去 here

我承认但不完全理解https://www.playframework.com/documentation/2.4.x/Migration24. I assume my problem has to do with static routing having been removed in 2.7

确实StaticRoutesGenerator一直removed which is need to have controllers as singleton objects. Perhaps using compile time dependency injection, with an example here, might bring you closer to what you were used to, however ControllerComponents will still need to be injected. Technically, it might be possible to do something ill-advised by putting play-test on the Compile classpath and make use 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,尽管仍然是 class,现在完全是固定的,并且在 ApplicationLoader.

中只创建了它的一个实例

就个人而言,我会反对这种恶作剧,并且相信有好的 arguments why Play moved away from singletons, for example, testability,

我的声誉不允许我对 by Mario Galic 发表评论,但您可以使用提供的 "right"(非测试)controllerComponents 轻松修改他的示例通过 BuiltInComponentsFromContext.

整个例子看起来像

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
  }
}

回答@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();
    }

}

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