在这种情况下,神秘地转义try-catch块的例外最可能的原因是什么?

我在这样的Kotlin项目中使用Spring WebClient

data class DTO(val name: String) @Component class Runner: ApplicationRunner { override fun run(args: ApplicationArguments?) { try { val dto = get() } catch (e: Exception) { println("ERROR, all exceptions should have been caught in 'get' ") } } } inline private fun get(): TResult? { var result: TResult? = null try { result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting") .get() .retrieve() .bodyToMono() .block() } catch (e: Exception) { println("WORKS AS EXPECTED!!") } return result } 

客户端会抛出一个exception,因为API将返回一个404.然而,这个exception不会被捕获到它应该在的地方,即在get函数的主体中,而是被传播到外部的exception处理程序。

有意思的是,只有当WebClient抛出exception时才会发生这种情况。 如果我用一个简单的throw Exception("error")替换try子句中的代码,那么该exception就会被捕获到。

同样的,当我改变签名的时候get一个非generics的 inline private fun get(): DTO? 问题也消失了。

为了逃避try-catch块的例外看起来像是Kotlin工具中的一个基本错误。 另一方面,这只发生在WebClient类的事实表明这是一个Spring问题。 或者,也可能只是我,以错误的方式使用工具。

我真的很困惑,不知道该怎么做。 任何想法,为什么这可能发生是最受欢迎的。 为了完整起见,这就是它在调试器中的样子:

在这里输入图像说明

编辑

升级Spring Boot到2.0.0.M6后,问题就消失了,它仍然存在于M5中。

所以这似乎是一个春季问题,而不是一个科特林问题。 另一方面,理解一个包含的库如何可能会导致程序违反编写的编程语言的规则,

我尝试了Spring Boot 2.0.0.M52.0.0.M6的代码,看起来下面的代码块的行为在这两个版本中是不同的:

 result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting") .get() .retrieve() .bodyToMono() .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.Runner.run(Test.kt:18)上的com.example.demo.DTO。 org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:760)org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:770)上的springframework.boot.SpringApplication.callRunner(SpringApplication.java:780) .springframework.boot.SpringApplication.run(SpringApplication.java:328)在org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)位于org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)at com.example.demo.DemoApplicationKt.main(DemoApplication.kt:10)

所以,实际上,问题在于,在返回调用val dto = get()试图将返回的WebClientResponseException转换为DTO类。 这意味着,当您分配result = ... ,还没有完成types检查。 因此,如果您将代码更改为例如调用get()而不是get() ,则不会触及任何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() {})"); 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; 。 这似乎是具有已指定types参数的内联方法的行为。

这是由于SPOT-16025 (参见相关的提交 ),因为Kotlin扩展在内部使用了ParameterizedTypeReference变体(在Spring Framework 5.0.1中已经修复),在Spring Boot 2.0.0.M6中是传递的。

注意,如果你在Spring Boot 2.0.0.M5中使用bodyToMono(TResult::class.java) ,它将按预期工作。