文档

§使用子项目

一个复杂的项目不一定是单个 Play 应用程序。您可能希望将大型项目拆分为几个较小的应用程序,甚至将某些逻辑提取到与 Play 应用程序无关的标准 Java 或 Scala 库中。

阅读 sbt 多项目构建文档 将会有所帮助。子项目可以在父项目的构建文件中完全定义,尽管在这里我们将子项目的设置放在它们自己的构建文件中。

§添加一个简单的库子项目

您可以使您的应用程序依赖于一个简单的库项目。只需在您的 build.sbt 文件中添加另一个 sbt 项目定义

name := "my-first-application"

version := "1.0"

lazy val myFirstApplication = (project in file("."))
    .enablePlugins(PlayScala)
    .aggregate(myLibrary)
    .dependsOn(myLibrary)

lazy val myLibrary = project

最后一行的小写 project 是一个 Scala 宏,它将使用它被分配到的 val 的名称来确定项目的名称和文件夹。

myFirstApplication 项目声明了基础项目。如果您没有任何子项目,这已经是隐含的,但是当声明子项目时,通常需要声明它,以便您可以确保它聚合(即,在基础项目中运行时,在子项目上运行诸如编译/测试等操作)并依赖于(即,将子项目添加到主项目的类路径)子项目。

上面的示例在应用程序的 myLibrary 文件夹中定义了一个子项目。这个子项目是一个标准的 sbt 项目,使用默认布局

myProject
 └ build.sbt
 └ app
 └ conf
 └ public
 └ myLibrary
   └ build.sbt
   └ src
     └ main
       └ java
       └ scala

myLibrary 有自己的 build.sbt 文件,它可以在其中声明自己的设置、依赖项等。

当您在构建中启用子项目时,您可以专注于此项目并单独编译、测试或运行它。只需在 Play 控制台提示符中使用 projects 命令即可显示所有项目

[my-first-application] $ projects
[info] In file:/Volumes/Data/gbo/myFirstApp/
[info] 	 * my-first-application
[info] 	   my-library

默认项目是其变量名称按字母顺序排在最前面的项目。您可以通过使它的变量名称为 aaaMain 来创建您的主项目。要更改当前项目,请使用 project 命令

[my-first-application] $ project my-library
[info] Set current project to my-library
>

当您在开发模式下运行 Play 应用程序时,依赖项目会自动重新编译,如果某些内容无法编译,您将在浏览器中看到结果

§共享公共变量和代码

如果您希望子项目和根项目共享一些通用设置或代码,则可以将这些设置或代码放在根项目的 `project` 目录中的 Scala 文件中。例如,在 `project/Common.scala` 中,您可能会有

import sbt._
import Keys._

object Common {
  val settings: Seq[Setting[_]] = Seq(
    organization := "com.example",
    version := "1.2.3-SNAPSHOT"
  )

  val fooDependency = "com.foo" %% "foo" % "2.4"
}

然后,在每个 `build.sbt` 文件中,您可以引用该文件中声明的任何内容

name := "my-sub-module"

Common.settings

libraryDependencies += Common.fooDependency

需要注意的是,如果您混合了 Play 项目和非 Play 项目,您可能需要显式地共享 Play 配置。例如,您可能希望为每个 Play 项目共享 `InjectedRoutesGenerator` 和 specs2

object Common {

  val playSettings = settings ++ Seq(
    routesGenerator := InjectedRoutesGenerator,
    libraryDependencies += specs2 % Test,
    resolvers += Resolver.ApacheMavenSnapshotsRepo // contains pekko(-http) snapshots
  )
}

在子项目的 `build.sbt` 文件中,您将拥有以下内容

Common.playSettings

§将您的 Web 应用程序拆分为多个部分

由于 Play 应用程序只是一个具有默认配置的标准 sbt 项目,因此它可以依赖于另一个 Play 应用程序。您可以通过在相应的 `build.sbt` 文件中添加 `PlayJava` 或 `PlayScala` 插件(取决于您的项目是 Java 项目还是 Scala 项目)来使任何子模块成为 Play 应用程序。

