用子项目工作

一个复杂的项目不一定是由单Play应用程序组成。你可能想要分割一个大型项目为几个小型应用程序, 或甚至提取一些逻辑到一个标准Java或Scala库,不用Play应用程序来做。

阅读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应用程序, 依赖项目会自动重新编译, 并且如果编译遇到问题会在浏览器显示结果:

""

Sharing common variables and code

If you want your sub projects and root projects to share some common settings or code, then these can be placed in a Scala file in the project directory of the root project. For example, in project/Common.scala you might have:

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

Then in each of your build.sbt files, you can reference anything declared in the file:

name := "my-sub-module"

Common.settings

libraryDependencies += Common.fooDependency

Splitting your web application into several parts

As a Play application is just a standard sbt project with a default configuration, it can depend on another Play application. You can make any sub module a Play application by adding the PlayJava or PlayScala plugins, depending on whether your project is a Java or Scala project, in its corresponding build.sbt file.

Note: In order to avoid naming collision, make sure your controllers, including the Assets controller in your subprojects are using a different name space than the main project

Splitting the route file

It’s also possible to split the route file into smaller pieces. This is a very handy feature if you want to create a robust, reusable multi-module play application

Consider the following build configuration

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(
  "mysql" % "mysql-connector-java" % "5.1.35",
  jdbc,
  anorm
)

Project structure

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

Note: Configuration and route file names must be unique in the whole project structure. Particularly, there must be only one application.conf file and only one routes file. To define additional routes or configuration in sub-projects, use sub-project-specific names. For instance, the route file in admin is called admin.routes. To use a specific set of settings in development mode for a sub project, it would be even better to put these settings into the build file, e.g. Keys.devSettings += ("play.http.router", "admin.Routes").

conf/routes:

GET /index                  controllers.Application.index()

->  /admin admin.Routes

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

modules/admin/conf/admin.routes:

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

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

Note: Resources are served from a unique classloader, and thus resource path must be relative from project classpath root. Subprojects resources are generated in target/web/public/main/lib/{module-name}, so the resources are accessible from /public/lib/{module-name} when using play.api.Application#resources(uri) method, which is what the Assets.at method does.

Assets and controller classes should be all defined in the controllers.admin package

modules/admin/controllers/Assets.scala:

package controllers.admin
import play.api.http.LazyHttpErrorHandler
object Assets extends controllers.AssetsBuilder(LazyHttpErrorHandler)

Note: Java users can do something very similar i.e.:

// Assets.java
package controllers.admin;
import play.api.mvc.*;

public class Assets {
  public static Action<AnyContent> at(String path, String file) {
    return controllers.Assets.at(path, file);
  }
}

and a controller:

modules/admin/controllers/Application.scala:

package controllers.admin

import play.api._
import play.api.mvc._
import views.html._

object Application extends Controller {

  def index = Action { implicit request =>
    Ok("admin")
  }
}

Reverse routing in admin

in case of a regular controller call:

controllers.admin.routes.Application.index

and for Assets:

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

Through the browser

http://localhost:9000/index

triggers

controllers.Application.index

and

http://localhost:9000/admin/index

triggers

controllers.admin.Application.index