从 Play 2.4 转换为 Play 2.5 时异步操作中的 NullPointerException
NullPointerException in async action when converting from Play 2.4 to Play 2.5
我正在将 Play 2.4 项目移植到 Play 2.5(.18)。我 运行 变成了一个虚假的 NullPointerException
我找不到原因。这是堆栈跟踪:
! @76g5ina3j - Internal server error, for (POST) [/.../tokens] ->
play.api.http.HttpErrorHandlerExceptions$$anon: 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()
阶段异步实现的。助手 class 最后通过调用
组装了 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));
}
不幸的是,这个动作一直没有问题,所以到目前为止我还没有对问题的简化证明。
我现在有点迷茫。谁能给我解释一下这是怎么回事以及如何解决这个问题?
好的,找到这个了。经过更深入的调查,情况有所不同。其实,动作本身并不是问题。控制器看起来像这样:
@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
东西 and/or (b) 只有最轻微的提示 getBody()
首先调用错误堆栈跟踪。就这样大海捞针找了两天...
我正在将 Play 2.4 项目移植到 Play 2.5(.18)。我 运行 变成了一个虚假的 NullPointerException
我找不到原因。这是堆栈跟踪:
! @76g5ina3j - Internal server error, for (POST) [/.../tokens] ->
play.api.http.HttpErrorHandlerExceptions$$anon: 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()
阶段异步实现的。助手 class 最后通过调用
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));
}
不幸的是,这个动作一直没有问题,所以到目前为止我还没有对问题的简化证明。
我现在有点迷茫。谁能给我解释一下这是怎么回事以及如何解决这个问题?
好的,找到这个了。经过更深入的调查,情况有所不同。其实,动作本身并不是问题。控制器看起来像这样:
@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
东西 and/or (b) 只有最轻微的提示 getBody()
首先调用错误堆栈跟踪。就这样大海捞针找了两天...