文档

§Play 2.7 迁移指南

本指南介绍如何从 Play 2.6 迁移到 Play 2.7。如果您需要从更早版本的 Play 迁移,则必须首先遵循 Play 2.6 迁移指南

§如何迁移

在您可以在 sbt 中加载/运行 Play 项目之前,需要执行以下步骤来更新您的 sbt 构建。

§Play 升级

project/plugins.sbt 中更新 Play 版本号以升级 Play

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.x")

其中 2.7.x 中的“x”是您要使用的 Play 的次要版本,例如 2.7.0

§sbt 升级到 1.2.8

虽然 Play 2.7 仍然支持 sbt 0.13 系列,但我们建议您从现在开始使用 sbt 1.x。此新版本正在积极维护和支持。要更新,请更改您的 project/build.properties,使其读取

sbt.version=1.2.8

在撰写本文时,1.2.8 是 sbt 1.x 系列中的最新版本,您也可以使用更新的版本。请查看 Play 2.7.x 的次要版本的发布说明以了解详细信息。有关更多信息,请参阅 sbt 版本列表

§API 变更

根据我们先弃用现有 API 然后再删除它们的政策,进行了多项 API 变更。本节详细介绍了这些变更。

§已弃用的 API 已被删除

在 Play 2.7 中,许多在早期版本中已弃用的 API 已被删除。如果您仍在使用它们,我们建议您在升级到 Play 2.7 之前迁移到新的 API。Javadoc 和 Scaladocs 通常都有关于如何迁移的适当文档。有关更多信息,请参阅 Play 2.6 的迁移指南

§StaticRoutesGenerator 已被删除

StaticRoutesGenerator 已在 2.6.0 中弃用,现已删除。如果您仍在使用它,您可能需要从您的 build.sbt 文件中删除类似以下的代码行

routesGenerator := StaticRoutesGenerator

§Java Http.Context 变更

请参阅 play.mvc.Http.Context API 中的变更。这仅与 Java 用户相关:Java Http.Context 变更

§Play WS 变更

在 Play 2.6 中,我们将 Play-WS 的大部分内容提取到一个 独立项目 中,该项目具有独立的发布周期。Play-WS 现在有一个重要的版本,需要在 Play 本身中进行一些更改。

Play-WS 2.0 带来了 Async-Http-Client 的更新版本,它具有一个全局的内部 Cookie 存储,如果在向第三方服务发送请求时发送用户敏感 Cookie,则可能会影响您的应用程序。例如,由于 Cookie 存储是全局的,因此应用程序在向同一主机发出请求时,可能会将用户的 Cookie 与另一个用户的 Cookie 混淆。现在有一个新的配置,您可以使用它来启用或禁用缓存。

# Enables global cache cookie store
play.ws.ahc.useCookieStore = true

默认情况下,缓存被禁用。这会影响其他地方,例如自动重定向。以前,第一个请求的 Cookie 会在后续请求中发送,但在禁用缓存的情况下并非如此。目前无法按请求配置缓存。

§Scala API

  1. play.api.libs.ws.WSRequest.requestTimeout 现在返回 Option[Duration] 而不是 Option[Int]

§Java API

  1. play.libs.ws.WSRequest.getUsername 现在返回 Optional<String> 而不是 String
  2. play.libs.ws.WSRequest.getContentType 现在返回 Optional<String> 而不是 String
  3. play.libs.ws.WSRequest.getPassword 现在返回 Optional<String> 而不是 String
  4. play.libs.ws.WSRequest.getScheme 现在返回 Optional<WSScheme 而不是 WSScheme
  5. play.libs.ws.WSRequest.getCalculator 现在返回 Optional<WSSignatureCalculator> 而不是 WSSignatureCalculator
  6. play.libs.ws.WSRequest.getRequestTimeout 现在返回 Optional<Duration> 而不是 long
  7. play.libs.ws.WSRequest.getRequestTimeoutDuration 已被移除,建议使用 play.libs.ws.WSRequest.getRequestTimeout
  8. play.libs.ws.WSRequest.getFollowRedirects 现在返回 Optional<Boolean> 而不是 boolean

还添加了一些新方法来改进 Java API。

新方法 play.libs.ws.WSResponse.getBodyAsSource 将响应主体转换为 Source<ByteString, ?>。例如

