From 63cd96fa5ff1fa42ac5091ea450306ffd20e3726 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 8 Apr 2026 15:28:29 +0200 Subject: [PATCH] Revise target bean exception with consistent message formatting --- .../web/method/HandlerMethod.java | 26 +++++----- .../support/InvocableHandlerMethodTests.java | 50 +++++++++---------- .../method/InvocableHandlerMethodTests.java | 23 ++++----- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index 15af91993e3..16f3fe6fb4b 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -390,19 +390,18 @@ public class HandlerMethod extends AnnotatedMethod { // Support methods for use in subclass variants /** - * Assert that the target bean class is an instance of the class where the given - * method is declared. In some cases the actual controller instance at request- - * processing time may be a JDK dynamic proxy (lazy initialization, prototype - * beans, and others). {@code @Controller}'s that require proxying should prefer - * class-based proxy mechanisms. + * Assert that the target bean class is an instance of the class where the given method + * is declared. In some cases the actual controller instance at request-processing time + * may be a JDK dynamic proxy (lazy initialization, prototype beans, and others). + * {@code @Controller}'s that require proxying should prefer class-based proxy mechanisms. */ protected void assertTargetBean(Method method, Object targetBean, Object[] args) { Class methodDeclaringClass = method.getDeclaringClass(); Class targetBeanClass = targetBean.getClass(); if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { String text = "The mapped handler method class '" + methodDeclaringClass.getName() + - "' is not an instance of the actual controller bean class '" + - targetBeanClass.getName() + "'. If the controller requires proxying " + + "' is not an instance of the actual target bean class '" + + targetBeanClass.getName() + "'. If the target bean requires proxying " + "(for example, due to @Transactional), please use class-based proxying."; throw new IllegalStateException(formatInvokeError(text, args)); } @@ -410,12 +409,15 @@ public class HandlerMethod extends AnnotatedMethod { protected String formatInvokeError(String text, Object[] args) { String formattedArgs = IntStream.range(0, args.length) - .mapToObj(i -> (args[i] != null ? - "[" + i + "] [type=" + args[i].getClass().getName() + "] [value=" + args[i] + "]" : - "[" + i + "] [null]")) - .collect(Collectors.joining(",\n", " ", " ")); + .mapToObj(i -> { + Object arg = args[i]; + return (arg != null ? + "[" + i + "] [type=" + arg.getClass().getName() + "] [value=" + arg + "]" : + "[" + i + "] [null]"); + }) + .collect(Collectors.joining(",\n")); return text + "\n" + - "Controller [" + getBeanType().getName() + "]\n" + + "Handler [" + getBeanType().getName() + "]\n" + "Method [" + getBridgedMethod().toGenericString() + "] " + "with argument values:\n" + formattedArgs; } diff --git a/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java b/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java index cc47ae56ac1..db11a0f174b 100644 --- a/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java @@ -49,7 +49,7 @@ class InvocableHandlerMethodTests { @BeforeEach - void setUp() { + void setup() { this.request = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()); } @@ -82,9 +82,9 @@ class InvocableHandlerMethodTests { @Test void cannotResolveArg() { - assertThatIllegalStateException().isThrownBy(() -> - getInvocable(Integer.class, String.class).invokeForRequest(request, null)) - .withMessageContaining("Could not resolve parameter [0]"); + assertThatIllegalStateException() + .isThrownBy(() -> getInvocable(Integer.class, String.class).invokeForRequest(request, null)) + .withMessageContaining("Could not resolve parameter [0]"); } @Test @@ -108,54 +108,53 @@ class InvocableHandlerMethodTests { @Test void exceptionInResolvingArg() { this.composite.addResolver(new ExceptionRaisingArgumentResolver()); - assertThatIllegalArgumentException().isThrownBy(() -> - getInvocable(Integer.class, String.class).invokeForRequest(request, null)); + assertThatIllegalArgumentException() + .isThrownBy(() -> getInvocable(Integer.class, String.class).invokeForRequest(request, null)); } @Test void illegalArgumentException() { this.composite.addResolver(new StubArgumentResolver(Integer.class, "__not_an_int__")); this.composite.addResolver(new StubArgumentResolver("value")); - assertThatIllegalStateException().isThrownBy(() -> - getInvocable(Integer.class, String.class).invokeForRequest(request, null)) - .withCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("Controller [") - .withMessageContaining("Method [") - .withMessageContaining("with argument values:") - .withMessageContaining("[0] [type=java.lang.String] [value=__not_an_int__]") - .withMessageContaining("[1] [type=java.lang.String] [value=value"); + assertThatIllegalStateException() + .isThrownBy(() -> getInvocable(Integer.class, String.class).invokeForRequest(request, null)) + .withCauseInstanceOf(IllegalArgumentException.class) + .withMessageContaining("[" + Handler.class.getName() + "]") + .withMessageContaining(Handler.class.getName() + ".handle") + .withMessageContaining("[0] [type=java.lang.String] [value=__not_an_int__]") + .withMessageContaining("[1] [type=java.lang.String] [value=value"); } @Test void invocationTargetException() { RuntimeException runtimeException = new RuntimeException("error"); assertThatRuntimeException() - .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, runtimeException)) - .isSameAs(runtimeException); + .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, runtimeException)) + .isSameAs(runtimeException); Error error = new Error("error"); assertThatExceptionOfType(Error.class) - .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, error)) - .isSameAs(error); + .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, error)) + .isSameAs(error); Exception exception = new Exception("error"); assertThatException() - .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, exception)) - .isSameAs(exception); + .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, exception)) + .isSameAs(exception); Throwable throwable = new Throwable("error"); assertThatIllegalStateException() - .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, throwable)) - .withCause(throwable) - .withMessageContaining("Invocation failure"); + .isThrownBy(() -> getInvocable(Throwable.class).invokeForRequest(this.request, null, throwable)) + .withCause(throwable) + .withMessageContaining("Invocation failure"); } @Test // SPR-13917 void invocationErrorMessage() { this.composite.addResolver(new StubArgumentResolver(double.class)); assertThatIllegalStateException() - .isThrownBy(() -> getInvocable(double.class).invokeForRequest(this.request, null)) - .withMessageContaining("Illegal argument"); + .isThrownBy(() -> getInvocable(double.class).invokeForRequest(this.request, null)) + .withMessageContaining("Illegal argument"); } private InvocableHandlerMethod getInvocable(Class... argTypes) { @@ -170,7 +169,6 @@ class InvocableHandlerMethodTests { } - @SuppressWarnings("unused") private static class Handler { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java index 7b20c1dcc95..00d3f387db0 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java @@ -117,8 +117,8 @@ class InvocableHandlerMethodTests { Mono mono = invoke(new TestController(), method); assertThatIllegalStateException() - .isThrownBy(mono::block) - .withMessage("Could not resolve parameter [0] in %s: No suitable resolver", method.toGenericString()); + .isThrownBy(mono::block) + .withMessage("Could not resolve parameter [0] in %s: No suitable resolver", method.toGenericString()); } @Test @@ -145,8 +145,8 @@ class InvocableHandlerMethodTests { Mono mono = invoke(new TestController(), method); assertThatExceptionOfType(UnsupportedMediaTypeStatusException.class) - .isThrownBy(mono::block) - .withMessage("415 UNSUPPORTED_MEDIA_TYPE \"boo\""); + .isThrownBy(mono::block) + .withMessage("415 UNSUPPORTED_MEDIA_TYPE \"boo\""); } @Test @@ -156,12 +156,11 @@ class InvocableHandlerMethodTests { Mono mono = invoke(new TestController(), method); assertThatIllegalStateException() - .isThrownBy(mono::block) - .withCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("Controller [") - .withMessageContaining("Method [") - .withMessageContaining("with argument values:") - .withMessageContaining("[0] [type=java.lang.Integer] [value=1]"); + .isThrownBy(mono::block) + .withCauseInstanceOf(IllegalArgumentException.class) + .withMessageContaining("[" + TestController.class.getName() + "]") + .withMessageContaining(TestController.class.getName() + ".singleArg") + .withMessageContaining("[0] [type=java.lang.Integer] [value=1]"); } @Test @@ -170,8 +169,8 @@ class InvocableHandlerMethodTests { Mono mono = invoke(new TestController(), method); assertThatIllegalStateException() - .isThrownBy(mono::block) - .withMessage("boo"); + .isThrownBy(mono::block) + .withMessage("boo"); } @Test