【问题标题】:What is the most likely cause of exceptions mysteriously escaping a try-catch block in this case?在这种情况下,异常神秘地逃脱了 try-catch 块的最可能原因是什么?
【发布时间】:2017-11-20 11:20:53
【问题描述】:

我在 Kotlin 项目中使用 Spring WebClient,如下所示:

data class DTO(val name: String)

@Component
class Runner: ApplicationRunner
{
    override fun run(args: ApplicationArguments?)
    {
        try
        {
            val dto = get<DTO>()
        }
        catch (e: Exception)
        {
            println("ERROR, all exceptions should have been caught in 'get' ")
        }
    }
}

inline private fun<reified TResult: Any> get(): TResult?
{
    var result: TResult? = null

    try
    {
    result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
        .get()
        .retrieve()
        .bodyToMono<TResult>()
        .block()
    }
    catch (e: Exception)
    {
        println("WORKS AS EXPECTED!!")
    }

    return result
}

客户端将抛出异常,因为 API 将返回 404。但是异常没有被捕获到它应该在的位置,即在 get 函数的主体中,但它被传播到外部异常处理程序。

有趣的是,只有在WebClient 抛出异常时才会发生这种情况。如果我用简单的throw Exception("error") 替换try 子句中的代码,异常就会被捕获到它应该在的地方。

同样,当我将get 的签名更改为非通用 inline private fun get(): DTO? 时,问题也消失了。

对于逃避try-catch 块的异常似乎是Kotlin 工具中的一个基本错误。另一方面,这种情况只发生在WebClient 类中,这表明这是一个Spring 问题。或者,可能只是我,以错误的方式使用这些工具。

我真的很困惑,不知道如何继续。任何关于为什么会发生这种情况的想法都非常受欢迎。只是为了完整起见,这就是它在调试器中的样子:

编辑

将 Spring Boot 升级到 2.0.0.M6 后问题消失,但在 M5 中仍然存在。

所以这似乎是 Spring 问题而不是 Kotlin 问题。另一方面,了解您包含的库如何看似会导致程序违反其编写的编程语言的法律,这仍然是一件好事。

【问题讨论】:

  • 如果您在 try-catch 中执行 return 会发生什么?
  • @Michael 问题依旧:ERROR, all exceptions should have been caught in 'get'

标签: spring generics exception kotlin spring-webflux


【解决方案1】:

我尝试了 Spring Boot 版本 2.0.0.M52.0.0.M6 的代码,似乎以下块的行为在这两个版本之间有所不同:

result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
    .get()
    .retrieve()
    .bodyToMono<TResult>()
    .block()

在链条的某处,在 Spring Boot 2.0.0.M5 上,WebClientResponseException返回,在 Spring Boot 2.0.0.M6 上被抛出

如果您在外部捕获中添加e.printStackTrace(),您会注意到堆栈跟踪是:

java.lang.ClassCastException: org.springframework.web.reactive.function.client.WebClientResponseException 无法转换为 com.example.demo.DTO 在 com.example.demo.Runner.run(Test.kt:18) 在 org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780) 在 org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:770) 在 org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:760) 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:328) 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:1245) 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:1233) 在 com.example.demo.DemoApplicationKt.main(DemoApplication.kt:10)

所以,实际上,问题是,在调用 val dto = get&lt;DTO&gt;()return 时,尝试将返回的 WebClientResponseException 强制转换为 DTO 类。这意味着,当您分配 result = ... 时,还没有完成类型检查。因此,如果您将代码更改为,例如,调用 get&lt;Object&gt;() 而不是 get&lt;DTO&gt;(),它不会遇到任何 catch 块。

如果在 IntelliJ Idea 中将其转换为字节码,然后将其反编译为 Java,则可以看到这个块:

public class Runner implements ApplicationRunner {
   public void run(@Nullable ApplicationArguments args) {
      try {
         Object result$iv = null;

         try {
            ResponseSpec $receiver$iv$iv = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting").get().retrieve();
            Mono var10000 = $receiver$iv$iv.bodyToMono((ParameterizedTypeReference)(new Runner$run$$inlined$get$1()));
            Intrinsics.checkExpressionValueIsNotNull(var10000, "bodyToMono(object : Para…zedTypeReference<T>() {})");
            result$iv = var10000.block();
         } catch (Exception var7) {
            String var5 = "WORKS AS EXPECTED!!";
            System.out.println(var5);
         }

         DTO var2 = (DTO)result$iv;
      } catch (Exception var8) {
         String var3 = "ERROR, all exceptions should have been caught in 'get' ";
         System.out.println(var3);
      }

   }
}

在这里您可以注意到,转换为 DTO 是在方法返回点完成的(这不再是返回,因为它是 内联),在内部 catch 块之后:DTO var2 = (DTO)result$iv;。这似乎是具有具体类型参数的内联方法的行为。

【讨论】:

    【解决方案2】:

    这是由于 SPR-16025(请参阅 related commit),因为 Kotlin 扩展在内部使用 ParameterizedTypeReference 变体,该变体已在 Spring Framework 5.0.1 中修复,并在 Spring Boot 2.0.0.M6 中传递.

    请注意,如果您将 bodyToMono(TResult::class.java) 与 Spring Boot 2.0.0.M5 一起使用,它将按预期工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-15
      • 2013-05-20
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      相关资源
      最近更新 更多