§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 本身中进行一些更改。
§Cookie 存储处理
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
play.api.libs.ws.WSRequest.requestTimeout
现在返回Option[Duration]
而不是Option[Int]
。
§Java API
play.libs.ws.WSRequest.getUsername
现在返回Optional<String>
而不是String
。play.libs.ws.WSRequest.getContentType
现在返回Optional<String>
而不是String
。play.libs.ws.WSRequest.getPassword
现在返回Optional<String>
而不是String
。play.libs.ws.WSRequest.getScheme
现在返回Optional<WSScheme
而不是WSScheme
。play.libs.ws.WSRequest.getCalculator
现在返回Optional<WSSignatureCalculator>
而不是WSSignatureCalculator
。play.libs.ws.WSRequest.getRequestTimeout
现在返回Optional<Duration>
而不是long
。play.libs.ws.WSRequest.getRequestTimeoutDuration
已被移除,建议使用play.libs.ws.WSRequest.getRequestTimeout
。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 中以改进其功能的方法
play.libs.ws.WSRequest.getBody
返回为该请求配置的主体。在实现play.libs.ws.WSRequestFilter
时,这可能很有用。play.libs.ws.WSRequest.getMethod
返回为该请求配置的方法。play.libs.ws.WSRequest.getAuth
返回WSAuth
。play.libs.ws.WSRequest.setAuth
为该请求设置WSAuth
。play.libs.ws.WSResponse.getUri
获取该响应的URI
。
§BodyParsers API 一致性
解析器 API 之前使用 Integer
和 Long
来定义缓冲区长度,这可能会导致值溢出。现在配置已统一使用 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._
§添加到 FilePart
和 FileInfo
的新字段和方法
Scala's
和 Java's
FilePart
类有两个新的字段/方法,它们为您提供通过 multipart/form-data
编码上传的文件的文件大小和处置类型
fileSize
在 Scala API 中,以及getFileSize()
在 Java API 中dispositionType
在 Scala API 中,以及getDispositionType()
在 Java API 中
Scala 的 FileInfo
类现在也具有 dispositionType
字段。
如果您有包含 FilePart
或 FileInfo
的 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 Scala或Play 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.1 和 4.2.0 版本说明),这会导致以下重大变更
play.test.TestBrowser.waitUntil
现在期望一个java.util.function.Function
而不是com.google.common.base.Function
。- 在 Scala 中,当覆盖
AbstractModule
的configure()
方法时,您现在需要在该方法前面加上override
标识符(因为它现在不是抽象方法)。
§静态 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.Logger
和play.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.db.jpa.Transactional
play.db.jpa.JPAApi.em()
play.db.jpa.JPAApi.withTransaction(final Runnable block)
play.db.jpa.JPAApi.withTransaction(Supplier<T> block)
play.db.jpa.JPAApi.withTransaction(String name, boolean readOnly, Supplier<T> block)
如 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 Form
、DynamicForm
和 FormFactory
构造函数已更改
Form
、DynamicForm
和 FormFactory
类(在 play.data
中)的构造函数,以前使用 Validator
参数,现在改为使用 ValidatorFactory
参数。
此外,这些构造函数现在还需要一个 com.typesafe.config.Config
参数。
例如,new Form(..., validator)
现在变为 new Form(..., validatorFactory, config)
。
此更改仅在您使用构造函数实例化表单而不是仅使用 formFactory.form(SomeForm.class)
时才会影响您 - 最有可能是在测试中。
§Java 缓存 API 的 get
方法已弃用,取而代之的是 getOptional
Java cacheApi
的 getOptional
方法将结果包装在 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.BuiltInModule
和 play.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
)或使用应用程序密钥生成器(使用 playGenerateSecret
或 playUpdateSecret
)来解决此错误。
应用程序密钥 用作确保 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
注意:您需要仔细查看内容安全策略以确保它满足您的需求。新的
CSPFilter
比default-src ‘self’
更宽松,并且基于 Google 严格 CSP 配置。您可以使用 CSP 报告控制器 的report-only
功能来查看策略违规。
有关更多信息,请参阅 CSPFilter 中的文档。
§CSRF 和语言 cookie 的 SameSite 属性
在 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-lang3
和 commons-codec
)
Play 在内部使用了一些 commons-codec
和 commons-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=
)
定义以下类型查询字符串参数的路由
UUID
、Char
、Double
、Float
、Long
、Int
或Boolean
- 或它们的 Java 等效项
并且被包装在以下类型中
- Scala
Option
(例如myparam: Option[Int]
)或 - 一个 Java
Optional
(例如myparam: java.util.Optional[Integer]
)或 - 一个 Scala
List
(例如myparam: List[Int]
或 - 一个 Java
List
(例如myparam: java.util.List[Integer]
)
在 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)
),则当然会传递该默认值。
注意:如果上述类型未包装在
Option
、Optional
或列表中,例如myparam: Int ?= 3
,则默认值行为也会发生变化,在 Play 2.7 之前,这也会导致400 Bad Request
,而不是获取默认值。
§当参数根本不存在时
为包装类型的查询字符串参数定义默认值的路由
- 一个 Scala
Option
(例如myparam: Option[...] ?= Option(...)
)或 - 一个 Java
Optional
(例如myparam: java.util.Optional[...] ?= java.util.Optional.of(...)
)或 - 一个 Scala
List
(例如myparam: List[...] ?= List(...)
或 - 一个 Java
List
(例如myparam: java.util.List[...] ?= java.util.Arrays.asList(...)
)
当此类请求的查询字符串参数根本不存在时,不会将该默认值传递给 action 方法。而是传递 None
、Optional.empty()
或空列表。
从 Play 2.7 开始,现在将默认值传递给此类不存在的查询参数的 action 方法。
§multipart/form-data
文件上传更改
在 Play 2.6 之前,通过 multipart/form-data
编码上传空文件与上传非空文件相同。然而,出于显而易见的原因,上传空文件没有意义,因此从 Play 2.7 开始,上传的空文件将被视为根本没有上传文件。
因此,通过 Scala API 或 Java API 检索上传的文件时,它永远不会为空。
注意:如果
multipart/form-data
文件上传部分的filename
标头为空,即使文件本身不为空,也会应用相同的逻辑。
§Twirl 语法解析改进
为了改进 Twirl 模板的解析,一些以前有效的代码将不再被支持。更多详情请参见 问题。
下一步: Java Http.Context 更改
发现本文档中的错误?此页面的源代码可以在 这里 找到。阅读完 文档指南 后,请随时贡献一个 pull 请求。有疑问或建议要分享?请访问 我们的社区论坛,与社区进行交流。