文档

§操作组合

本章介绍了几种定义通用操作功能的方法。

§关于操作的提醒

之前,我们说过操作是一个返回 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.Authenticatedplay.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()访问已经解析的请求主体。但是,在某些情况下,将主体解析延迟到操作组合发生之后是有意义的。例如

当然,当延迟正文解析时,请求正文在 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,但不建议这样做。

下一步:内容协商


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