Skip to content

Commit d11b957

Browse files
rstoyanchevjhoeller
authored andcommitted
Fix NPE in InvocableHandlerMethod
Issue: SPR-13917 (cherry picked from commit b1a46cc)
1 parent 1ea9dd0 commit d11b957

File tree

2 files changed

+64
-36
lines changed

2 files changed

+64
-36
lines changed

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

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -32,15 +32,15 @@
3232
import org.springframework.web.method.HandlerMethod;
3333

3434
/**
35-
* Provides a method for invoking the handler method for a given request after resolving its method argument
36-
* values through registered {@link HandlerMethodArgumentResolver}s.
35+
* Provides a method for invoking the handler method for a given request after resolving its
36+
* method argument values through registered {@link HandlerMethodArgumentResolver}s.
3737
*
38-
* <p>Argument resolution often requires a {@link WebDataBinder} for data binding or for type conversion.
39-
* Use the {@link #setDataBinderFactory(WebDataBinderFactory)} property to supply a binder factory to pass to
40-
* argument resolvers.
38+
* <p>Argument resolution often requires a {@link WebDataBinder} for data binding or for type
39+
* conversion. Use the {@link #setDataBinderFactory(WebDataBinderFactory)} property to supply
40+
* a binder factory to pass to argument resolvers.
4141
*
42-
* <p>Use {@link #setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)} to customize
43-
* the list of argument resolvers.
42+
* <p>Use {@link #setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)}
43+
* to customize the list of argument resolvers.
4444
*
4545
* @author Rossen Stoyanchev
4646
* @since 3.1
@@ -55,17 +55,17 @@ public class InvocableHandlerMethod extends HandlerMethod {
5555

5656

5757
/**
58-
* Create an instance from the given handler and method.
58+
* Create an instance from a {@code HandlerMethod}.
5959
*/
60-
public InvocableHandlerMethod(Object bean, Method method) {
61-
super(bean, method);
60+
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
61+
super(handlerMethod);
6262
}
6363

6464
/**
65-
* Create an instance from a {@code HandlerMethod}.
65+
* Create an instance from a bean instance and a method.
6666
*/
67-
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
68-
super(handlerMethod);
67+
public InvocableHandlerMethod(Object bean, Method method) {
68+
super(bean, method);
6969
}
7070

7171
/**
@@ -75,7 +75,9 @@ public InvocableHandlerMethod(HandlerMethod handlerMethod) {
7575
* @param parameterTypes the method parameter types
7676
* @throws NoSuchMethodException when the method cannot be found
7777
*/
78-
public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
78+
public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
79+
throws NoSuchMethodException {
80+
7981
super(bean, methodName, parameterTypes);
8082
}
8183

@@ -107,16 +109,18 @@ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDisc
107109

108110

