我在 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
抛出异常时才会发生这种情况。如果我使用简单的try
替换throw Exception("error")
子句中的代码,则异常将被捕获到应该存在的位置。
同样,当我将get
的签名更改为非泛型 inline private fun get(): DTO?
时,问题也会消失。
对于逃避try-catch
块的异常,似乎是Kotlin工具中的一个基本错误。另一方面,只有WebClient
类才发生这一事实表明这是一个Spring问题。或者,可能只是我,以错误的方式使用工具。
我真的很困惑,不知道如何继续。关于为什么会发生这种情况的任何想法都是最受欢迎的。为了完整起见,这就是调试器中的样子:
修改
将Spring Boot升级到2.0.0.M6后,问题就消失了,它仍然存在于M5中。
所以看来这是一个Spring问题而不是Kotlin问题。另一方面,理解你所包含的库如何看起来会导致程序违反其编写的编程语言规则,这仍然是一件好事。
答案 0 :(得分:7)
我尝试使用Spring Boot版本2.0.0.M5
和2.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 at com.example.demo.Runner.run(Test.kt:18)at 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)
所以,实际上,问题是,在调用WebClientResponseException
的 return 时,尝试将返回的DTO
强制转换为val dto = get<DTO>()
类。这意味着,当您分配result = ...
时,尚未完成类型检查。因此,如果您将代码更改为,例如,请调用get<Object>()
而不是get<DTO>()
,则不会触及任何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);
}
}
}
在这里你可以注意到,在内部catch块之后,在方法返回点(不再是返回,因为它是 inlined )完成对DTO的转换:DTO var2 = (DTO)result$iv;
。这似乎是内联方法的行为,具有相应的类型参数。
答案 1 :(得分:4)
这是由于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一起使用,它将按预期工作。