Consistent InvocableHandlerMethod implementations
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.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -149,7 +149,7 @@ public class DefaultMessageHandlerMethodFactoryTests {
|
||||
createInvocableHandlerMethod(instance, "simpleString", String.class);
|
||||
|
||||
thrown.expect(MethodArgumentResolutionException.class);
|
||||
thrown.expectMessage("No suitable resolver for");
|
||||
thrown.expectMessage("No suitable resolver");
|
||||
invocableHandlerMethod2.invoke(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user