§操作组合
本章介绍了几种定义通用操作功能的方法。
§关于操作的提醒
之前,我们说过操作是一个返回 play.mvc.Result
值的 Java 方法。实际上,Play 在内部将操作管理为函数。Java API 提供的操作是 play.mvc.Action
的实例。Play 为您构建一个根操作,它只调用适当的操作方法。这允许更复杂的操作组合。
§组合操作
以下是 VerboseAction
的定义
public class VerboseAction extends play.mvc.Action.Simple {
public CompletionStage<Result> call(Http.Request req) {
log.info("Calling action for {}", req);
return delegate.call(req);
}
}
您可以使用 @With
注解将操作方法提供的代码与另一个 play.mvc.Action
组合在一起
@With(VerboseAction.class)
public Result verboseIndex() {
return ok("It works!");
}
在某一点,您需要使用 delegate.call(...)
委托给包装的操作。
您还可以通过使用自定义操作注释来混合多个操作
@Security.Authenticated
@Cached(key = "index.result")
public Result authenticatedCachedIndex() {
return ok("It works!");
}
注意: 每个请求**必须**由您
play.mvc.Action
的独立实例处理。如果使用单例模式,在多个请求场景中请求将被错误地路由。例如,如果您使用 Spring 作为 Play 的 DI 容器,您需要确保您的操作 Bean 是原型范围的。注意:
play.mvc.Security.Authenticated
和play.cache.Cached
注解以及相应的预定义操作随 Play 一起提供。有关更多信息,请参阅相关的 API 文档。
§操作注解和 WebSocket 操作方法
默认情况下,在处理WebSocket
时不应用操作组合。有关如何启用操作组合(包括示例)的指南,请参阅WebSockets 文档。
§定义自定义操作注解
您也可以使用自己的注解标记操作组合,该注解本身必须使用@With
注解
@With(VerboseAnnotationAction.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface VerboseAnnotation {
boolean value() default true;
}
您的Action
定义将注解作为配置检索
public class VerboseAnnotationAction extends Action<VerboseAnnotation> {
public CompletionStage<Result> call(Http.Request req) {
if (configuration.value()) {
log.info("Calling action for {}", req);
}
return delegate.call(req);
}
}
然后,您可以将新的注解与操作方法一起使用
@VerboseAnnotation(false)
public Result verboseAnnotationIndex() {
return ok("It works!");
}
§注解控制器
您也可以将任何操作组合注解直接放在Controller
类上。在这种情况下,它将应用于该控制器定义的所有操作方法。
@Security.Authenticated
public class Admin extends Controller {
...
}
注意: 如果您希望放在
Controller
类上的操作组合注解在放在操作方法上的注解之前执行,请在application.conf
中设置play.http.actionComposition.controllerAnnotationsFirst = true
。但是,请注意,如果您在项目中使用第三方模块,它可能依赖于其注解的特定执行顺序。
§从操作传递对象到控制器
您可以通过利用请求属性将对象从操作传递到控制器。
public class Attrs {
public static final TypedKey<User> USER = TypedKey.<User>create("user");
}
public class PassArgAction extends play.mvc.Action.Simple {
public CompletionStage<Result> call(Http.Request req) {
return delegate.call(req.addAttr(Attrs.USER, User.findById(1234)));
}
}
然后,您可以在操作中像这样获取请求属性
@With(PassArgAction.class)
public static Result passArgIndex(Http.Request request) {
User user = request.attrs().get(Attrs.USER);
return ok(Json.toJson(user));
}
§调试操作组合顺序
要查看操作组合链中的操作将按什么顺序执行,请将以下内容添加到logback.xml
中
<logger name="play.mvc.Action" level="DEBUG" />
您现在将在日志中看到整个操作组合链,以及相应的注解(及其关联的方法/控制器)。
[debug] play.mvc.Action - ### Start of action order
[debug] play.mvc.Action - 1. ...
[debug] play.mvc.Action - 2. ...
[debug] play.mvc.Action - ### End of action order
§操作组合与主体解析的交互
默认情况下,主体解析在操作组合发生之前进行,这意味着您可以在每个操作的call(...)
方法中通过request.body()
访问已经解析的请求主体。但是,在某些情况下,将主体解析延迟到操作组合发生之后是有意义的。例如
- 当您希望通过 请求属性 将特定于请求的信息传递给正文解析器时。例如,用户依赖的最大文件上传大小或用户依赖的 Web 服务或对象存储的凭据,正文解析器应将上传重定向到该凭据。
- 当使用动作组合进行(粒度)授权 时,您可能甚至不想解析请求正文,并在权限检查失败时提前取消请求。
当然,当延迟正文解析时,请求正文在 call(...)
方法中尚未解析,因此 request.body()
将返回 null
。
您可以在 conf/application.conf
中全局启用延迟正文解析。
play.server.deferBodyParsing = true
请注意,与所有 play.server.*
配置键一样,此配置在 DEV 模式下运行时不会被 Play 拾取,而只会在 PROD 模式下拾取。要在 DEV 模式下设置此配置,您必须在 build.sbt
中设置它。
PlayKeys.devSettings += "play.server.deferBodyParsing" -> "true"
除了全局启用延迟正文解析之外,您还可以使用 路由修饰符 deferBodyParsing
仅为特定路由启用它。
+ deferBodyParsing
POST / controllers.HomeController.uploadFileToS3(request: Request)
反之亦然。如果您全局启用延迟正文解析,则可以使用 路由修饰符 dontDeferBodyParsing
为特定路由禁用它。
+ dontDeferBodyParsing
POST / controllers.HomeController.processUpload(request: Request)
§使用依赖注入
您可以使用 运行时依赖注入 或 编译时依赖注入 与动作组合一起使用。
§运行时依赖注入
例如,如果您想定义自己的结果缓存解决方案,首先定义注释
@With(MyOwnCachedAction.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithCache {
String key();
}
然后,您可以使用注入的依赖项定义您的操作
public class MyOwnCachedAction extends Action<WithCache> {
private final AsyncCacheApi cacheApi;
@Inject
public MyOwnCachedAction(AsyncCacheApi cacheApi) {
this.cacheApi = cacheApi;
}
@Override
public CompletionStage<Result> call(Http.Request req) {
return cacheApi.getOrElseUpdate(configuration.key(), () -> delegate.call(req));
}
}
注意:如上所述,每个请求必须由
play.mvc.Action
的不同实例提供服务,并且您不能将您的操作注释为@Singleton
。
§编译时依赖注入
当使用 编译时依赖注入 时,您需要手动将您的 Action
供应商添加到 JavaHandlerComponents
。您可以通过在 BuiltInComponents
中覆盖方法 javaHandlerComponents
来实现。
public class MyComponents extends BuiltInComponentsFromContext
implements NoHttpFiltersComponents, CaffeineCacheComponents {
public MyComponents(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
return Router.empty();
}
@Override
public MappedJavaHandlerComponents javaHandlerComponents() {
return super.javaHandlerComponents()
// Add action that does not depends on any other component
.addAction(VerboseAction.class, VerboseAction::new)
// Add action that depends on the cache api
.addAction(MyOwnCachedAction.class, () -> new MyOwnCachedAction(defaultCacheApi()));
}
}
注意:如上所述,每个请求**必须**由您
play.mvc.Action
的独立实例处理,这就是您添加java.util.function.Supplier<Action>
而不是实例本身的原因。当然,您可以使用每次都返回相同实例的Supplier
,但不建议这样做。
下一步:内容协商
发现此文档中的错误?此页面的源代码可以在此处找到。在阅读文档指南后,请随时贡献拉取请求。有疑问或建议要分享?前往我们的社区论坛与社区进行交流。