wsClient.url("https://playframework.com.cn")
    .stream() // this returns a CompletionStage<StandaloneWSResponse>
    .thenApply(StandaloneWSResponse::getBodyAsSource);

其他添加到 Java API 中以改进其功能的方法

  1. play.libs.ws.WSRequest.getBody 返回为该请求配置的主体。在实现 play.libs.ws.WSRequestFilter 时,这可能很有用。
  2. play.libs.ws.WSRequest.getMethod 返回为该请求配置的方法。
  3. play.libs.ws.WSRequest.getAuth 返回 WSAuth
  4. play.libs.ws.WSRequest.setAuth 为该请求设置 WSAuth
  5. play.libs.ws.WSResponse.getUri 获取该响应的 URI

§BodyParsers API 一致性

解析器 API 之前使用 IntegerLong 来定义缓冲区长度,这可能会导致值溢出。现在配置已统一使用 Long。这意味着,如果您依赖于 play.api.mvc.PlayBodyParsers.DefaultMaxTextLength,例如,您现在需要使用 Long。因此,play.api.http.ParserConfiguration.maxMemoryBuffer 现在也是 Long

§解析器 maxMemoryBuffer 限制

某些有效负载在解析时会在内存中扩展。因此,内存表示比从网络读取的纯文本表示占用更多空间。JSON 就是其中一种格式。为了防止可能导致内存不足错误并造成拒绝服务攻击的攻击,正文解析和表单绑定必须遵守 play.http.parser.maxMemoryBuffer 设置。

也可以在特定情况下放宽 maxMemoryBuffer。JSON 表示和扩展表示的大小可能不同,您可能需要使用不同的限制。您可以使用具有自定义限制的表单绑定,方法是

class MyController @Inject()(cc: ControllerComponents) {

  // This will be the action that handles our form post
  def myMethod = Action { implicit request: Request[_] =>
    // create a new formBinding instance with increased limit 
    val defaultFormBinding: FormBinding = cc.parsers.formBinding(300*1024) // limit to 300KiB
    form.bindFromRequest()(request, formBinding)
    ...
  }

}

控制器将始终具有一个 FormBinding 实例,该实例构建为遵守 play.http.parser.maxMemoryBuffer。如果您在控制器之外的某些代码中使用表单,则可能需要提供一个隐式 FormBinding。例如,如果您编写单元测试,可以使用 play.api.data.FormBinding.Implicits._ 中提供的 FormBinding,该绑定使用一个硬编码限制,该限制足以用于测试。将隐式添加到作用域中