109111
/**
110-
* Invoke the method after resolving its argument values in the context of the given request. <p>Argument
111-
* values are commonly resolved through {@link HandlerMethodArgumentResolver}s. The {@code provideArgs}
112-
* parameter however may supply argument values to be used directly, i.e. without argument resolution.
113-
* Examples of provided argument values include a {@link WebDataBinder}, a {@link SessionStatus}, or
114-
* a thrown exception instance. Provided argument values are checked before argument resolvers.
112+
* Invoke the method after resolving its argument values in the context of the given request.
113+
* <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s.
114+
* The {@code providedArgs} parameter however may supply argument values to be used directly,
115+
* i.e. without argument resolution. Examples of provided argument values include a
116+
* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
117+
* Provided argument values are checked before argument resolvers.
115118
* @param request the current request
116119
* @param mavContainer the ModelAndViewContainer for this request
117120
* @param providedArgs "given" arguments matched by type, not resolved
118121
* @return the raw value returned by the invoked method
119-
* @exception Exception raised if no suitable argument resolver can be found, or the method raised an exception
122+
* @exception Exception raised if no suitable argument resolver can be found,
123+
* or if the method raised an exception
120124
*/
121125
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
122126
Object... providedArgs) throws Exception {
@@ -129,7 +133,7 @@ public final Object invokeForRequest(NativeWebRequest request, ModelAndViewConta
129133
sb.append(Arrays.asList(args));
130134
logger.trace(sb.toString());
131135
}
132-
Object returnValue = invoke(args);
136+
Object returnValue = doInvoke(args);
133137
if (logger.isTraceEnabled()) {
134138
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
135139
}
@@ -159,8 +163,8 @@ private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewC
159163
continue;
160164
}
161165
catch (Exception ex) {
162-
if (logger.isTraceEnabled()) {
163-
logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
166+
if (logger.isDebugEnabled()) {
167+
logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
164168
}
165169
throw ex;
166170
}
@@ -180,7 +184,8 @@ private String getArgumentResolutionErrorMessage(String message, int index) {
180184
}
181185

182186
/**
183-
* Adds HandlerMethod details such as the controller type and method signature to the given error message.
187+
* Adds HandlerMethod details such as the controller type and method
188+
* signature to the given error message.
184189
* @param message error message to append the HandlerMethod details to
185190
*/
186191
protected String getDetailedErrorMessage(String message) {
@@ -206,17 +211,19 @@ private Object resolveProvidedArgument(MethodParameter parameter, Object... prov
206211
return null;
207212
}
208213

214+
209215
/**
210216
* Invoke the handler method with the given argument values.
211217
*/
212-
private Object invoke(Object... args) throws Exception {
218+
protected Object doInvoke(Object... args) throws Exception {
213219
ReflectionUtils.makeAccessible(getBridgedMethod());
214220
try {
215221
return getBridgedMethod().invoke(getBean(), args);
216222
}
217223
catch (IllegalArgumentException ex) {
218224
assertTargetBean(getBridgedMethod(), getBean(), args);
219-
throw new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex);
225+
String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
226+
throw new IllegalStateException(getInvocationErrorMessage(message, args), ex);
220227
}
221228
catch (InvocationTargetException ex) {
222229
// Unwrap for HandlerExceptionResolvers ...
@@ -249,7 +256,7 @@ private void assertTargetBean(Method method, Object targetBean, Object[] args) {
249256
Class<?> targetBeanClass = targetBean.getClass();
250257
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
251258
String msg = "The mapped controller method class '" + methodDeclaringClass.getName() +
252-
"' is not an instance of the actual controller bean instance '" +
259+
"' is not an instance of the actual controller bean class '" +
253260
targetBeanClass.getName() + "'. If the controller requires proxying " +
254261
"(e.g. due to @Transactional), please use class-based proxying.";
255262
throw new IllegalStateException(getInvocationErrorMessage(msg, args));
@@ -259,7 +266,7 @@ private void assertTargetBean(Method method, Object targetBean, Object[] args) {
259266
private String getInvocationErrorMessage(String message, Object[] resolvedArgs) {
260267
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message));
261268
sb.append("Resolved arguments: \n");
262-
for (int i=0; i < resolvedArgs.length; i++) {
269+
for (int i = 0; i < resolvedArgs.length; i++) {
263270
sb.append("[").append(i).append("] ");
264271
if (resolvedArgs[i] == null) {
265272
sb.append("[null] \n");

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -16,16 +16,11 @@
1616

1717
package org.springframework.web.method.support;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertNotNull;
21-
import static org.junit.Assert.assertSame;
22-
import static org.junit.Assert.assertTrue;
23-
import static org.junit.Assert.fail;
24-
2519
import java.lang.reflect.Method;
2620

2721
import org.junit.Before;
2822
import org.junit.Test;
23+
2924
import org.springframework.core.MethodParameter;
3025
import org.springframework.http.converter.HttpMessageNotReadableException;
3126
import org.springframework.mock.web.test.MockHttpServletRequest;
@@ -34,6 +29,9 @@
3429
import org.springframework.web.context.request.NativeWebRequest;
3530
import org.springframework.web.context.request.ServletWebRequest;
3631

32+
import static org.hamcrest.Matchers.*;
33+
import static org.junit.Assert.*;
34+
3735
/**
3836
* Test fixture for {@link InvocableHandlerMethod} unit tests.
3937
*
@@ -198,6 +196,26 @@ public void invocationTargetException() throws Exception {
198196
}
199197
}
200198

199+
@Test // SPR-13917
200+
public void invocationErrorMessage() throws Exception {
201+
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
202+
composite.addResolver(new StubArgumentResolver(double.class, null));
203+
204+
Method method = Handler.class.getDeclaredMethod("handle", double.class);
205+
Object handler = new Handler();
206+
InvocableHandlerMethod hm = new InvocableHandlerMethod(handler, method);
207+
hm.setHandlerMethodArgumentResolvers(composite);
208+
209+
try {
210+
hm.invokeForRequest(this.webRequest, new ModelAndViewContainer());
211+
fail();
212+
}
213+
catch (IllegalStateException ex) {
214+
assertThat(ex.getMessage(), containsString("Illegal argument"));
215+
}
216+
}
217+
218+
201219
private void invokeExceptionRaisingHandler(Throwable expected) throws Exception {
202220
Method method = ExceptionRaisingHandler.class.getDeclaredMethod("raiseException");
203221
Object handler = new ExceptionRaisingHandler(expected);
@@ -212,6 +230,9 @@ private static class Handler {
212230
public String handle(Integer intArg, String stringArg) {
213231
return intArg + "-" + stringArg;
214232
}
233+
234+
public void handle(double amount) {
235+
}
215236
}
216237

217238

0 commit comments

Comments
 (0)