java - 从 Play 2.4 转换到 Play 2.5 时异步操作中出现 NullPointerException

标签 java playframework nullpointerexception playframework-2.5

我正在将 Play 2.4 项目移植到 Play 2.5(.18)。我遇到了一个虚假的 NullPointerException 我找不到原因。这是堆栈跟踪:

! @76g5ina3j - Internal server error, for (POST) [/.../tokens] ->

play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[CompletionException: java.lang.NullPointerException]]
    at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:293)
    at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:220)
    at play.api.GlobalSettings$class.onError(GlobalSettings.scala:160)
    at play.api.DefaultGlobal$.onError(GlobalSettings.scala:188)
    at play.api.http.GlobalSettingsHttpErrorHandler.onServerError(HttpErrorHandler.scala:100)
Caused by: java.util.concurrent.CompletionException: java.lang.NullPointerException
    at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
    at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:604)
    at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
    at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:443)
Caused by: java.lang.NullPointerException: null
    at akka.stream.scaladsl.RunnableGraph.run(Flow.scala:350)
    at akka.stream.scaladsl.Source.runWith(Source.scala:81)
    at akka.stream.javadsl.Source.runWith(Source.scala:528)
    at akka.stream.javadsl.Source.runFold(Source.scala:539)
    at play.http.HttpEntity.consumeData(HttpEntity.java:58)

如您所见,堆栈跟踪没有对我的程序中的代码行进行任何引用,仅提到了框架。我已经尽力追踪了。 Play 操作是使用 CompletableFuturesupplyAsync 和多个 thenApply() 阶段异步实现的。辅助类最终通过调用

来组装 Result
return Controller.status(some_resultcode);

最终的 NPE 原因始于 HttpEntity.consumeData(),并在 Play 的 Scala 部分和 Akka 框架深处结束。该 RunnableGraph.run() 方法读取

def run()(implicit materializer: Materializer): Mat = materializer.materialize(this)

虽然这绝对超出了我的 Scala 知识几个数量级,但我的结论是,这里唯一的 null 东西可能是神秘的 materializer,无论它是什么。它从何而来?到底有什么好处呢?怎么可能为空?

我尝试用一​​个非常非常简单的操作来重现该问题:

public CompletionStage<Result> version() {
  return CompletableFuture
    .supplyAsync(()->"2")
    .thenApplyAsync(version->ok("Server v"+version));
}

不幸的是,这个操作执行起来没有任何问题,所以到目前为止我还没有问题的简化证明。

我现在有点迷失。谁能向我解释一下发生了什么事以及如何解决问题?

最佳答案

好的,找到这个了。经过更深入的调查后,情况有所不同。事实上, Action 本身并不是问题。 Controller 看起来像这样:

@With(OurLogger.class)
public class OurController extends Controller {

  public CompletionStage<Result> ourAction() {
    return CompletableFuture.supplyAsync(()->...);
  }
}

问题出在 OurLogger 的包装上。它的特点是:

public class OurLogger extends Action.Simple {
  private Result logResult(Result result) {
    System.err.println("Result body: "+
      JavaResultExtractor.getBody(result,1,null).utf8String());
  }
  public CompletionStage<Result> call(Context ctx) {
    return delegate.call(ctx)
      .thenApplyAsync(answer->logResult(answer));
  }
}

问题出在对 JavaResultExtractor.getBody() 的调用上。在Play 2.4中,它只获取两个参数。 Play 2.5 添加了第三个 - 这个神秘的 Materializer,据我所知,它编码了一些 Akka 管道概念。在使代码在 Play 2.5 中可编译时,一位同事刚刚添加了 null 作为第三个参数 - 而正是这个 null 正是框架深处的 Akka 代码后来偶然发现的.

解决方案位于 How to extract result content from play.mvc.Result object in play application? 的答案部分并导致 OurLogger 发生以下变化:

public class OurLogger extends Action.Simple {

  @Inject Materializer materializer;

  private Result logResult(Result result) {
    System.err.println("Result body: "+
      JavaResultExtractor.getBody(result,1,materializer).utf8String());
  }

  // call() method unchanged
}

如果 (a) Play 迁移文档提到了这个 Materializer 内容和/或 (b) 只有最轻微的这个 提示,我会更快地发现问题首先在错误堆栈跟踪中调用 getBody() 。所以这是两天大海捞针的搜索......

关于java - 从 Play 2.4 转换到 Play 2.5 时异步操作中出现 NullPointerException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48080334/

相关文章:

java - Play Framework 1.2.4 : Selected Option for #{select} template

java.lang.NullPointerException : While displaying all my user files

Java Split() 函数给出空指针异常错误

java - 无法检索 urlencoded 变音符号。 [解决方案: use UTF8]

java - 如何在 Android 应用程序的警告对话框中为用户输入编码?

java - 如何解决 Groovy 与 Apache Common Logging 的冲突?

java - 不定义类或注释的 JAXB

java - Play Framework (2.4)、sbt (0.13.17) 的热重载以及整个项目完全重新编译的原因

scala - 在scala/play框架中构建Json文件

java - 返回 null 的方法导致 java.lang.NullPointerException