- 后端
- Web框架
【免费下载链接】playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
Play Framework 2.5 是一次以「去全局状态、拥抱 Java 8、统一流式处理」为核心的大版本升级,涉及构建配置、Scala 版本、路由生成器、依赖注入、CSRF 安全策略、WS 客户端与 Netty 底层等多个层面。本文以官方 Migration25 指南为主体,结合当前仓库中的实际源码与参考配置,系统讲解从 Play 2.4 迁移到 2.5 的全部步骤、关键破坏性变更及其背后的实现原理,帮助你在升级后获得可运行、可验证、贴近最佳实践的 Play 2.5 应用。
提示:如果你需要从更早的版本升级,请先阅读 Play 2.4 Migration Guide。此外,两个专题迁移指南提供了更细粒度的信息:Streams Migration Guide(迁移到 Akka Streams)与 Java Migration Guide(Java 应用迁移到原生 Java 8 类型)。
第一步:升级构建配置
升级 Play 2.4 → 2.5 的第一步是修改 sbt 构建,使项目能在 sbt 中正常加载与运行。
Play 版本升级
在project/plugins.sbt中把 Play 的 sbt 插件版本升级到 2.5.x:
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.x")其中2.5.x的x表示你想要使用的次版本号,例如2.5.0。
sbt 升级到 0.13.11
Play 2.5 仍然兼容 sbt 0.13.8,但官方推荐升级到 0.13.11(该版本包含大量改进与 bug 修复)。将project/build.properties更新为:
sbt.version=0.13.11Play Slick 升级
如果你的项目使用 Play Slick,需要升级到 2.0.0:
libraryDependencies += "com.typesafe.play" %% "play-slick" % "2.0.0"如果同时使用 evolutions 支持:
libraryDependencies ++= Seq( "com.typesafe.play" %% "play-slick" % "2.0.0", "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0" )Play Ebean 升级
如果使用 Play Ebean,升级其 sbt 插件:
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "3.0.0")ScalaTest + Play 升级
如果使用 ScalaTest + Play,升级测试依赖:
libraryDependencies ++= Seq( "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % "test" )Scala 2.10 支持终止:全面迁移到 Scala 2.11
Play 2.3 与 2.4 同时支持 Scala 2.10 和 2.11,而 Play 2.5 终止了对 Scala 2.10 的支持,仅支持 Scala 2.11。原因有二:
- Play 2.5 内部大量使用
scala-java8-compat库(该库仅支持 Scala 2.11),它提供了 Scala 与 Java 8 类型之间的转换,例如 ScalaFuture与 JavaCompletionStage的互转。这个库对应用代码同样很有用。 - 下一代 Play 计划支持 Scala 2.12,先统一到 2.11 能让后续过渡更平滑。
迁移步骤
Scala 与 Java 用户都必须在 sbt 中配置 Scala 2.11——即使项目没有任何 Scala 代码,Play 本身使用 Scala,必须为其配置正确的 Scala 库。
在 sbt 中设置scalaVersion即可:
scalaVersion := "2.11.8"单项目构建可直接把该设置放在build.sbt中。多项目构建则必须为每个项目设置,通常放在所有项目共享的公共设置里:
def common = Seq( scalaVersion := "2.11.8" ) lazy val projectA = (project in file("projectA")) .enablePlugins(PlayJava) .settings(common: _*) lazy val projectB = (project in file("projectB")) .enablePlugins(PlayJava) .settings(common: _*)Logback 配置变更:ColoredLevel 包路径迁移
为了移除 Play 对 Logback 的硬编码依赖(详见 Highlights25 的 Support for other logging frameworks 一节),Logback 配置所用的一个类被移动到了新的包。
迁移步骤
更新logback*.xml中所有对旧类play.api.Logger$ColoredLevel的引用,改为新的play.api.libs.logback.ColoredLevel:
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />当前仓库中该类的实现位于 ColoredLevel.scala,它继承自 logback 的ClassicConverter,把日志级别渲染成带颜色的小写文本(TRACE 蓝色、DEBUG 青色、INFO 白色、WARN 黄色、ERROR 红色),对应配置中的%coloredLevel转换词。
如果你使用编译期依赖注入(compile time DI),需要把 application loader 中的Logger.configure(...)替换为:
LoggerConfigurator(context.environment.classLoader).foreach { _.configure(context.environment) }LoggerConfigurator是 Play 面向日志框架的抽象接口(定义于 LoggerConfigurator.scala),提供多个configure重载,分别接收Environment、Configuration与属性映射。这一设计正是 2.5 支持任意 SLF4J 兼容日志框架的基础:默认仍使用 Logback,但可以通过disablePlugins(PlayLogback)移除,再引入自定义框架的 SLF4J adapter,详见 SettingsLogger 的 Using a Custom Logging Framework 一节。
Play WS 升级到 AsyncHttpClient 2
Play WS 底层升级为 AsyncHttpClient 2(基于 Netty 4.0)。大部分变化在底层,但 AHC 2.0 的一些重大重构带来了 WS API 的破坏性变更:
AsyncHttpClientConfig被DefaultAsyncHttpClientConfig取代。allowPoolingConnection与allowSslConnectionPool在 AsyncHttpClient 中被合并为单一的keepAlive变量。因此play.ws.ning.allowPoolingConnection与play.ws.ning.allowSslConnectionPool不再有效,配置它们会抛出异常。webSocketIdleTimeout已移除,AhcWSClientConfig中不再提供。ioThreadMultiplier已移除,AhcWSClientConfig中不再提供。FluentCaseInsensitiveStringsMap类被删除,由 Netty 的HttpHeader类取代。Realm.AuthScheme.None已移除,WSAuthScheme中不再提供。
此外还有一些小变更:
- 为反映正确的 AsyncHttpClient 库名,包
play.api.libs.ws.ning更名为play.api.libs.ws.ahc,Ning*类更名为Ahc*;AHC 配置前缀也从play.ws.ning改为play.ws.ahc,例如play.ws.ning.maxConnectionsPerHost现在是play.ws.ahc.maxConnectionsPerHost。 - 已废弃的接口
play.libs.ws.WSRequestHolder被移除。 play.libs.ws.play.WSRequest接口现在返回java.util.concurrent.CompletionStage而非F.Promise(这是 Java 8 化改造的一部分,参见 JavaMigration25)。- 依赖
Play.current或Play.application的静态方法被标记为废弃。 - 2.5 之前 Play WS 会从 Content-Type 推断字符集,并在请求头未设置字符集时自动追加到
Content-Type头;该行为曾引发混淆与 bug,因此在 2.5.x 中Content-Type头不再自动携带推断出的字符集。如果显式设置Content-Type头,则按原样生效。
从当前仓库的源码结构看,AHC 相关的客户端实现位于 play-ahc-ws 模块,AhcWSClient的构造函数接收AhcWSClientConfig(见 AhcWSClient.scala),AhcWSModule则通过AhcWSClientConfigParser从play.ws.ahc前缀的配置解析客户端参数(见 AhcWSModule.scala)。
GlobalSettings 被废弃
作为 Play 持续去除全局状态工作的一部分,GlobalSettings与应用Global对象被标记为废弃。如何迁移离开GlobalSettings的详细说明,参见 Play 2.4 迁移指南中的 GlobalSettings 章节。Play 2.4 起推荐的替代方案是将GlobalSettings的职责拆分为HttpErrorHandler、HttpRequestHandler与HttpFilters三个可注入组件(对照表见 Migration24 的 Dependency Injected Components 一节)。
Plugins API 被移除
Plugins API 在 Play 2.4 中被标记为废弃,并在 Play 2.5 中正式移除。它已被 Play 的依赖注入与模块系统取代,后者提供了更干净、更灵活的方式构建可复用组件。从插件迁移到依赖注入的细节参见 Play 2.4 迁移指南的 PluginsToModules 章节。
路由默认使用 InjectedRoutesGenerator
Play 2.5 起,路由默认由依赖注入感知的InjectedRoutesGenerator生成,而非假设控制器是单例对象的StaticRoutesGenerator。
从源码看,InjectedRoutesGenerator定义于 RoutesGenerator.scala,其id为"injected",用于增量编译时判断路由生成器是否发生变化。它的核心逻辑是按控制器分组生成依赖描述符(Dependency):对每个路由,若使用@语法(instantiate为 true),则依赖jakarta.inject.Provider[控制器类],否则直接依赖控制器类(见 RoutesGenerator.scala 第 126-140 行)。也就是说,@前缀让路由器每次按需通过 Provider 获取控制器实例。在 RoutesCompiler.scala 中,InjectedRoutesGenerator也是默认传入的生成器。
如果代码中仍然使用object MyController这类静态控制器,想恢复旧行为,可以在build.sbt中添加:
routesGenerator := StaticRoutesGenerator如果使用Build.scala而不是build.sbt,需要导入routesGenerator设置键:
import play.sbt.routes.RoutesCompiler.autoImport._使用静态控制器配合静态路由生成器并不算废弃,但官方推荐迁移到使用依赖注入的类。
静态控制器替换为依赖注入
controllers.ExternalAssets现在是一个类,不再提供静态等价物。controllers.Assets与controllers.Default也是类,虽然静态等价物仍然存在,但推荐使用类版本。当前仓库中ExternalAssets的实现位于 ExternalAssets.scala,通过@Inject构造注入,专门用于从外部目录提供静态资源(该控制器不适合在生产模式使用,源码注释明确说明它会在生产模式自动禁用)。
迁移步骤
推荐方案是让所有控制器都使用类。由于InjectedRoutesGenerator现在是默认路由生成器,routes文件中的控制器会被当作类而非对象。
如果仍有静态控制器,可以使用StaticRoutesGenerator(见上文),并在routes文件中给路由加@符号,例如:
GET /assets/*file @controllers.ExternalAssets.at(path = "/public", file)play.Play 与 play.api.Play 方法被废弃
play.Play中以下方法已被废弃:
public static Application application()public static Mode mode()public static boolean isDev()public static boolean isProd()public static boolean isTest()
同样,play.api.Play中接受隐式Application并委托给 Application 的方法(例如def classloader(implicit app: Application))也已被废弃。
迁移步骤
这些方法实际委托给play.Application或play.Environment——使用它们的代码应改为通过依赖注入获取相应组件。Play 内置组件的注入方式对照表参见 Play 2.4 迁移指南的 Dependency Injected Components 一节。
例如,下面的 Scala 控制器把 environment 与 configuration 注入进来:
class HomeController @Inject() (environment: play.api.Environment, configuration: play.api.Configuration) extends Controller { def index = Action { Ok(views.html.index("Your new application is ready.")) } def config = Action { Ok(configuration.underlying.getString("some.config")) } def count = Action { val num = environment.resource("application.conf").toSeq.size Ok(num.toString) } }play.api.Environment的实现位于 Environment.scala,它封装了应用部署的 rootPath、classLoader 与 mode 三个关注点,并提供getFile、resource等基于 rootPath/classloader 的资源访问方法——这正是上面示例中environment.resource("application.conf")的底层能力。
处理遗留组件
通常你的组件不需要依赖整个应用,但有时不得不处理要求传入 Application 的遗留组件。可以通过把应用注入到某个组件中来解决:
class FooController @Inject() (appProvider: Provider[Application]) extends Controller { implicit lazy val app = appProvider.get() def bar = Action { Ok(Foo.bar(app)) } }注意此时通常应使用Provider[Application]以避免循环依赖。
更好的做法是自建一个*Api类,把静态方法包装成实例方法:
class FooApi @Inject() (appProvider: Provider[Application]) { implicit lazy val app = appProvider.get() def bar = Foo.bar(app) def baz = Foo.baz(app) }这样既能享受依赖注入带来的可测试性,又能继续使用依赖全局状态的库。
Content-Type 字符集变更
在 Play 2.5 之前,Play 会为某些未定义 charset 参数的 Content-Type(特别是application/json与application/x-www-form-urlencoded)自动追加charset参数。现在Content-Type默认不带 charset 发送,无论是对 WS 发送请求,还是从 Play action 返回响应。如果存在不符合规范、要求必须携带 charset 参数的客户端或服务端,可以显式设置Content-Type头。
Guice injector 与 Guice builder 变更
默认情况下 Guice 可以通过代理环中的接口来解析循环依赖。由于循环依赖通常是代码坏味道,且可以通过注入 Provider 打破循环,Play 选择在默认 Guice injector 上禁用该特性。其他 DI 框架大多不具备此特性,保留它也会在编写 Play 模块时引发问题。
现在GuiceInjectorBuilder与GuiceApplicationBuilder提供了四个新方法来自定义 Guice 的注入行为:
disableCircularProxies:禁用上面提到的通过代理接口解析循环依赖的行为;如需允许代理,使用disableCircularProxies(false)。requireExplicitBindings:指示 injector 只注入在模块中显式绑定的类,在测试中可用于校验绑定。requireAtInjectOnConstructors:要求构造器带有@Inject注解才能实例化类。requireExactBindingAnnotations:禁用 Guice 中容易出错的行为——在注入@Named("foo") Foo时用@Named Foo的绑定来替代。
这些方法的实现可以在 GuiceInjectorBuilder.scala 中找到:它们通过BinderOption枚举(DisableCircularProxies、RequireAtInjectOnConstructors、RequireExactBindingAnnotations、RequireExplicitBindings,见第 400-403 行)累积到 builder 中,最终应用到 GuiceBinder。其中disableCircularProxies默认启用(disable: Boolean = true),另外三个选项默认关闭,与迁移指南的描述一致。
CSRF 变更:默认策略大幅收紧
为了让 Play 的 CSRF 过滤器更能抵御浏览器插件漏洞与新扩展,CSRF 过滤器的默认配置变得极为保守。主要变化包括:
- 不再黑名单化
POST请求,而是只白名单GET、HEAD、OPTIONS,其余所有请求都需要 CSRF 检查——这意味着DELETE与PUT请求现在也会被检查。 - 不再黑名单化
application/x-www-form-urlencoded、multipart/form-data与text/plain,而是所有 Content-Type(包括无 Content-Type)的请求都需要 CSRF 检查。一个直接后果是:使用application/json的 AJAX 请求现在必须在Csrf-Token头中携带合法 CSRF token。 - 基于无状态头的绕过机制(如
X-Requested-With)默认被禁用。
同时新增了一个配置项,可针对携带特定头的请求绕过新的 CSRF 保护。该配置项默认对 Cookie 与 Authorization 头启用,这样不使用 session 认证的 REST 客户端无需发送 CSRF token 也能正常工作。
但需要注意:由于该配置项会放行所有没有这些头的请求,使用其他认证方案(NTLM、TLS 客户端证书)的应用将面临 CSRF 风险。这类应用应禁用该配置项,使其无 cookie 的已认证请求也受到 CSRF 过滤器保护。
最后还新增了一个选项:对 CORS 过滤器信任的来源跳过 CSRF 检查。注意 CORS 过滤器必须位于 CSRF 过滤器之前才能生效。当前仓库的 CSRF 过滤器实现中,该逻辑体现在 CSRFActions.scala 第 493 行:当csrfConfig.bypassCorsTrustedOrigins为 true 且请求属性中带有 CORS 过滤器写入的Origin时,跳过 CSRF 检查。相关配置类CSRFConfig定义于 csrf.scala,其默认headerName为"Csrf-Token"、bypassCorsTrustedOrigins默认为true。
play-filters-helpers 模块的 reference.conf 展示了 2.5 收紧后的默认配置:
play.filters.csrf { # 由 CORS 过滤器信任的来源可绕过 CSRF 检查 bypassCorsTrustedOrigins = true header { # 接受 CSRF token 的请求头名称 name = "Csrf-Token" # 必须存在才会执行 CSRF 检查的头;默认为 Cookie 与 Authorization, # 设为 null 或空对象则保护所有请求 protectHeaders { Cookie = "*" Authorization = "*" } # 存在即绕过的请求头(默认为空) bypassHeaders {} } method { # 非空时,不在此列表中的方法都会被检查 whiteList = ["GET", "HEAD", "OPTIONS"] # 仅当 whiteList 为空时才使用 blackList = [] } contentType { whiteList = [] blackList = [] } }如需恢复 Play 旧版的默认行为,可在application.conf中添加如下配置:
play.filters.csrf { header { bypassHeaders { X-Requested-With = "*" Csrf-Token = "nocheck" } protectHeaders = null } bypassCorsTrustedOrigins = false method { whiteList = [] blackList = ["POST"] } contentType.blackList = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"] }获取 CSRF token
此前可以在任意 action 中从 HTTP 请求获取 CSRF token。现在必须有 CSRF 过滤器或 CSRF action,CSRF.getToken才能工作。如果未使用过滤器,可以在 Scala 中使用CSRFAddTokenaction、在 Java 中使用AddCSRFToken注解,确保 session 中有 token。
另外本版本修复了一个小 bug:此前若 token 签名无效,CSRF token 会变为空(导致模板 helper 抛异常);现在它会在同一请求内重新生成,因此模板 helper 与CSRF.getToken仍能取到 token。
Java 与 Scala 的 CSRF 完整文档分别见 JavaCsrf 与 ScalaCsrf。
Crypto 被废弃:拆分为专用签名器
自 Play 1.x 起,Play 就带有一个提供加密操作的Crypto对象(Play 内部使用,文档未正式提及,仅在 scaladoc 中被称为「cryptographic utilities」)。由于多种原因,以便捷工具形式提供加密能力被证明不可行。在 2.5.x 中,Play 专属功能被拆分为CookieSigner、CSRFTokenSigner与AESSigner三个 trait,Crypto单例对象被废弃。
迁移方法
加密迁移取决于你的使用场景,尤其是是否存在不安全的加密原语构造方式。简而言之:尽量使用 Kalium;否则使用 Tink 或直接使用 JCA。详细方案参见 Crypto Migration Guide。
从当前仓库源码看,拆分后的签名器实现在 CookieSigner.scala 与 CSRFTokenSigner.scala:
CookieSigner负责对 cookie 内容计算消息认证码(MAC),DefaultCookieSigner通过SecretConfiguration读取应用密钥(play.crypto.secret),其sign(message: String): String返回十六进制编码的签名;trait 的文档明确警告它「不应作为通用 MAC 工具使用」。CSRFTokenSigner提供signToken、extractSignedToken、verifySignedToken与generateSignedToken等方法,用于对 CSRF token 进行签名、提取与校验。
CryptoMigration25 中还强调了几个关键安全结论:不要用Crypto.sign或任何 HMAC 做密码哈希(密码哈希应当慢而昂贵,如 scrypt、bcrypt、PBKDF2);Crypto.encryptAES默认使用 AES-CTR 模式,只加密不认证,存在可延展性(malleability)问题;Play 内部对 session cookie 采用「先加密后 MAC」构造,因此不受影响。更多细节可阅读 CryptoMigration25。
Netty 4 升级:Channel 选项配置键变更
Netty 从 3.10 升级到 4.0,一个直接后果是 Netty channel 选项的配置方式发生了变化。完整选项列表以 Netty 4.0 的ChannelOptionAPI 为准。
迁移步骤
将所有play.server.netty.option键修改为ChannelOption中定义的新键。常用映射如下:
| 旧键 | 新键 |
|---|---|
play.server.netty.option.backlog | play.server.netty.option.SO_BACKLOG |
play.server.netty.option.child.keepAlive | play.server.netty.option.child.SO_KEEPALIVE |
play.server.netty.option.child.tcpNoDelay | play.server.netty.option.child.TCP_NODELAY |
当前仓库中 play-netty-server 模块的 reference.conf 展示了这一命名体系的当前形态:监听服务器 socket 的选项定义在option顶层,接收客户端连接对应的 socket 选项以child.*前缀区分,例如SO_BACKLOG、child.SO_KEEPALIVE、child.TCP_NODELAY,并支持通过完整限定类名加#指定原生传输专属选项(如io.netty.channel.ChannelOption#TCP_FASTOPEN)。
sendFile / sendPath / sendResource 的默认行为变更
此前 Java(play.mvc.StatusHeader)与 Scala(play.api.mvc.Results.Status)的文件发送 API 在 inline 与 attachment 两种模式下表现不一致:
| API | 方法 | 默认值 |
|---|---|---|
| Scala | play.api.mvc.Results.Status.sendResource | inline |
| Scala | play.api.mvc.Results.Status.sendPath | attachment |
| Scala | play.api.mvc.Results.Status.sendFile | attachment |
| Java | play.mvc.StatusHeader.sendInputStream | none |
| Java | play.mvc.StatusHeader.sendResource | inline |
| Java | play.mvc.StatusHeader.sendPath | attachment |
| Java | play.mvc.StatusHeader.sendFile | inline |
也就是说,旧版本在发送文件时混用了 inline 与 attachment 模式。现在发送文件、路径与资源时默认统一使用inline行为。当然,你仍可通过这些方法的参数在两种模式间切换。
当前仓库中 Scala 端的实现位于 Results.scala:sendFile、sendPath、sendResource三个方法的inline: Boolean = true参数默认值均已统一为true(见第 622、642、666 行)。实现细节上,sendPath通过FileIO.fromPath流式读取文件,sendResource通过 classloader 的getResourceAsStream打开资源后以流式发送,二者都会结合Content-Disposition(由Results.contentDispositionHeader(inline, name)生成)与文件名的 MIME 类型推断来构建响应;onClose回调可用于发送后清理临时文件等场景。
迁移自检清单
完成上述步骤后,可以按以下清单快速自查迁移是否完整:
project/plugins.sbt中 sbt-plugin 版本为 2.5.x,project/build.properties中 sbt 版本为 0.13.11;scalaVersion已设置为 2.11.x,且多项目构建的每个 project 都生效;logback*.xml中不再引用play.api.Logger$ColoredLevel,编译期 DI 的 loader 使用LoggerConfigurator;- 路由生成器为默认的
InjectedRoutesGenerator,控制器改为类并注入依赖;仍有静态控制器时使用StaticRoutesGenerator且路由前加@; - 代码中不再调用废弃的
play.Play/play.api.Play静态方法,改用注入的Environment、Configuration等组件; - 检查
play.ws.ning.*配置是否全部迁移为play.ws.ahc.*; - CSRF 相关客户端(尤其是
application/jsonAJAX 与 REST 客户端)确认携带Csrf-Token头,或按需通过配置调整默认策略; play.server.netty.option.*已映射为 Netty 4.0 的ChannelOption键名;- 文件发送场景确认
sendFile/sendPath/sendResource的 inline 默认值是否符合预期。
Play 2.5 的迁移本质上是「拥抱 Java 8 与依赖注入、移除全局状态、统一流式处理」的一次整体演进:本指南覆盖的构建升级、Scala 2.11 迁移、路由生成器切换、Guice 行为收紧、CSRF 策略保守化与 Netty 4 升级,都是这一主线在具体 API 上的落地。对照上文各节与仓库源码逐项落实,即可平稳完成升级,并为后续版本(如 Scala 2.12 支持)打好基础。
- 后端
- Web框架
【免费下载链接】playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
相关推荐
Play 2.9 迁移指南:从 Play 2.8 升级到 Play 2.9 的完整实战手册
Play 2.9 迁移指南:从 Play 2.8 升级到 Play 2.9 的完整实战手册 导读 本文基于 Play Framework 官方仓库中的《Play
后端Web框架Play Framework Scala 3 迁移指南:从 Scala 2 平滑升级的完整实战手册
Play Framework Scala 3 迁移指南:从 Scala 2 平滑升级的完整实战手册 Play Framework(Java 与 Scala 的高
后端Web框架Play Framework 2.8 迁移指南:从 2.7 平滑升级的完整实践手册
Play Framework 2.8 迁移指南:从 2.7 平滑升级的完整实践手册 导读 :本文以 Play Framework 官方迁移文档为主体,结合本仓库
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考