From b30f4d7bb770be1d13171ffd4a394635f8dbecdd Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 25 Apr 2022 16:01:02 +0100 Subject: [PATCH] Exposes all root causes to ExceptionHandler methods Closes gh-28155 --- .../RequestMappingHandlerAdapter.java | 26 +++++++++++++------ .../annotation/ControllerAdviceTests.java | 9 ++++--- ...pingExceptionHandlingIntegrationTests.java | 4 +-- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java index 094c8a72875..7f970f670b6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.web.reactive.result.method.annotation; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -213,21 +214,30 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod); if (invocable != null) { + ArrayList exceptions = new ArrayList<>(); try { if (logger.isDebugEnabled()) { logger.debug(exchange.getLogPrefix() + "Using @ExceptionHandler " + invocable); } bindingContext.getModel().asMap().clear(); - Throwable cause = exception.getCause(); - if (cause != null) { - return invocable.invoke(exchange, bindingContext, exception, cause, handlerMethod); - } - else { - return invocable.invoke(exchange, bindingContext, exception, handlerMethod); + + // Expose causes as provided arguments as well + Throwable exToExpose = exception; + while (exToExpose != null) { + exceptions.add(exToExpose); + Throwable cause = exToExpose.getCause(); + exToExpose = (cause != exToExpose ? cause : null); } + Object[] arguments = new Object[exceptions.size() + 1]; + exceptions.toArray(arguments); // efficient arraycopy call in ArrayList + arguments[arguments.length - 1] = handlerMethod; + + return invocable.invoke(exchange, bindingContext, arguments); } catch (Throwable invocationEx) { - if (logger.isWarnEnabled()) { + // Any other than the original exception (or a cause) is unintended here, + // probably an accident (e.g. failed assertion or the like). + if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) { logger.warn(exchange.getLogPrefix() + "Failure in @ExceptionHandler " + invocable, invocationEx); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java index ec0e73ee02c..d506d2af4b6 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,9 +82,10 @@ public class ControllerAdviceTests { @Test public void resolveExceptionWithAssertionErrorAsRootCause() throws Exception { - AssertionError cause = new AssertionError("argh"); - FatalBeanException exception = new FatalBeanException("wrapped", cause); - testException(exception, cause.toString()); + AssertionError rootCause = new AssertionError("argh"); + FatalBeanException cause = new FatalBeanException("wrapped", rootCause); + Exception exception = new Exception(cause); + testException(exception, rootCause.toString()); } private void testException(Throwable exception, String expected) throws Exception { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java index 7acff0afd13..2dbf712d796 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -138,7 +138,7 @@ class RequestMappingExceptionHandlingIntegrationTests extends AbstractRequestMap @GetMapping("/thrown-exception-with-cause-to-handle") public Publisher handleAndThrowExceptionWithCauseToHandle() { - throw new RuntimeException("State", new IOException("IO")); + throw new RuntimeException("State1", new RuntimeException("State2", new IOException("IO"))); } @GetMapping(path = "/mono-error")