Skip to content

Commit 29b40b9

Browse files
committed
Revised InvocableHandlerMethod exception handling
Issue: SPR-11281 (cherry picked from commit 1a1c72c)
1 parent 85e336e commit 29b40b9

File tree

2 files changed

+74
-53
lines changed

2 files changed

+74
-53
lines changed

spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,15 +47,15 @@
4747
*/
4848
public class InvocableHandlerMethod extends HandlerMethod {
4949

50-
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
51-
5250
private WebDataBinderFactory dataBinderFactory;
5351

52+
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
53+
5454
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
5555

5656

5757
/**
58-
* Creates an instance from the given handler and method.
58+
* Create an instance from the given handler and method.
5959
*/
6060
public InvocableHandlerMethod(Object bean, Method method) {
6161
super(bean, method);
@@ -69,19 +69,19 @@ public InvocableHandlerMethod(HandlerMethod handlerMethod) {
6969
}
7070

7171
/**
72-
* Constructs a new handler method with the given bean instance, method name and parameters.
72+
* Construct a new handler method with the given bean instance, method name and parameters.
7373
* @param bean the object bean
7474
* @param methodName the method name
7575
* @param parameterTypes the method parameter types
7676
* @throws NoSuchMethodException when the method cannot be found
7777
*/
78-
public InvocableHandlerMethod(
79-
Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
78+
public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
8079
super(bean, methodName, parameterTypes);
8180
}
8281

82+
8383
/**
84-
* Sets the {@link WebDataBinderFactory} to be passed to argument resolvers allowing them to create
84+
* Set the {@link WebDataBinderFactory} to be passed to argument resolvers allowing them to create
8585
* a {@link WebDataBinder} for data binding and type conversion purposes.
8686
* @param dataBinderFactory the data binder factory.
8787
*/
@@ -97,78 +97,74 @@ public void setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverCompo
9797
}
9898

9999
/**
100-
* Set the ParameterNameDiscoverer for resolving parameter names when needed (e.g. default request attribute name).
101-
* <p>Default is an {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer} instance.
100+
* Set the ParameterNameDiscoverer for resolving parameter names when needed
101+
* (e.g. default request attribute name).
102+
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer} instance.
102103
*/
103104
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
104105
this.parameterNameDiscoverer = parameterNameDiscoverer;
105106
}
106107

108+
107109
/**
108110
* Invoke the method after resolving its argument values in the context of the given request. <p>Argument
109111
* values are commonly resolved through {@link HandlerMethodArgumentResolver}s. The {@code provideArgs}
110112
* parameter however may supply argument values to be used directly, i.e. without argument resolution.
111113
* Examples of provided argument values include a {@link WebDataBinder}, a {@link SessionStatus}, or
112114
* a thrown exception instance. Provided argument values are checked before argument resolvers.
113-
*
114115
* @param request the current request
115116
* @param mavContainer the ModelAndViewContainer for this request
116117
* @param providedArgs "given" arguments matched by type, not resolved
117118
* @return the raw value returned by the invoked method
118119
* @exception Exception raised if no suitable argument resolver can be found, or the method raised an exception
119120
*/
120-
public final Object invokeForRequest(NativeWebRequest request,
121-
ModelAndViewContainer mavContainer,
122-
Object... providedArgs) throws Exception {
123-
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
121+
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
122+
Object... providedArgs) throws Exception {
124123

124+
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
125125
if (logger.isTraceEnabled()) {
126-
StringBuilder builder = new StringBuilder("Invoking [");
127-
builder.append(this.getMethod().getName()).append("] method with arguments ");
128-
builder.append(Arrays.asList(args));
129-
logger.trace(builder.toString());
126+
StringBuilder sb = new StringBuilder("Invoking [");
127+
sb.append(getBeanType().getSimpleName()).append(".");
128+
sb.append(getMethod().getName()).append("] method with arguments ");
129+
sb.append(Arrays.asList(args));
130+
logger.trace(sb.toString());
130131
}
131-
132132
Object returnValue = invoke(args);
133-
134133
if (logger.isTraceEnabled()) {
135-
logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue + "]");
134+
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
136135
}
137-
138136
return returnValue;
139137
}
140138

