从Play 2.4转换为Play 2.5

时间:2018-01-03 15:25:42

标签: 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动作是使用CompletableFuture supplyAsync和几个thenApply()阶段异步实现的。辅助类最终通过调用

组装Result
return Controller.status(some_resultcode);

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

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

虽然这肯定超过了我的斯卡拉知识几个级别,但我的结论是,这里唯一的null事物可能是神秘的materializer,无论它是什么。它从何而来?到底有什么好处呢?怎么会是null?

我尝试用更简单的操作重现问题:

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

不幸的是,这个动作没有问题,所以到目前为止我还没有对问题进行简化的说明。

我目前有点失落。任何人都可以向我解释发生了什么以及如何解决问题吗?

1 个答案:

答案 0 :(得分:1)

好的,找到了这个。经过深入调查,情况有所不同。实际上,行动本身不是问题。控制器看起来像这样:

@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代码后来发现的。{/ p>

解决方案位于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()调用提示,我会更快地发现问题首先在错误堆栈跟踪中。所以这是两天的大海捞针搜索......