注意:为了避免命名冲突,请确保您的控制器(包括子项目中的 Assets 控制器)使用与主项目不同的命名空间。例如,`admin` 模块中的控制器应该具有 `admin.MyController` 的完全限定包名。

§拆分路由文件

也可以将路由文件拆分成更小的部分。如果您想创建一个健壮、可重用的多模块 Play 应用程序,这是一个非常方便的功能。

§考虑以下构建配置

build.sbt:

name := "myproject"

lazy val admin = (project in file("modules/admin")).enablePlugins(PlayScala)

lazy val main = (project in file("."))
    .enablePlugins(PlayScala).dependsOn(admin).aggregate(admin)

modules/admin/build.sbt

name := "myadmin"

libraryDependencies ++= Seq(
  "com.mysql" % "mysql-connector-j" % "8.0.33",
  jdbc,
  anorm
)

§项目结构

build.sbt
app
  └ controllers
  └ models
  └ views
conf
  └ application.conf
  └ routes
modules
  └ admin
    └ build.sbt
    └ conf
      └ admin.routes
    └ app
      └ controllers
      └ models
      └ views
project
  └ build.properties
  └ plugins.sbt

注意:配置和路由文件名在整个项目结构中必须是唯一的。特别是,只能有一个 `application.conf` 文件和一个 `routes` 文件。要在子项目中定义额外的路由或配置,请使用子项目特定的名称。例如,`admin` 中的路由文件称为 `admin.routes`。要在开发模式下为子项目使用一组特定的设置,最好将这些设置放在构建文件中,例如 `PlayKeys.devSettings += ("play.http.router", "admin.Routes")`。

conf/routes:

GET /index                  controllers.HomeController.index()

->  /admin admin.Routes

GET     /assets/*file       controllers.Assets.at(path="/public", file)

modules/admin/conf/admin.routes:

GET /index                  controllers.admin.HomeController.index()

GET /assets/*file           controllers.Assets.at(path="/public/lib/myadmin", file)

注意:资源来自唯一的类加载器,因此资源路径必须相对于项目类路径根目录。
子项目资源生成在target/web/public/main/lib/{module-name}中,因此当使用play.api.Application#resources(uri)方法(即Assets.at方法所做的事情)时,可以从/public/lib/{module-name}访问这些资源。

§资产和控制器类应该全部定义在controllers.admin包中

Java
package controllers.admin;

import controllers.AssetsMetadata;
import javax.inject.Inject;
import play.api.Environment;
import play.api.http.HttpErrorHandler;
import play.api.mvc.*;

public class Assets extends controllers.Assets {

  @Inject
  public Assets(HttpErrorHandler errorHandler, AssetsMetadata meta, Environment env) {
    super(errorHandler, meta, env);
  }

  public Action<AnyContent> at(String path, String file) {
    boolean aggressiveCaching = true;
    return super.at(path, file, aggressiveCaching);
  }
}
Scala
import javax.inject._

import play.api.http.HttpErrorHandler
import play.api.Environment

class Assets @Inject() (
    errorHandler: HttpErrorHandler,
    assetsMetadata: controllers.AssetsMetadata,
    environment: Environment
) extends controllers.AssetsBuilder(errorHandler, assetsMetadata, environment)

以及一个控制器

Java
/*
 * Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
 */

package controllers.admin;

import play.mvc.Controller;
import play.mvc.Result;

public class HomeController extends Controller {
  public Result index() {
    return ok("admin");
  }
}
Scala
package controllers.admin

import javax.inject.Inject

import play.api.mvc._

class HomeController @Inject() (val controllerComponents: ControllerComponents) extends BaseController {
  def index: Action[AnyContent] = Action { implicit request => Ok("admin") }
}

§admin中反向路由

对于常规控制器调用

controllers.admin.routes.HomeController.index

以及对于Assets

controllers.admin.routes.Assets.at("...")

§通过浏览器

http://localhost:9000/index

触发

controllers.HomeController.index

以及

http://localhost:9000/admin/index

触发

controllers.admin.HomeController.index

下一步:聚合反向路由器


发现此文档中的错误?此页面的源代码可以在此处找到。在阅读文档指南后,请随时贡献拉取请求。有疑问或建议要分享?请访问我们的社区论坛,与社区开始对话。