141139
/**
142140
* Get the method argument values for the current request.
143141
*/
144-
private Object[] getMethodArgumentValues(
145-
NativeWebRequest request, ModelAndViewContainer mavContainer,
142+
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
146143
Object... providedArgs) throws Exception {
147144

148145
MethodParameter[] parameters = getMethodParameters();
149146
Object[] args = new Object[parameters.length];
150147
for (int i = 0; i < parameters.length; i++) {
151148
MethodParameter parameter = parameters[i];
152-
parameter.initParameterNameDiscovery(parameterNameDiscoverer);
149+
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
153150
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
154-
155151
args[i] = resolveProvidedArgument(parameter, providedArgs);
156152
if (args[i] != null) {
157153
continue;
158154
}
159-
160-
if (argumentResolvers.supportsParameter(parameter)) {
155+
if (this.argumentResolvers.supportsParameter(parameter)) {
161156
try {
162-
args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
157+
args[i] = this.argumentResolvers.resolveArgument(
158+
parameter, mavContainer, request, this.dataBinderFactory);
163159
continue;
164-
} catch (Exception ex) {
160+
}
161+
catch (Exception ex) {
165162
if (logger.isTraceEnabled()) {
166163
logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
167164
}
168165
throw ex;
169166
}
170167
}
171-
172168
if (args[i] == null) {
173169
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
174170
throw new IllegalStateException(msg);
@@ -214,17 +210,17 @@ private Object resolveProvidedArgument(MethodParameter parameter, Object... prov
214210
* Invoke the handler method with the given argument values.
215211
*/
216212
private Object invoke(Object... args) throws Exception {
217-
ReflectionUtils.makeAccessible(this.getBridgedMethod());
213+
ReflectionUtils.makeAccessible(getBridgedMethod());
218214
try {
219215
return getBridgedMethod().invoke(getBean(), args);
220216
}
221-
catch (IllegalArgumentException e) {
222-
String msg = getInvocationErrorMessage(e.getMessage(), args);
223-
throw new IllegalArgumentException(msg, e);
217+
catch (IllegalArgumentException ex) {
218+
assertTargetBean(getBridgedMethod(), getBean(), args);
219+
throw new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex);
224220
}
225-
catch (InvocationTargetException e) {
221+
catch (InvocationTargetException ex) {
226222
// Unwrap for HandlerExceptionResolvers ...
227-
Throwable targetException = e.getTargetException();
223+
Throwable targetException = ex.getTargetException();
228224
if (targetException instanceof RuntimeException) {
229225
throw (RuntimeException) targetException;
230226
}
@@ -241,6 +237,25 @@ else if (targetException instanceof Exception) {
241237
}
242238
}
243239

240+
/**
241+
* Assert that the target bean class is an instance of the class where the given
242+
* method is declared. In some cases the actual controller instance at request-
243+
* processing time may be a JDK dynamic proxy (lazy initialization, prototype
244+
* beans, and others). {@code @Controller}'s that require proxying should prefer
245+
* class-based proxy mechanisms.
246+
*/
247+
private void assertTargetBean(Method method, Object targetBean, Object[] args) {
248+
Class<?> methodDeclaringClass = method.getDeclaringClass();
249+
Class<?> targetBeanClass = targetBean.getClass();
250+
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
251+
String msg = "The mapped controller method class '" + methodDeclaringClass.getName() +
252+
"' is not an instance of the actual controller bean instance '" +
253+
targetBeanClass.getName() + "'. If the controller requires proxying " +
254+
"(e.g. due to @Transactional), please use class-based proxying.";
255+
throw new IllegalStateException(getInvocationErrorMessage(msg, args));
256+
}
257+
}
258+
244259
private String getInvocationErrorMessage(String message, Object[] resolvedArgs) {
245260
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message));
246261
sb.append("Resolved arguments: \n");

spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,13 +45,15 @@ public class InvocableHandlerMethodTests {
4545

4646
private NativeWebRequest webRequest;
4747

48+
4849
@Before
4950
public void setUp() throws Exception {
5051
Method method = Handler.class.getDeclaredMethod("handle", Integer.class, String.class);
5152
this.handlerMethod = new InvocableHandlerMethod(new Handler(), method);
5253
this.webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
5354
}
5455

56+
5557
@Test
5658
public void resolveArg() throws Exception {
5759
StubArgumentResolver intResolver = new StubArgumentResolver(Integer.class, 99);
@@ -63,11 +65,9 @@ public void resolveArg() throws Exception {
6365
handlerMethod.setHandlerMethodArgumentResolvers(composite);
6466

6567
Object returnValue = handlerMethod.invokeForRequest(webRequest, null);
66-
6768
assertEquals(1, intResolver.getResolvedParameters().size());
6869
assertEquals(1, stringResolver.getResolvedParameters().size());
6970
assertEquals("99-value", returnValue);
70-
7171
assertEquals("intArg", intResolver.getResolvedParameters().get(0).getParameterName());
7272
assertEquals("stringArg", stringResolver.getResolvedParameters().get(0).getParameterName());
7373
}
@@ -83,7 +83,6 @@ public void resolveNullArg() throws Exception {
8383
handlerMethod.setHandlerMethodArgumentResolvers(composite);
8484

8585
Object returnValue = handlerMethod.invokeForRequest(webRequest, null);
86-
8786
assertEquals(1, intResolver.getResolvedParameters().size());
8887
assertEquals(1, stringResolver.getResolvedParameters().size());
8988
assertEquals("null-null", returnValue);
@@ -94,7 +93,8 @@ public void cannotResolveArg() throws Exception {
9493
try {
9594
handlerMethod.invokeForRequest(webRequest, null);
9695
fail("Expected exception");
97-
} catch (IllegalStateException ex) {
96+
}
97+
catch (IllegalStateException ex) {
9898
assertTrue(ex.getMessage().contains("No suitable resolver for argument [0] [type=java.lang.Integer]"));
9999
}
100100
}
@@ -118,7 +118,6 @@ public void resolveProvidedArgFirst() throws Exception {
118118
handlerMethod.setHandlerMethodArgumentResolvers(composite);
119119

120120
Object returnValue = handlerMethod.invokeForRequest(webRequest, null, 2, "value2");
121-
122121
assertEquals("2-value2", returnValue);
123122
}
124123

@@ -131,9 +130,9 @@ public void exceptionInResolvingArg() throws Exception {
131130
try {
132131
handlerMethod.invokeForRequest(webRequest, null);
133132
fail("Expected exception");
134-
} catch (HttpMessageNotReadableException ex) {
135-
// Expected..
136-
// Allow HandlerMethodArgumentResolver exceptions to propagate..
133+
}
134+
catch (HttpMessageNotReadableException ex) {
135+
// expected - allow HandlerMethodArgumentResolver exceptions to propagate
137136
}
138137
}
139138

@@ -150,7 +149,8 @@ public void illegalArgumentException() throws Exception {
150149
try {
151150
handlerMethod.invokeForRequest(webRequest, null);
152151
fail("Expected exception");
153-
} catch (IllegalArgumentException ex) {
152+
}
153+
catch (IllegalStateException ex) {
154154
assertNotNull("Exception not wrapped", ex.getCause());
155155
assertTrue(ex.getCause() instanceof IllegalArgumentException);
156156
assertTrue(ex.getMessage().contains("Controller ["));
@@ -166,28 +166,32 @@ public void invocationTargetException() throws Exception {
166166
Throwable expected = new RuntimeException("error");
167167
try {
168168
invokeExceptionRaisingHandler(expected);
169-
} catch (RuntimeException actual) {
169+
}
170+
catch (RuntimeException actual) {
170171
assertSame(expected, actual);
171172
}
172173

173174
expected = new Error("error");
174175
try {
175176
invokeExceptionRaisingHandler(expected);
176-
} catch (Error actual) {
177+
}
178+
catch (Error actual) {
177179
assertSame(expected, actual);
178180
}
179181

180182
expected = new Exception("error");
181183
try {
182184
invokeExceptionRaisingHandler(expected);
183-
} catch (Exception actual) {
185+
}
186+
catch (Exception actual) {
184187
assertSame(expected, actual);
185188
}
186189

187190
expected = new Throwable("error");
188191
try {
189192
invokeExceptionRaisingHandler(expected);
190-
} catch (IllegalStateException actual) {
193+
}
194+
catch (IllegalStateException actual) {
191195
assertNotNull(actual.getCause());
192196
assertSame(expected, actual.getCause());
193197
assertTrue(actual.getMessage().contains("Failed to invoke controller method"));
@@ -201,6 +205,7 @@ private void invokeExceptionRaisingHandler(Throwable expected) throws Exception
201205
fail("Expected exception");
202206
}
203207

208+
204209
@SuppressWarnings("unused")
205210
private static class Handler {
206211

@@ -209,6 +214,7 @@ public String handle(Integer intArg, String stringArg) {
209214
}
210215
}
211216

217+
212218
@SuppressWarnings("unused")
213219
private static class ExceptionRaisingHandler {
214220

@@ -221,9 +227,9 @@ public ExceptionRaisingHandler(Throwable t) {
221227
public void raiseException() throws Throwable {
222228
throw t;
223229
}
224-
225230
}
226231

232+
227233
private static class ExceptionRaisingArgumentResolver implements HandlerMethodArgumentResolver {
228234

229235
@Override

0 commit comments

Comments
 (0)