Browse Source
This commit makes the 3 existing InvocableHandlerMethod types more consistent and comparable with each other. 1. Use of consistent method names and method order. 2. Consistent error formatting. 3. Explicit for loops for resolving argument values in webflux variant because that makes it more readable, creates less garabage, and it's the only way to bring consistency since the other two variants cannot throw exceptions inside Optional lambdas (vs webflux variant which can wrap it in a Mono). 4. Use package private HandlerMethodArgumentComposite in webflux variant in order to pick up the resolver argument caching that the other two variants have. 5. Polish tests. 6. Add missing tests for messaging variant.pull/2005/head
Rossen Stoyanchev
6 years ago
14 changed files with 790 additions and 381 deletions
@ -0,0 +1,231 @@
@@ -0,0 +1,231 @@
|
||||
/* |
||||
* Copyright 2002-2017 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging.handler.invocation; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
import static org.hamcrest.Matchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
/** |
||||
* Unit tests for {@link InvocableHandlerMethod}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class InvocableHandlerMethodTests { |
||||
|
||||
private Message<?> message; |
||||
|
||||
private final HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); |
||||
|
||||
|
||||
@Before |
||||
public void setUp() { |
||||
this.message = null; |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void resolveArg() throws Exception { |
||||
this.composite.addResolver(new StubArgumentResolver(99)); |
||||
this.composite.addResolver(new StubArgumentResolver("value")); |
||||
|
||||
Object value = getInvocable("handle", Integer.class, String.class).invoke(this.message); |
||||
|
||||
assertEquals(1, getStubResolver(0).getResolvedParameters().size()); |
||||
assertEquals(1, getStubResolver(1).getResolvedParameters().size()); |
||||
assertEquals("99-value", value); |
||||
assertEquals("intArg", getStubResolver(0).getResolvedParameters().get(0).getParameterName()); |
||||
assertEquals("stringArg", getStubResolver(1).getResolvedParameters().get(0).getParameterName()); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveNullArg() throws Exception { |
||||
this.composite.addResolver(new StubArgumentResolver(Integer.class)); |
||||
this.composite.addResolver(new StubArgumentResolver(String.class)); |
||||
|
||||
Object returnValue = getInvocable("handle", Integer.class, String.class).invoke(this.message); |
||||
|
||||
assertEquals(1, getStubResolver(0).getResolvedParameters().size()); |
||||
assertEquals(1, getStubResolver(1).getResolvedParameters().size()); |
||||
assertEquals("null-null", returnValue); |
||||
} |
||||
|
||||
@Test |
||||
public void cannotResolveArg() throws Exception { |
||||
try { |
||||
getInvocable("handle", Integer.class, String.class).invoke(this.message); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (MethodArgumentResolutionException ex) { |
||||
assertNotNull(ex.getMessage()); |
||||
assertTrue(ex.getMessage().contains("Could not resolve parameter [0]")); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void resolveProvidedArg() throws Exception { |
||||
Object value = getInvocable("handle", Integer.class, String.class).invoke(this.message, 99, "value"); |
||||
|
||||
assertNotNull(value); |
||||
assertEquals(String.class, value.getClass()); |
||||
assertEquals("99-value", value); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveProvidedArgFirst() throws Exception { |
||||
this.composite.addResolver(new StubArgumentResolver(1)); |
||||
this.composite.addResolver(new StubArgumentResolver("value1")); |
||||
Object value = getInvocable("handle", Integer.class, String.class).invoke(this.message, 2, "value2"); |
||||
|
||||
assertEquals("2-value2", value); |
||||
} |
||||
|
||||
@Test |
||||
public void exceptionInResolvingArg() throws Exception { |
||||
this.composite.addResolver(new ExceptionRaisingArgumentResolver()); |
||||
try { |
||||
getInvocable("handle", Integer.class, String.class).invoke(this.message); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (IllegalArgumentException ex) { |
||||
// expected - allow HandlerMethodArgumentResolver exceptions to propagate
|
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void illegalArgumentException() throws Exception { |
||||
this.composite.addResolver(new StubArgumentResolver(Integer.class, "__not_an_int__")); |
||||
this.composite.addResolver(new StubArgumentResolver("value")); |
||||
try { |
||||
getInvocable("handle", Integer.class, String.class).invoke(this.message); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (IllegalStateException ex) { |
||||
assertNotNull("Exception not wrapped", ex.getCause()); |
||||
assertTrue(ex.getCause() instanceof IllegalArgumentException); |
||||
assertTrue(ex.getMessage().contains("Endpoint [")); |
||||
assertTrue(ex.getMessage().contains("Method [")); |
||||
assertTrue(ex.getMessage().contains("with argument values:")); |
||||
assertTrue(ex.getMessage().contains("[0] [type=java.lang.String] [value=__not_an_int__]")); |
||||
assertTrue(ex.getMessage().contains("[1] [type=java.lang.String] [value=value")); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void invocationTargetException() throws Exception { |
||||
Throwable expected = new RuntimeException("error"); |
||||
try { |
||||
getInvocable("handleWithException", Throwable.class).invoke(this.message, expected); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (RuntimeException actual) { |
||||
assertSame(expected, actual); |
||||
} |
||||
|
||||
expected = new Error("error"); |
||||
try { |
||||
getInvocable("handleWithException", Throwable.class).invoke(this.message, expected); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (Error actual) { |
||||
assertSame(expected, actual); |
||||
} |
||||
|
||||
expected = new Exception("error"); |
||||
try { |
||||
getInvocable("handleWithException", Throwable.class).invoke(this.message, expected); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (Exception actual) { |
||||
assertSame(expected, actual); |
||||
} |
||||
|
||||
expected = new Throwable("error"); |
||||
try { |
||||
getInvocable("handleWithException", Throwable.class).invoke(this.message, expected); |
||||
fail("Expected exception"); |
||||
} |
||||
catch (IllegalStateException actual) { |
||||
assertNotNull(actual.getCause()); |
||||
assertSame(expected, actual.getCause()); |
||||
assertTrue(actual.getMessage().contains("Invocation failure")); |
||||
} |
||||
} |
||||
|
||||
@Test // Based on SPR-13917 (spring-web)
|
||||
public void invocationErrorMessage() throws Exception { |
||||
this.composite.addResolver(new StubArgumentResolver(double.class)); |
||||
try { |
||||
getInvocable("handle", double.class).invoke(this.message); |
||||
fail(); |
||||
} |
||||
catch (IllegalStateException ex) { |
||||
assertThat(ex.getMessage(), containsString("Illegal argument")); |
||||
} |
||||
} |
||||
|
||||
public InvocableHandlerMethod getInvocable(String methodName, Class<?>... argTypes) { |
||||
Method method = ClassUtils.getMethod(Handler.class, methodName, argTypes); |
||||
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(new Handler(), method); |
||||
handlerMethod.setMessageMethodArgumentResolvers(this.composite); |
||||
return handlerMethod; |
||||
} |
||||
|
||||
private StubArgumentResolver getStubResolver(int index) { |
||||
return (StubArgumentResolver) this.composite.getResolvers().get(index); |
||||
} |
||||
|
||||
|
||||
|
||||
@SuppressWarnings("unused") |
||||
private static class Handler { |
||||
|
||||
public String handle(Integer intArg, String stringArg) { |
||||
return intArg + "-" + stringArg; |
||||
} |
||||
|
||||
public void handle(double amount) { |
||||
} |
||||
|
||||
public void handleWithException(Throwable ex) throws Throwable { |
||||
throw ex; |
||||
} |
||||
} |
||||
|
||||
|
||||
private static class ExceptionRaisingArgumentResolver implements HandlerMethodArgumentResolver { |
||||
|
||||
@Override |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) { |
||||
throw new IllegalArgumentException("oops, can't read"); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* Copyright 2002-2018 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging.handler.invocation; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.messaging.Message; |
||||
|
||||
/** |
||||
* Stub resolver for a fixed value type and/or value. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class StubArgumentResolver implements HandlerMethodArgumentResolver { |
||||
|
||||
private final Class<?> valueType; |
||||
|
||||
@Nullable |
||||
private final Object value; |
||||
|
||||
private List<MethodParameter> resolvedParameters = new ArrayList<>(); |
||||
|
||||
|
||||
public StubArgumentResolver(Object value) { |
||||
this(value.getClass(), value); |
||||
} |
||||
|
||||
public StubArgumentResolver(Class<?> valueType) { |
||||
this(valueType, null); |
||||
} |
||||
|
||||
public StubArgumentResolver(Class<?> valueType, Object value) { |
||||
this.valueType = valueType; |
||||
this.value = value; |
||||
} |
||||
|
||||
|
||||
public List<MethodParameter> getResolvedParameters() { |
||||
return resolvedParameters; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return parameter.getParameterType().equals(this.valueType); |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) { |
||||
this.resolvedParameters.add(parameter); |
||||
return this.value; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
/* |
||||
* Copyright 2002-2018 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.reactive.result.method; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.web.reactive.BindingContext; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
/** |
||||
* Resolves method parameters by delegating to a list of registered |
||||
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}. |
||||
* Previously resolved method parameters are cached for faster lookups. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.3 |
||||
*/ |
||||
class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { |
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass()); |
||||
|
||||
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>(); |
||||
|
||||
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = |
||||
new ConcurrentHashMap<>(256); |
||||
|
||||
|
||||
/** |
||||
* Add the given {@link HandlerMethodArgumentResolver}. |
||||
*/ |
||||
public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) { |
||||
this.argumentResolvers.add(resolver); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}. |
||||
* @since 4.3 |
||||
*/ |
||||
public HandlerMethodArgumentResolverComposite addResolvers(@Nullable HandlerMethodArgumentResolver... resolvers) { |
||||
if (resolvers != null) { |
||||
Collections.addAll(this.argumentResolvers, resolvers); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}. |
||||
*/ |
||||
public HandlerMethodArgumentResolverComposite addResolvers( |
||||
@Nullable List<? extends HandlerMethodArgumentResolver> resolvers) { |
||||
|
||||
if (resolvers != null) { |
||||
this.argumentResolvers.addAll(resolvers); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Return a read-only list with the contained resolvers, or an empty list. |
||||
*/ |
||||
public List<HandlerMethodArgumentResolver> getResolvers() { |
||||
return Collections.unmodifiableList(this.argumentResolvers); |
||||
} |
||||
|
||||
/** |
||||
* Clear the list of configured resolvers. |
||||
* @since 4.3 |
||||
*/ |
||||
public void clear() { |
||||
this.argumentResolvers.clear(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Whether the given {@linkplain MethodParameter method parameter} is |
||||
* supported by any registered {@link HandlerMethodArgumentResolver}. |
||||
*/ |
||||
@Override |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return getArgumentResolver(parameter) != null; |
||||
} |
||||
|
||||
/** |
||||
* Iterate over registered |
||||
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} and |
||||
* invoke the one that supports it. |
||||
* @throws IllegalStateException if no suitable |
||||
* {@link HandlerMethodArgumentResolver} is found. |
||||
*/ |
||||
@Override |
||||
public Mono<Object> resolveArgument( |
||||
MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { |
||||
|
||||
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); |
||||
if (resolver == null) { |
||||
throw new IllegalArgumentException( |
||||
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." + |
||||
" supportsParameter should be called first."); |
||||
} |
||||
return resolver.resolveArgument(parameter, bindingContext, exchange); |
||||
} |
||||
|
||||
/** |
||||
* Find a registered {@link HandlerMethodArgumentResolver} that supports |
||||
* the given method parameter. |
||||
*/ |
||||
@Nullable |
||||
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { |
||||
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); |
||||
if (result == null) { |
||||
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) { |
||||
if (methodArgumentResolver.supportsParameter(parameter)) { |
||||
result = methodArgumentResolver; |
||||
this.argumentResolverCache.put(parameter, result); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue