- 后端
- Web框架
【免费下载链接】playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
本文基于 Play Framework 仓库中 GlobalSettings.md 编写,面向从 Play 2.3 及更早版本升级的应用。文章以该迁移文档为主体骨架,结合仓库内 HttpErrorHandler.scala、HttpRequestHandler.scala、ApplicationLifecycle.scala 等核心实现,逐方法说明
GlobalSettings各项钩子的替代方案,并给出可直接落地的迁移步骤与代码示例。
GlobalSettings是 Play 2.3 及更早版本中用于拦截应用生命周期与 HTTP 处理流程的全局钩子类。随着 Play 全面转向依赖注入(DI),官方强烈建议应用把GlobalSettings实现类中的代码尽可能迁移出去,理想情况下彻底删除这个类。本文按方法逐一给出 Scala 与 Java 两种 API 的迁移路径:启动逻辑交给 DI 构造器,停止逻辑交给ApplicationLifecycle,错误处理交给HttpErrorHandler,请求处理交给HttpRequestHandler,过滤逻辑交给HttpFilters。读完本文,你将能够把旧式GlobalSettings完全替换为组件化、可测试、由依赖注入管理的新式实现。
背景:为什么移除GlobalSettings
GlobalSettings曾经是 Play 应用的"万能钩子":它同时承担应用启动/停止回调、HTTP 错误处理、请求预处理、路由、过滤器注册、配置加载等大量职责。这种方式有几个明显问题:
- 全局可变状态难以测试:钩子逻辑游离于组件图之外,无法按需注入依赖;
- 职责混杂:错误处理、路由、过滤等横切逻辑全部堆在一个类里;
- 生命周期不可控:
beforeStart/onStart等回调与依赖的构造顺序没有类型安全的约束。
Play 自 2.4 起引入了一套组件化 API 替代这些钩子(从 HttpErrorHandler.scala 的@since 2.4.0注释可以印证这些组件自该版本起提供)。如果你还没有阅读依赖注入相关的指南,建议先阅读 Java 依赖注入指南 或 Scala 依赖注入指南,再对照本文进行迁移。
迁移总原则:构造器即启动,Lifecycle 即停止
在深入逐方法迁移前,先掌握两条贯穿全文的核心设计思想(在 ApplicationLifecycle.scala 的源码注释中有完整阐述):
- 构造器就是启动回调。DI 框架实例化某个类时,其构造器中的初始化代码就会执行。这样"什么时候启动、谁先启动"由组件依赖图决定,顺序是类型安全、可验证的。
- Play 只提供停止回调。因为构造器已经承担了"启动",Play 只通过
ApplicationLifecycle提供停止钩子;停止钩子按注册顺序的逆序执行,保证组件在被关闭前仍可安全使用它所依赖的组件。
Scala 应用迁移指南
启动逻辑:beforeStart/onStart→ 构造器 + 急切绑定
原来写在GlobalSettings.beforeStart和GlobalSettings.onStart里的代码,现在应该放进某个依赖注入类的构造器中——DI 框架加载该类时初始化即会执行。
如果这些代码必须在应用真正对外服务之前执行(例如预热缓存、连接远程系统),则需要急切初始化,也就是急切绑定(eager binding)。在 Scala 中通过Module声明绑定并追加.eagerly:
class MyModule extends play.api.inject.Module { def bindings(environment: play.api.Environment, configuration: play.api.Configuration) = Seq( bind[MyStartupService].toSelf.eagerly() ) }关于急切绑定的完整说明,参见 Scala 依赖注入指南中的 Eager bindings 一节。需要注意的是,急切绑定在开发模式(sbt run)与生产模式(sbt stage)下的初始化时机略有差异:开发模式下应用启动时创建、但可能延迟到首个请求才完全初始化,以便快速热重载;生产模式下则会在启动时立即完整初始化。
停止逻辑:onStop→ApplicationLifecycle停止钩子
在需要注册停止钩子的类中注入ApplicationLifecycle依赖,把onStop的实现移入传给addStopHook的Future中:
import play.api.inject.ApplicationLifecycle import jakarta.inject.{Inject, Singleton} import scala.concurrent.Future @Singleton class MyConnectionPool @Inject() (applicationLifecycle: ApplicationLifecycle) { private val pool = new SomeConnectionPool() applicationLifecycle.addStopHook { () => Future.successful(pool.shutdown()) } }停止钩子返回的Future应在其完成时兑现;如果立即完成并返回成功Future也是允许的。详细的停止/清理说明见 Scala 依赖注入指南。从源码可以看到,DefaultApplicationLifecycle 使用ConcurrentLinkedDeque保存钩子并通过hooks.push压栈,执行时依次poll,从而保证后注册的钩子先执行。
服务器错误:onError→HttpErrorHandler.onServerError
创建继承自HttpErrorHandler的类,把GlobalSettings.onError的实现移入HttpErrorHandler.onServerError方法:
import play.api.http.HttpErrorHandler import play.api.mvc.{RequestHeader, Result, Results} import scala.concurrent.Future class MyErrorHandler extends HttpErrorHandler { def onClientError(request: RequestHeader, statusCode: Int, message: String): Future[Result] = Future.successful(Results.Status(statusCode)(s"Client error: $statusCode")) def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = Future.successful(Results.InternalServerError("Server error occurred")) }onServerError处理 5xx 服务端错误,onClientError处理 4xx 客户端错误(statusCode 必须大于等于 400 且小于 500,源码对此有明确注释)。更多细节参见 Scala 错误处理指南。
请求接收:onRequestReceived→HttpRequestHandler.handlerForRequest
创建继承自HttpRequestHandler的类,把GlobalSettings.onRequestReceived的实现移入handlerForRequest方法:
import play.api.http.{DefaultHttpRequestHandler, HttpRequestHandler} import play.api.mvc.{Handler, RequestHeader} class MyRequestHandler extends DefaultHttpRequestHandler { override def handlerForRequest(request: RequestHeader): (RequestHeader, Handler) = { // 在这里执行原来 onRequestReceived 的逻辑 super.handlerForRequest(request) } }特别提醒:如果你原来的onRequestReceived实现中调用了super.onRequestReceived,那么应继承DefaultHttpRequestHandler而非HttpRequestHandler,并把所有super.onRequestReceived调用替换为super.handlerForRequest。handlerForRequest允许返回被修改(例如被打上路由信息标签)的请求和对应的Handler,Play 会把返回的请求继续传给错误处理器与过滤器,相关设计意图见 HttpRequestHandler.scala。参见 Scala 请求处理器指南。
路由请求:onRouteRequest→DefaultHttpRequestHandler.routeRequest
创建继承自DefaultHttpRequestHandler的类,把onRouteRequest的实现移入routeRequest方法:
import play.api.http.DefaultHttpRequestHandler import play.api.mvc.{Handler, RequestHeader} class MyRouter extends DefaultHttpRequestHandler { override def routeRequest(request: RequestHeader): Option[Handler] = { // 在这里执行原来 onRouteRequest 的逻辑,例如按请求参数选择不同路由器 super.routeRequest(request) } }源码中的默认实现是router.get().handlerFor(request),注释明确说明可以覆写此方法以实现"基于请求参数使用不同路由器"等自定义路由策略。
请求完成回调:onRequestCompletion(已弃用,不再被调用)
这个方法是已弃用的,并且 Play不再调用它。替代方案是创建一个自定义过滤器,把onDoneEnumerating回调挂到返回结果流的Enumerator上。过滤器创建方法见 Scala HTTP 过滤器指南。
处理器未找到:onHandlerNotFound→HttpErrorHandler.onClientError
创建继承自HttpErrorHandler的类,实现onClientError。注意该方法接收statusCode参数,所以你的实现应归结为:
if (statusCode == play.api.http.Status.NOT_FOUND) { // 在这里移入你原来 GlobalSettings.onHandlerNotFound 的实现 }错误请求:onBadRequest→HttpErrorHandler.onClientError
同样地,把onBadRequest的实现放入onClientError,并以状态码为判断条件:
if (statusCode == play.api.http.Status.BAD_REQUEST) { // 在这里移入你原来 GlobalSettings.onBadRequest 的实现 }实际上,DefaultHttpErrorHandler的内部实现正是这样做的:它的 onClientError 用statusCode match把BAD_REQUEST、FORBIDDEN、NOT_FOUND分别分发给onBadRequest、onForbidden、onNotFound这三个 protected 方法。因此,更优雅的做法是直接继承DefaultHttpErrorHandler并只覆写onBadRequest/onNotFound方法,而不是手动判断状态码。详见 Scala 错误处理指南。
配置加载:configure/onLoadConfig→ 配置文件或自定义ApplicationLoader
把GlobalSettings.configure和GlobalSettings.onLoadConfig中的逻辑改为:
- 尽可能把所有配置写入
application.conf等配置文件;或 - 创建你自己的
ApplicationLoader,通过GuiceApplicationBuilder.loadConfig加载配置。
进阶用法参见 Scala 依赖注入指南 中关于扩展 GuiceApplicationLoader 的内容。
过滤器:doFilter/WithFilters→HttpFilters
创建继承自HttpFilters的类,实现filters方法返回过滤器序列:
import play.api.http.{DefaultHttpFilters, HttpFilters} import play.api.mvc.EssentialFilter class MyFilters extends DefaultHttpFilters( new MyFirstFilter(), new MySecondFilter() )特别注意:如果你的Global类混入了WithFilterstrait,那么现在应创建一个继承自HttpFilters的过滤器类,并且放在空包(empty package)中。Play 会从配置项play.http.filters读取过滤器类名并实例化,具体机制见 HttpFilters.scala 中的bindingsFromConfiguration,以及 Scala HTTP 过滤器指南。
Java 应用迁移指南
Java API 的迁移思路与 Scala 一致,只是部分类名与异步类型不同(Java 侧使用CompletionStage/Promise而非Future)。
启动逻辑:beforeStart/onStart
与 Scala 相同:启动时需要做的事情移到依赖注入类的构造器中。若需要急切初始化(例如在应用真正启动前执行某些代码),定义急切绑定(eager binding),参见 Java 依赖注入指南 的 Eager bindings 一节。
停止逻辑:onStop→ApplicationLifecycle.addStopHook
在需要注册停止钩子的类中注入ApplicationLifecycle,把onStop的实现移入传给addStopHook的Promise(即异步结果)中:
import play.inject.ApplicationLifecycle; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.concurrent.CompletableFuture; @Singleton public class MyConnectionPool { private final ConnectionPool pool = new ConnectionPool(); @Inject public MyConnectionPool(ApplicationLifecycle applicationLifecycle) { applicationLifecycle.addStopHook(() -> CompletableFuture.completedFuture(pool.shutdown())); } }说明:迁移文档写作时(Play 2.4 时代)Java 侧使用的还是
Promise;当前仓库中的ApplicationLifecycle已提供接收Callable<? extends CompletionStage<?>>的重载(见 ApplicationLifecycle.scala),因此现代写法直接返回CompletableFuture即可。详见 Java 依赖注入指南 的 Stopping/cleaning-up 一节。
服务器错误:onError→HttpErrorHandler.onServerError
创建实现play.http.HttpErrorHandler接口的类,把onError的实现移入onServerError:
import play.http.HttpErrorHandler; import play.mvc.Http.RequestHeader; import play.mvc.Result; import play.mvc.Results; import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletableFuture; public class MyErrorHandler implements HttpErrorHandler { @Override public CompletionStage<Result> onClientError(RequestHeader request, int statusCode, String message) { return CompletableFuture.completedFuture(Results.status(statusCode, "Client error: " + statusCode)); } @Override public CompletionStage<Result> onServerError(RequestHeader request, Throwable exception) { return CompletableFuture.completedFuture(Results.internalServerError("Server error")); } }接口定义见 HttpErrorHandler.java。完整说明参见 Java 错误处理指南。
请求预处理:onRequest→DefaultHttpRequestHandler.createAction
创建继承自play.http.DefaultHttpRequestHandler的类,把onRequest的实现移入createAction方法。DefaultHttpRequestHandler的职责是委托给 Scala 侧的JavaCompatibleHttpRequestHandler(见 DefaultHttpRequestHandler.java)。参见 Java ActionCreator 指南。
路由请求:onRouteRequest(暂无简单迁移路径)
Java API 没有简单的迁移方案。如果你确实需要onRouteRequest的能力,只能暂时保留你的Global类更长时间,或考虑在 Scala 侧通过自定义HttpRequestHandler实现,再经 Java 适配层桥接。
onHandlerNotFound/onBadRequest→HttpErrorHandler.onClientError
与 Scala 相同,实现onClientError并按状态码分派:
if (statusCode == play.mvc.Http.Status.NOT_FOUND) { // 在这里移入你原来 GlobalSettings.onHandlerNotFound 的实现 }if (statusCode == play.mvc.Http.Status.BAD_REQUEST) { // 在这里移入你原来 GlobalSettings.onBadRequest 的实现 }同样可以继承play.http.DefaultHttpErrorHandler并覆写onNotFound/onBadRequest方法,避免手工判断状态码。参见 Java 错误处理指南。
配置加载:onLoadConfig→ 配置文件或自定义ApplicationLoader
把所有配置写进配置文件,或者创建你自己的ApplicationLoader,通过GuiceApplicationBuilder.loadConfig加载,参见 Java 依赖注入指南。
过滤器:filters→HttpFilters
创建实现play.http.HttpFilters接口的类,实现filters()方法:
import play.http.HttpFilters; import play.mvc.EssentialFilter; public class MyFilters implements HttpFilters { @Override public EssentialFilter[] filters() { return new EssentialFilter[] { new MyFirstFilter(), new MySecondFilter() }; } }参见 Java HTTP 过滤器指南。
源码印证:新组件如何协同工作
以上迁移并非简单的"改名",而是把钩子职责拆解到了 Play 请求处理管线的真实组件中。从仓库源码可以印证它们的协同方式:
- 错误处理管线:DefaultHttpErrorHandler.onServerError 会先通过
HttpErrorHandlerExceptions.throwableToUsefulException把异常转换为带 ID、可展示源码位置的UsefulException(开发模式会展示调试页,生产模式只返回通用错误页),并区分onDevServerError与onProdServerError两个可覆写方法。 - 请求处理管线:DefaultHttpRequestHandler.handlerForRequest 依次执行:开发模式下的
WebCommands拦截(如 evolutions UI)→ 路由查找(routeRequest)→ HEAD 请求自动回退为 GET 路由 → 未命中返回 404 → 应用过滤器链。 - 过滤器装配:HttpFilters.scala 中的
EnabledFilters会从配置项play.filters.enabled/play.filters.disabled读取过滤器类名,并通过注入器实例化;你也可以用DefaultHttpFilters在代码中直接声明过滤器序列。 - 生命周期装配:
ApplicationLifecycle的停止钩子通过逆序(后进先出)执行保证依赖安全,且自 2.7.0 起stop()是幂等的(多次调用只执行一次),见 ApplicationLifecycle.scala。
迁移检查清单
完成迁移后,用下面的清单核对你的应用是否已完全脱离GlobalSettings:
- 所有启动逻辑移入 DI 类的构造器;需要提前执行的用
.eagerly()(Scala)或急切绑定(Java)声明 - 所有停止逻辑通过注入
ApplicationLifecycle注册addStopHook - 自定义错误页面通过实现
HttpErrorHandler提供(onServerError与onClientError) - 请求预处理与自定义路由通过
HttpRequestHandler/DefaultHttpRequestHandler提供 - 过滤器通过
HttpFilters提供,配置写入play.http.filters - 配置加载统一收敛到配置文件或自定义
ApplicationLoader - 已删除
GlobalSettings实现类及conf/application.conf中相关的全局设置项
迁移完成后,你的应用将获得类型安全、可测试、由依赖图驱动的组件生命周期,这正是 Play 从 2.4 起推荐(并在后续版本强制)的架构方向。
- 后端
- Web框架
【免费下载链接】playframework
The Community Maintained High Velocity Web Framework For Java and Scala.
相关推荐
Play Framework 2.4 迁移指南:从 2.3 升级到 Java 8、依赖注入与新配置体系
Play Framework 2.4 迁移指南:从 2.3 升级到 Java 8、依赖注入与新配置体系 本文是 Play Framework 官方 2.4 迁移
后端Web框架Play Framework 接入 CAT 实时监控:Scala Filter 与 Context 迁移实战指南
Play Framework 接入 CAT 实时监控:Scala Filter 与 Context 迁移实战指南 导读 本文基于 CAT 开源仓库中的 inte
可观测性指标监控告警APM后端链路追踪Play Framework Scala 测试指南:用 GuiceApplicationBuilder 与 GuiceInjectorBuilder 配置测试中的依赖注入
Play Framework Scala 测试指南:用 GuiceApplicationBuilder 与 GuiceInjectorBuilder 配置测试中
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考