From 3d211b71b14da6e560c25b14aad5c71ebbf3fa45 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 | 19 ++++--- .../support/InvocableHandlerMethodTests.java | 50 +++++++++---------- .../method/InvocableHandlerMethodTests.java | 23 ++++----- 3 files changed, 44 insertions(+), 48 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 7114c2a300b..f0f6ed38cfc 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 @@ -409,19 +409,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, @Nullable 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)); } @@ -432,12 +431,12 @@ public class HandlerMethod extends AnnotatedMethod { .mapToObj(i -> { Object arg = args[i]; return (arg != null ? - "[" + i + "] [type=" +arg.getClass().getName() + "] [value=" + arg + "]" : + "[" + i + "] [type=" + arg.getClass().getName() + "] [value=" + arg + "]" : "[" + i + "] [null]"); }) - .collect(Collectors.joining(",\n", " ", " ")); + .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 5716b1f8cf9..758705f8cd2 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 @@ -51,7 +51,7 @@ class InvocableHandlerMethodTests { @BeforeEach - void setUp() { + void setup() { this.request = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()); } @@ -84,9 +84,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 @@ -118,54 +118,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) { @@ -180,7 +179,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 9e4c1f24e18..db2e20215ef 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