`scala import play.api.data.FormBinding.Implicits._

§添加到 FilePartFileInfo 的新字段和方法

Scala'sJava's FilePart 类有两个新的字段/方法,它们为您提供通过 multipart/form-data 编码上传的文件的文件大小和处置类型

Scala 的 FileInfo 类现在也具有 dispositionType 字段。

如果您有包含 FilePartFileInfo 的 Scala case 语句,则需要更新这些语句以包含这些新字段,否则您会收到编译器错误

FilePart
case FilePart(key, filename, contentType, file, fileSize, dispositionType) => ...
// Or if you don't use these new fields:
case FilePart(key, filename, contentType, file, _, _) => ...
FileInfo
case FileInfo(partName, filename, contentType, dispositionType) => ...
// Or if you don't use these new fields:
case FileInfo(partName, filename, contentType, _) => ...

§使用自定义主体解析器时,将上传文件的大小传递给FilePart

当使用Play ScalaPlay Java中的multipart/form-data编码上传文件时,FilePart现在通过Scala API中的fileSize和Java API中的getFileSize()公开上传文件的大小。
如果您使用自定义主体解析器进行文件上传,则需要自己将文件大小传递给生成的FilePart实例。否则,文件大小将不会设置,并将默认为-1。请查看更新后的自定义多部分文件部分主体解析器示例 - 在这些示例中,现在将处理的字节数(上传文件)的count传递给创建的FilePart

§Java的FilePart公开上传文件的TemporaryFile

默认情况下,通过multipart/form-data编码上传文件使用TemporaryFile API,该 API 依赖于将文件存储在临时文件系统中。
但是,直到 Play 2.6,您无法直接访问该TemporaryFile,而只能访问它支持的File

Http.MultipartFormData<File> body = request.body().asMultipartFormData();
Http.MultipartFormData.FilePart<File> picture = body.getFile("picture");
if (picture != null) {
    File file = picture.getFile();
}

上面使用的getFile()方法现在已弃用,您应该使用getRef(),它为您提供一个TemporaryFile实例,其中包含一些有用的方法
从 Play 2.7 开始,上面的代码应该重构为

Http.MultipartFormData<TemporaryFile> body = request.body().asMultipartFormData();
Http.MultipartFormData.FilePart<TemporaryFile> picture = body.getFile("picture");
if (picture != null) {
    TemporaryFile tempFile = picture.getRef();
    File file = tempFile.path().toFile();
}

§TemporaryFile中添加了copyTo并重命名了移动方法

在 Play 2.5 之前,moveTo 方法实际上是将文件的副本复制到目标并删除源。Play 2.6 中有一个细微的更改,其中文件改为根据某些条件原子地移动。对于此类情况,源和目标最终都使用相同的inode
为了使 API 围绕这一点更加清晰,现在有一个copyTo方法,它始终创建一个不共享源文件inode的副本。

Play 2.7 的另一个变化是,TemporaryFile 中用于移动文件的那些方法已经被重命名了。

已弃用方法 新方法
moveTo(...) moveFileTo(...)
atomicMoveWithFallback(...) atomicMoveFileWithFallback(...)

这些新方法现在返回一个 Path 而不是 TemporaryFile。从这些方法返回 TemporaryFile 从一开始就是一个错误,因为有人可能会错误地认为这些返回的文件是真正的临时文件,最终会在 Play 的临时文件清理机制中自动删除 - 但事实并非如此。
因为这些方法旨在用于将文件移出 Play 的内部临时文件夹(上传的文件最初存储在那里),所以最终由开发人员负责如何处理移动后的目标文件(以及是否、如何以及何时删除它)。现在更改返回类型使这一点更加明确。

§Guice 兼容性变更

Guice 已升级到版本 4.2.2(另请参阅 4.2.14.2.0 版本说明),这会导致以下重大变更

§静态 Logger 单例已弃用

Java play.Logger 的大多数 static 方法和 Scala play.api.Logger 单例对象的几乎所有方法都已弃用。这些单例写入 application 日志记录器,它在 logback.xml 中被引用为

<logger name="application" level="DEBUG" />

如果您担心更改日志记录配置,这里最简单的迁移方法是使用 Logger("application")(Scala)或 Logger.of("application")(Java)定义您自己的单例“application”日志记录器。发送到此日志记录器的所有日志将与 Play 单例日志记录器完全相同。虽然我们通常不推荐这种方法,但最终由您决定。Play 和 Logback 不会强制您对日志记录器使用任何特定的命名方案。

如果您愿意进行一些简单的代码更改并更改日志记录配置,我们建议您为每个类创建一个新的日志记录器,其名称与类名匹配。这使您可以为每个类或包配置不同的日志级别。例如,要将所有 com.example.models 的日志级别设置为 info 级别,您可以在 logback.xml 中设置

<logger name="com.example.models" level="INFO" />

要在每个类中定义日志记录器,您可以定义

Java
import play.Logger;
private static final Logger.ALogger logger = Logger.of(YourClass.class);
Scala
import play.api.Logger
private val logger = Logger(classOf[YourClass])

对于 Scala,Play 还提供了一个 play.api.Logging 特性,可以将其混合到类或特性中,以自动添加 val logger: Logger

import play.api.Logging

class MyClass extends Logging {
  // `logger` is automaticaly defined by the `Logging` trait:
  logger.info("hello!")
}

当然,您也可以直接使用 SLF4J

Java
private static final Logger logger = LoggerFactory.getLogger(YourClass.class);
Scala
private val logger = LoggerFactory.getLogger(classOf[YourClass])

如果您希望在直接使用 SLF4J 的情况下获得更简洁的解决方案,您也可以考虑使用 Project Lombok 的 @Slf4j 注解

注意org.slf4j.Logger,SLF4J 的日志记录接口,尚未提供接受 lambda 表达式作为参数以进行延迟评估的日志记录方法。play.Loggerplay.api.Logger,它们主要是 org.slf4j.Logger 的简单包装器,提供了这样的方法。

迁移到不再使用 application 日志记录器后,您可以从 logback.xml 中删除引用它的 logger 条目

<logger name="application" level="DEBUG" />

§应用程序加载器 API 更改

如果您使用的是自定义 ApplicationLoader,则您可能在运行测试时手动创建此加载器的实例。为此,您首先需要创建一个 ApplicationLoader.Context 实例,例如

val env = Environment.simple()
val context = ApplicationLoader.Context(
  environment = env,
  sourceMapper = None,
  webCommands = new DefaultWebCommands(),
  initialConfiguration = Configuration.load(env),
  lifecycle = new DefaultApplicationLifecycle()
)
val loader = new MyApplicationLoader()
val application = loader.load(context)

但是,上面代码中使用的 ApplicationLoader.Context apply 方法现在已弃用,当 webCommands 不为空时会抛出异常。新代码应为

val env = Environment.simple()
val context = ApplicationLoader.Context.create(env)
val loader = new GreetingApplicationLoader()
val application = loader.load(context)

§JPA 删除和弃用

play.db.jpa.JPA 已在 Play 2.6 中弃用,现已完全删除。如果您尚未迁移,请查看 Play 2.6 JPA 迁移说明

随着此 Play 版本的发布,更多与 JPA 相关的 方法和注解已被弃用

如 Play 2.6 JPA 迁移说明中所述,请使用 使用 play.db.jpa.JPAApi 中描述的注入的 JPAApi 实例,而不是这些已弃用的方法和注解。

§Router#withPrefix 应始终添加前缀

以前,router.withPrefix(prefix) 用于向路由器添加前缀,但仍然允许“旧版实现”更新其现有前缀。Play 的 SimpleRouter 和其他类遵循此行为。现在所有实现都已更新为添加前缀,因此 router.withPrefix(prefix) 应始终返回一个路由器,该路由器以与 router 路由 path 相同的方式路由 s"$prefix/$path"

默认情况下,路由器没有前缀,因此只有当您在已由 withPrefix 返回的路由器上调用 withPrefix 时,才会导致行为发生变化。要替换已在路由器上设置的前缀,您必须在原始无前缀路由器上调用 withPrefix,而不是在带前缀的版本上调用。

§运行钩子

RunHook.afterStarted() 不再接受 InetSocketAddress 作为参数。

§所有 Java 表单的 validate 方法都需要迁移到类级别的约束

Java 表单的“旧”validate 方法将不再执行。
正如在 Play 2.6 迁移指南 中宣布的那样,您必须将此类 validate 方法迁移到 类级别的约束

重要:升级到 Play 2.7 时,您不会看到任何编译器警告,提示您必须迁移 validate 方法(因为 Play 通过反射执行了它们)。

§Java FormDynamicFormFormFactory 构造函数已更改

FormDynamicFormFormFactory 类(在 play.data 中)的构造函数,以前使用 Validator 参数,现在改为使用 ValidatorFactory 参数。
此外,这些构造函数现在还需要一个 com.typesafe.config.Config 参数。
例如,new Form(..., validator) 现在变为 new Form(..., validatorFactory, config)
此更改仅在您使用构造函数实例化表单而不是仅使用 formFactory.form(SomeForm.class) 时才会影响您 - 最有可能是在测试中。

§Java 缓存 API 的 get 方法已弃用,取而代之的是 getOptional

Java cacheApigetOptional 方法将结果包装在 Optional 中返回。

play.cache.SyncCacheApi 中的更改

已弃用方法 新方法
<T> T get(String key) <T> Optional<T> getOptional(String key)

play.cache.AsyncCacheApi 中的更改

已弃用方法 新方法
<T> CompletionStage<T> get(String key) <T> CompletionStage<Optional<T>> getOptional(String key)

§Server.getHandlerFor 已移至 Server#getHandlerFor

Server 特性上的 getHandlerFor 方法在 Play 服务器代码中路由请求时被内部使用。它已被删除,并替换为 Server 对象上的同名方法。

§添加了 Java DI 无关的 Play Module API 支持,所有内置 Java Module 的类型都已更改

您现在可以通过扩展 play.inject.Module 来创建 Java DI 无关的 Play Module,这更适合 Java,因为它使用 Java API 并用 Java 编写。此外,所有现有的内置 Java Module,例如 play.inject.BuiltInModuleplay.libs.ws.ahc.AhcWSModule,不再扩展 Scala play.api.inject.Module,而是扩展 Java play.inject.Module

由于 Java play.inject.Module 是 Scala play.api.inject.Module 的子类,因此 Module 实例仍然可以以相同的方式使用,只是接口略有不同。

public class MyModule extends play.inject.Module {
    @Override
    public java.util.List<play.inject.Binding<?>> bindings(final play.Environment environment, final com.typesafe.config.Config config) {
        return java.util.Collections.singletonList(
            // Note: it is bindClass() but not bind()
            bindClass(MyApi.class).toProvider(MyApiProvider.class)
        );
    }
}

§play.mvc.Results.TODO 已移至 play.mvc.Controller.TODO

所有 Play 的错误页面都已更新,如果存在 CSPFilter,则会渲染 CSP nonce。这意味着错误页面模板必须将请求作为参数。在 2.6.x 中,TODO 字段以前被渲染为静态结果而不是具有 HTTP 上下文的动作,因此可能在控制器之外被调用。在 2.7.0 中,TODO 字段已被删除,现在 play.mvc.Controller 中有一个 TODO(Http.Request request) 方法。

public abstract class Controller extends Results implements Status, HeaderNames {
    public static Result TODO(play.mvc.Http.Request request) {
        return status(NOT_IMPLEMENTED, views.html.defaultpages.todo.render(request.asScala()));
    }
}

§内部更改

Play 的内部 API 进行了许多更改。这些 API 用于内部,不遵循正常的弃用流程。以下可能会提到更改,以帮助那些直接与 Play 内部 API 集成的人。

§配置更改

§play.allowGlobalApplication 默认值为 false

在 Play 2.7.0 中,默认情况下设置了 play.allowGlobalApplication = false。这意味着 Play.current 在被调用时会抛出异常。您可以将其设置为 true 以使 Play.current 和其他已弃用的静态帮助程序再次工作,但请注意,此功能将在将来的版本中删除。

将来,如果您仍然需要使用应用程序组件的静态实例,可以使用 静态注入 使用 Guice 注入它们,或者在应用程序加载器启动时手动设置静态字段。只要您小心不要并发运行应用程序(例如,在测试中),这些方法应该与 Play 的未来版本向前兼容。

由于 Play.current 仍然被一些已弃用的 API 调用,因此在使用这些 API 时,您需要在 application.conf 文件中添加以下行

play.allowGlobalApplication = true

例如,当使用带有嵌入式 Play 和 Scala Sird 路由器play.api.mvc.Action 对象时,它会访问全局状态

import play.api.mvc._
import play.api.routing.sird._
import play.core.server._

// It can also be NettyServer
val server = AkkaHttpServer.fromRouter() {
  // `Action` in this case is the `Action` object which access global state
  case GET(p"/") => Action {
    Results.Ok(s"Hello World")
  }
}

上面的示例需要您配置 play.allowGlobalApplication = true(如前所述),或者重写为

import play.api._
import play.api.mvc._
import play.api.routing.sird._
import play.core.server._

// It can also be NettyServer
val server = AkkaHttpServer.fromRouterWithComponents() { components: BuiltInComponents => {
    case GET(p"/") => components.defaultActionBuilder {
      Results.Ok(s"Hello World")
    }
  }
}

§HikariCP 不会快速失败

Play 2.7 将 HikariCP 的 initializationFailTimeout 的默认值更改为 -1。这意味着即使数据库不可用,您的应用程序也会启动。您可以通过将 initializationFailTimeout 配置为 1 来恢复旧行为,这将使池快速失败。

如果应用程序使用数据库 Evolutions,则在应用程序启动时会请求连接以验证是否有新的演变需要应用。因此,如果数据库不可用,则启动将失败,因为需要连接。然后,超时将由 connectionTimeout 定义(默认值为 30 秒)。

有关更多详细信息,请参阅 SettingsJDBC

§协调关闭 play.akka.run-cs-from-phase 配置

配置 akka.coordinated-shutdown.exit-jvm 不再受支持。启用该设置后,Play 将不会启动,并且会记录错误。Play 附带了 akka.coordinated-shutdown.* 的默认值,这些值应该适用于大多数场景,因此您不太可能需要覆盖它们。

配置 play.akka.run-cs-from-phase 不再受支持,添加它不会影响应用程序关闭。如果存在,将记录警告。Play 现在运行所有阶段以确保在 ApplicationLifecycle 中注册的所有钩子和添加到协调关闭的所有任务都已执行。如果您需要从特定阶段运行 CoordinatedShutdown,您始终可以手动执行。

import akka.actor.ActorSystem
import javax.inject.Inject

import akka.actor.CoordinatedShutdown
import akka.actor.CoordinatedShutdown.Reason

class Shutdown @Inject()(actorSystem: ActorSystem) {

  // Define your own reason to run the shutdown
  case object CustomShutdownReason extends Reason

  def shutdown() = {
    // Use a phase that is appropriated for your application
    val runFromPhase = Some(CoordinatedShutdown.PhaseBeforeClusterShutdown)
    val coordinatedShutdown = CoordinatedShutdown(actorSystem).run(CustomShutdownReason, runFromPhase)
  }
}

以及 Java

import akka.actor.ActorSystem;
import akka.actor.CoordinatedShutdown;

import javax.inject.Inject;
import java.util.Optional;

class Shutdown {

    public static final CoordinatedShutdown.Reason customShutdownReason = new CustomShutdownReason();

    private final ActorSystem actorSystem;

    @Inject
    public Shutdown(ActorSystem actorSystem) {
        this.actorSystem = actorSystem;
    }

    public void shutdown() {
        // Use a phase that is appropriated for your application
        Optional<String> runFromPhase = Optional.of(CoordinatedShutdown.PhaseBeforeClusterShutdown());
        CoordinatedShutdown.get(actorSystem).run(customShutdownReason, runFromPhase);
    }

    public static class CustomShutdownReason implements CoordinatedShutdown.Reason {}
}

§检查应用程序密钥的最小长度

在生产环境中,将检查 应用程序密钥 配置 play.http.secret.key 的最小长度。如果密钥少于十五个字符,则会记录警告。如果密钥少于八个字符,则会抛出错误,并且配置无效。您可以通过将密钥设置为至少 32 字节的完全随机输入(例如 head -c 32 /dev/urandom | base64)或使用应用程序密钥生成器(使用 playGenerateSecretplayUpdateSecret)来解决此错误。

应用程序密钥 用作确保 Play 会话 cookie 有效的密钥,即由服务器生成而不是由攻击者伪造。但是,密钥只指定一个字符串,并不确定该字符串中的熵量。无论如何,可以通过简单地测量密钥的长度来对密钥中的熵量设置上限:如果密钥长度为八个字符,则最多为 64 位熵,这在现代标准中是不够的。

§play.filters.headers.contentSecurityPolicy 已弃用,请使用 CSPFilter

The SecurityHeaders 过滤器 有一个 contentSecurityPolicy 属性:它在 2.7.0 中已弃用。contentSecurityPolicy 已从 default-src 'self' 更改为 null - null 的默认设置意味着不会将 Content-Security-Policy 标头添加到来自 SecurityHeaders 过滤器的 HTTP 响应中。请使用新的 CSPFilter 来启用 CSP 功能。

如果 play.filters.headers.contentSecurityPolicy 不为 null,您将收到警告。从技术上讲,可以同时启用 contentSecurityPolicy 和新的 CSPFilter,但不建议这样做。

您可以通过将新的 CSPFilter 添加到 play.filters.enabled 属性来启用它

play.filters.enabled += play.filters.csp.CSPFilter

注意:您需要仔细查看内容安全策略以确保它满足您的需求。新的 CSPFilterdefault-src ‘self’ 更宽松,并且基于 Google 严格 CSP 配置。您可以使用 CSP 报告控制器report-only 功能来查看策略违规。

有关更多信息,请参阅 CSPFilter 中的文档。

在 Play 2.6 中,SameSite cookie 属性 已为会话和闪存默认启用
从 Play 2.7 开始,CSRF 和语言 cookie 也是如此。默认情况下,CSRF cookie 的 SameSite 属性将与会话 cookie 的值相同,而语言 cookie 将默认使用 SameSite=Lax
您可以使用配置来调整它。例如

play.filters.csrf.cookie.sameSite = null // no same-site for csrf cookie
play.i18n.langCookieSameSite = "strict" // strict same-site for language cookie

§默认值更改

Play 使用的一些默认值已更改,这可能会影响您的应用程序。本节详细介绍了默认值的更改。

§application/javascript 作为 JavaScript 的默认内容类型

application/javascript 现在是为 JavaScript 返回的默认内容类型,而不是 text/javascript。对于生成的 <script> 标签,我们现在也省略了 type 属性。有关省略 type 属性的更多详细信息,请参阅 HTML 5 规范

§自签名 HTTPS 证书的更改

现在它是在target/dev-mode/generated.keystore下生成,而不是直接在根文件夹下生成。

§text/plain 内容类型上的默认字符集更改

文本和容错文本主体解析器现在使用US-ASCII作为默认字符集,替换了之前的默认字符集ISO-8859-1

这是因为一些较新的 HTTP 标准,特别是RFC 7231,附录 B,其中指出“文本媒体类型的 ISO-8859-1 默认字符集已被删除;默认值现在是媒体类型定义中所述的任何值。”text/plain 媒体类型定义由RFC 6657,第 4 节定义,该定义指定 US-ASCII。文本和容错文本主体解析器使用text/plain作为内容类型,因此现在默认情况下适用。

§更新的库

本节列出了对我们依赖项进行的重大更新。

§Akka 更新

Play 2.7 使用最新版本的 Akka 2.5 系列。混合版本的 Akka 库是不允许的,并且最新版本在检测到使用多个版本的 Akka 工件时会记录警告。您会看到类似以下内容

Detected possible incompatible versions on the classpath. Please note that a given Akka version MUST be the same across all modules of Akka that you are using, e.g. if you use [2.5.19] all other modules that are released together MUST be of the same version. Make sure you're using a compatible set of libraries. Possibly conflicting versions [2.5.4, 2.5.19] in libraries [akka-actor:2.5.19, akka-remote:2.5.4]

在此示例中,修复方法是将akka-remote更新到 Play 使用的相同版本,例如

val AkkaVersion = "2.5.19" // should match the version used by Play

libraryDependencies += "com.typesafe.akka" %% "akka-remote" % AkkaVersion

如果您的应用程序使用的是比 Play 使用的版本更新的版本,您可以在build.sbt文件中更新 Akka 版本

§HikariCP 更新

HikariCP 已更新到最新版本,该版本最终删除了配置initializationFailFast,并将其替换为initializationFailTimeout。请参阅HikariCP 变更日志initializationFailTimeout 的文档,以更好地了解如何使用此配置。

§Guava 版本更新到 27.1-jre

Play 2.6.x 提供了 23.0 版本的 Guava 库。现在它已更新到最新的实际版本 27.1-jre。库中进行了许多更改,您可以查看完整的变更日志此处

§specs2 更新到 4.3.5

之前的版本是3.8.x。有很多更改和改进,因此我们建议您阅读最近版本的 Specs2 的发行说明。使用的版本更新了Mockito 版本,更新到2.18.x,因此我们也对其进行了更新。

§Jackson 更新至 2.9

Jackson 版本已从 2.8 更新至 2.9。此版本的发布说明 在此。这是一个保持兼容性的版本,因此您的应用程序应该不会受到影响。但您可能对新功能感兴趣。

§Hibernate Validator 更新至 6.0

Hibernate Validator 已更新至 6.0 版本,现在与 Bean Validation 2.0 兼容。查看 此处 的新增内容,或阅读 这篇关于新版本的详细博客文章

注意:请记住,此版本可能与项目中可能存在的其他 Hibernate 依赖项不完全兼容。例如,如果您使用的是 hibernate-jpamodelgen,则需要使用最新版本以确保一切正常工作。

// Visit https://mvnrepository.com/artifact/org.hibernate/hibernate-jpamodelgen to see the list of versions available
libraryDependencies += "org.hibernate" % "hibernate-jpamodelgen" % "5.3.7.Final" % "provided"

§已删除的库

为了使默认的 Play 发行版更小,我们删除了一些库。以下库不再是 Play 2.7 中的依赖项,因此如果您使用它们,则需要手动将它们添加到您的构建中。

§已删除 BoneCP

BoneCP 已被删除。如果您的应用程序配置为使用 BoneCP,则需要切换到 HikariCP,它是默认的 JDBC 连接池。

play.db.pool = "default"  # Use the default connection pool provided by the platform (HikariCP)
play.db.pool = "hikaricp" # Use HikariCP

您可能需要重新配置池以使用 HikariCP。例如,如果您想配置 HikariCP 的最大连接数,则操作如下。

play.db.prototype.hikaricp.maximumPoolSize = 15

有关更多详细信息,请参阅 JDBC 配置部分

此外,您可以通过指定完全限定的类名来使用实现 play.api.db.ConnectionPool 的自己的池。

play.db.pool=your.own.ConnectionPool

§Apache Commons (commons-lang3commons-codec)

Play 在内部使用了一些 commons-codeccommons-lang3,如果您在项目中使用它们,则需要将其添加到您的 build.sbt 中。

// Visit https://mvnrepository.com/artifact/commons-codec/commons-codec to see the list of versions available
libraryDependencies += "commons-codec" % "commons-codec" % "1.11"

对于 commons-lang3

// Visit https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 to see the list of versions available
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.8.1"

§其他重要更改

§DEV 模式下需要应用 evolutions 脚本时,应用程序启动

在 Play 2.6 之前,当数据库需要在 DEV 模式下执行演变脚本时,应用程序会在启动时中止。因此,依赖于 ApplicationEvolutions 的模块甚至不会被初始化。这意味着你可以确定,如果你在一个模块中依赖于 ApplicationEvolutions,那么在模块初始化时,所有演变脚本都已成功执行,并且你可以例如在该模块中插入数据库数据,依赖于演变脚本创建了你的查询所需的表或其他数据库对象。

然而,从 Play 2.7 开始,在 DEV 模式下,应用程序(以及所有模块)将始终启动,无论是否需要应用演变脚本。这意味着你不能依赖于演变脚本已成功执行并且在模块初始化时某个数据库结构可用这一事实。
这就是我们添加 ApplicationEvolutions.upToDate 的原因,它指示应用演变的过程是否已完成。只有当该方法返回 true 时,你才能确定所有演变脚本都已成功执行。upToDate 最终会在某个时刻返回 true,因为每次你在 DEV 模式下应用或解决演变脚本时,应用程序都会自动重启,重新初始化所有模块。

§演变注释语法

Play 演变现在正确支持 SQL92 注释语法。这意味着你可以使用 -- 在行首编写演变,而不是 #,无论你选择什么。使用演变 API 生成的全新演变现在也会在所有区域使用 SQL92 风格的注释语法。文档也已相应更新,以优先使用 SQL92 风格,尽管旧的注释风格仍然完全支持。

§查询字符串参数绑定行为已更改

§当参数值为空时(例如 ?myparam=

定义以下类型查询字符串参数的路由

并且被包装在以下类型中

在 Play 2.6 之前,如果请求的查询字符串参数为空(例如 ?myparam=),此类情况将返回 400 Bad Request
这是因为无法从空字符串解析上述任何类型(例如,在 Scala 中,"".toInt 会抛出异常,因为所有其他上述类型在解析方法中都会这样做)。

从 Play 2.7 开始,将不再出现错误请求,而是将 None(对于 Scala 的 Option)、Optional.empty()(对于 Java 的 Optional)或空列表传递给此类查询参数的 action 方法。
如果定义了默认值(例如 myparam: Option[Int] ?= Option(123)),则当然会传递该默认值。

注意:如果上述类型未包装在 OptionOptional 或列表中,例如 myparam: Int ?= 3,则默认值行为也会发生变化,在 Play 2.7 之前,这也会导致 400 Bad Request,而不是获取默认值。

§当参数根本不存在时

为包装类型的查询字符串参数定义默认值的路由

当此类请求的查询字符串参数根本不存在时,不会将该默认值传递给 action 方法。而是传递 NoneOptional.empty() 或空列表。

从 Play 2.7 开始,现在将默认值传递给此类不存在的查询参数的 action 方法。

§multipart/form-data 文件上传更改

在 Play 2.6 之前,通过 multipart/form-data 编码上传空文件与上传非空文件相同。然而,出于显而易见的原因,上传空文件没有意义,因此从 Play 2.7 开始,上传的空文件将被视为根本没有上传文件。
因此,通过 Scala APIJava API 检索上传的文件时,它永远不会为空。

注意:如果 multipart/form-data 文件上传部分的 filename 标头为空,即使文件本身不为空,也会应用相同的逻辑。

§Twirl 语法解析改进

为了改进 Twirl 模板的解析,一些以前有效的代码将不再被支持。更多详情请参见 问题

下一步: Java Http.Context 更改


发现本文档中的错误?此页面的源代码可以在 这里 找到。阅读完 文档指南 后,请随时贡献一个 pull 请求。有疑问或建议要分享?请访问 我们的社区论坛,与社区进行交流。