代码之家  ›  专栏  ›  技术社区  ›  Dirk Hillbrecht

从播放2.4转换到播放2.5时,异步操作中出现NullPointerException

  •  1
  • Dirk Hillbrecht  · 技术社区  · 8 年前

    我正在将一个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)
    

    如您所见,堆栈跟踪没有对程序中的代码行进行任何引用,只提到了框架。我已经尽了最大努力。播放操作是使用异步实现的 CompletableFuture 具有 supplyAsync 还有几个 thenApply() 阶段。助手类最终将 Result 通过调用

    return Controller.status(some_resultcode);
    

    最终的NPE原因始于 HttpEntity.consumeData() 并在游戏的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));
    }
    

    不幸的是,这个操作一直没有出现问题,所以到目前为止,我还没有关于这个问题的简化测试。

    我现在有点迷路了。有人能向我解释一下发生了什么事以及如何解决这个问题吗?

    1 回复  |  直到 8 年前
        1
  •  1
  •   Dirk Hillbrecht    8 年前

    好的,找到这个了。经过深入调查,情况有所不同。实际上,行动本身并不是问题所在。控制器如下所示:

    @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中,它只获得两个参数。游戏2.5增加了第三个-这个神秘的 Materializer 据我所知,它编纂了一些Akka管道概念。在Play 2.5中使代码可编译时,一位同事刚刚添加 null 作为第三个参数 无效的 框架深处的Akka代码后来偶然发现了这一点。

    解决方案在的答案部分 How to extract result content from play.mvc.Result object in play application? 并导致以下变化: 我们的记录器 :

    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 migration文档提到这一点,我会更快地发现问题 物化器 材料和/或(b)这只是一点小小的暗示 getBody() 首先调用错误堆栈跟踪。所以这是两天大海捞针的搜寻。。。