invoker) {
+ MethodInvocationInterceptor interceptor = new MethodInvocationInterceptor();
+ T proxy = initProxy(this.objectClass, interceptor);
+ invoker.accept(proxy);
+ Method method = interceptor.getInvokedMethod();
+ return new ResolvableMethod(method);
+ }
+
+
+ // Build & resolve shortcuts...
+
+ /**
+ * Resolve and return the {@code Method} equivalent to:
+ * {@code build().method()}
+ */
+ public final Method resolveMethod() {
+ return build().method();
+ }
+
+ /**
+ * Resolve and return the {@code Method} equivalent to:
+ *
{@code named(methodName).build().method()}
+ */
+ public Method resolveMethod(String methodName) {
+ return named(methodName).build().method();
+ }
+
+ /**
+ * Resolve and return the declared return type equivalent to:
+ *
{@code build().returnType()}
+ */
+ public final MethodParameter resolveReturnType() {
+ return build().returnType();
+ }
+
+ /**
+ * Shortcut to the unique return type equivalent to:
+ *
{@code returning(returnType).build().returnType()}
+ * @param returnType the return type
+ * @param generics optional array of generic types
+ */
+ public MethodParameter resolveReturnType(Class> returnType, Class>... generics) {
+ return returning(returnType, generics).build().returnType();
+ }
+
+ /**
+ * Shortcut to the unique return type equivalent to:
+ *
{@code returning(returnType).build().returnType()}
+ * @param returnType the return type
+ * @param generic at least one generic type
+ * @param generics optional extra generic types
+ */
+ public MethodParameter resolveReturnType(Class> returnType, ResolvableType generic,
+ ResolvableType... generics) {
+
+ return returning(returnType, generic, generics).build().returnType();
+ }
+
+ public MethodParameter resolveReturnType(ResolvableType returnType) {
+ return returning(returnType).build().returnType();
+ }
+
+
+ @Override
+ public String toString() {
+ return "ResolvableMethod.Builder[\n" +
+ "\tobjectClass = " + this.objectClass.getName() + ",\n" +
+ "\tfilters = " + formatFilters() + "\n]";
+ }
+
+ private String formatFilters() {
+ return this.filters.stream().map(Object::toString)
+ .collect(joining(",\n\t\t", "[\n\t\t", "\n\t]"));
+ }
+ }
+
+
+ /**
+ * Predicate with a descriptive label.
+ */
+ private static class LabeledPredicate implements Predicate {
+
+ private final String label;
+
+ private final Predicate delegate;
+
+
+ private LabeledPredicate(String label, Predicate delegate) {
+ this.label = label;
+ this.delegate = delegate;
+ }
+
+
+ @Override
+ public boolean test(T method) {
+ return this.delegate.test(method);
+ }
+
+ @Override
+ public Predicate and(Predicate super T> other) {
+ return this.delegate.and(other);
+ }
+
+ @Override
+ public Predicate negate() {
+ return this.delegate.negate();
+ }
+
+ @Override
+ public Predicate or(Predicate super T> other) {
+ return this.delegate.or(other);
+ }
+
+ @Override
+ public String toString() {
+ return this.label;
+ }
+ }
+
+
+ /**
+ * Resolver for method arguments.
+ */
+ public class ArgResolver {
+
+ private final List> filters = new ArrayList<>(4);
+
+
+ @SafeVarargs
+ private ArgResolver(Predicate... filter) {
+ this.filters.addAll(Arrays.asList(filter));
+ }
+
+ /**
+ * Filter on method arguments with annotations.
+ */
+ @SafeVarargs
+ public final ArgResolver annot(Predicate... filters) {
+ this.filters.addAll(Arrays.asList(filters));
+ return this;
+ }
+
+ /**
+ * Filter on method arguments that have the given annotations.
+ * @param annotationTypes the annotation types
+ * @see #annot(Predicate[])
+ */
+ @SafeVarargs
+ public final ArgResolver annotPresent(Class extends Annotation>... annotationTypes) {
+ this.filters.add(param -> Arrays.stream(annotationTypes).allMatch(param::hasParameterAnnotation));
+ return this;
+ }
+
+ /**
+ * Filter on method arguments that don't have the given annotations.
+ * @param annotationTypes the annotation types
+ */
+ @SafeVarargs
+ public final ArgResolver annotNotPresent(Class extends Annotation>... annotationTypes) {
+ this.filters.add(param ->
+ (annotationTypes.length > 0 ?
+ Arrays.stream(annotationTypes).noneMatch(param::hasParameterAnnotation) :
+ param.getParameterAnnotations().length == 0));
+ return this;
+ }
+
+ /**
+ * Resolve the argument also matching to the given type.
+ * @param type the expected type
+ */
+ public MethodParameter arg(Class> type, Class>... generics) {
+ return arg(toResolvableType(type, generics));
+ }
+
+ /**
+ * Resolve the argument also matching to the given type.
+ * @param type the expected type
+ */
+ public MethodParameter arg(Class> type, ResolvableType generic, ResolvableType... generics) {
+ return arg(toResolvableType(type, generic, generics));
+ }
+
+ /**
+ * Resolve the argument also matching to the given type.
+ * @param type the expected type
+ */
+ public MethodParameter arg(ResolvableType type) {
+ this.filters.add(p -> type.toString().equals(ResolvableType.forMethodParameter(p).toString()));
+ return arg();
+ }
+
+ /**
+ * Resolve the argument.
+ */
+ public final MethodParameter arg() {
+ List matches = applyFilters();
+ Assert.state(!matches.isEmpty(), () ->
+ "No matching arg in method\n" + formatMethod());
+ Assert.state(matches.size() == 1, () ->
+ "Multiple matching args in method\n" + formatMethod() + "\nMatches:\n\t" + matches);
+ return matches.get(0);
+ }
+
+
+ private List applyFilters() {
+ List matches = new ArrayList<>();
+ for (int i = 0; i < method.getParameterCount(); i++) {
+ MethodParameter param = new SynthesizingMethodParameter(method, i);
+ param.initParameterNameDiscovery(nameDiscoverer);
+ if (this.filters.stream().allMatch(p -> p.test(param))) {
+ matches.add(param);
+ }
+ }
+ return matches;
+ }
+ }
+
+
+ private static class MethodInvocationInterceptor
+ implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
+
+ private Method invokedMethod;
+
+
+ Method getInvokedMethod() {
+ return this.invokedMethod;
+ }
+
+ @Override
+ @Nullable
+ public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) {
+ if (ReflectionUtils.isObjectMethod(method)) {
+ return ReflectionUtils.invokeMethod(method, object, args);
+ }
+ else {
+ this.invokedMethod = method;
+ return null;
+ }
+ }
+
+ @Override
+ @Nullable
+ public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
+ return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T initProxy(Class> type, MethodInvocationInterceptor interceptor) {
+ Assert.notNull(type, "'type' must not be null");
+ if (type.isInterface()) {
+ ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
+ factory.addInterface(type);
+ factory.addInterface(Supplier.class);
+ factory.addAdvice(interceptor);
+ return (T) factory.getProxy();
+ }
+
+ else {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(type);
+ enhancer.setInterfaces(new Class>[] {Supplier.class});
+ enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
+ enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
+
+ Class> proxyClass = enhancer.createClass();
+ Object proxy = null;
+
+ if (objenesis.isWorthTrying()) {
+ try {
+ proxy = objenesis.newInstance(proxyClass, enhancer.getUseCache());
+ }
+ catch (ObjenesisException ex) {
+ logger.debug("Objenesis failed, falling back to default constructor", ex);
+ }
+ }
+
+ if (proxy == null) {
+ try {
+ proxy = ReflectionUtils.accessibleConstructor(proxyClass).newInstance();
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Unable to instantiate proxy " +
+ "via both Objenesis and default constructor fails as well", ex);
+ }
+ }
+
+ ((Factory) proxy).setCallbacks(new Callback[] {interceptor});
+ return (T) proxy;
+ }
+ }
+
+}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java
index 6a960ee956..fbb0350d1b 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java
@@ -21,8 +21,9 @@ import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
-import org.springframework.util.ClassUtils;
+import org.springframework.messaging.handler.ResolvableMethod;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -37,15 +38,15 @@ public class InvocableHandlerMethodTests {
private final Message> message = mock(Message.class);
- private final HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
+ private final HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
@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);
+ this.resolvers.addResolver(new StubArgumentResolver(99));
+ this.resolvers.addResolver(new StubArgumentResolver("value"));
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ Object value = invoke(new Handler(), method);
assertEquals(1, getStubResolver(0).getResolvedParameters().size());
assertEquals(1, getStubResolver(1).getResolvedParameters().size());
@@ -56,20 +57,21 @@ public class InvocableHandlerMethodTests {
@Test
public void resolveNoArgValue() 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);
+ this.resolvers.addResolver(new StubArgumentResolver(Integer.class));
+ this.resolvers.addResolver(new StubArgumentResolver(String.class));
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ Object value = invoke(new Handler(), method);
assertEquals(1, getStubResolver(0).getResolvedParameters().size());
assertEquals(1, getStubResolver(1).getResolvedParameters().size());
- assertEquals("null-null", returnValue);
+ assertEquals("null-null", value);
}
@Test
public void cannotResolveArg() throws Exception {
try {
- getInvocable("handle", Integer.class, String.class).invoke(this.message);
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ invoke(new Handler(), method);
fail("Expected exception");
}
catch (MethodArgumentResolutionException ex) {
@@ -80,7 +82,8 @@ public class InvocableHandlerMethodTests {
@Test
public void resolveProvidedArg() throws Exception {
- Object value = getInvocable("handle", Integer.class, String.class).invoke(this.message, 99, "value");
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ Object value = invoke(new Handler(), method, 99, "value");
assertNotNull(value);
assertEquals(String.class, value.getClass());
@@ -89,18 +92,20 @@ public class InvocableHandlerMethodTests {
@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");
+ this.resolvers.addResolver(new StubArgumentResolver(1));
+ this.resolvers.addResolver(new StubArgumentResolver("value1"));
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ Object value = invoke(new Handler(), method, 2, "value2");
assertEquals("2-value2", value);
}
@Test
public void exceptionInResolvingArg() throws Exception {
- this.composite.addResolver(new ExceptionRaisingArgumentResolver());
+ this.resolvers.addResolver(new ExceptionRaisingArgumentResolver());
try {
- getInvocable("handle", Integer.class, String.class).invoke(this.message);
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ invoke(new Handler(), method);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
@@ -110,10 +115,11 @@ public class InvocableHandlerMethodTests {
@Test
public void illegalArgumentException() throws Exception {
- this.composite.addResolver(new StubArgumentResolver(Integer.class, "__not_an_int__"));
- this.composite.addResolver(new StubArgumentResolver("value"));
+ this.resolvers.addResolver(new StubArgumentResolver(Integer.class, "__not_an_int__"));
+ this.resolvers.addResolver(new StubArgumentResolver("value"));
try {
- getInvocable("handle", Integer.class, String.class).invoke(this.message);
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method();
+ invoke(new Handler(), method);
fail("Expected exception");
}
catch (IllegalStateException ex) {
@@ -129,10 +135,12 @@ public class InvocableHandlerMethodTests {
@Test
public void invocationTargetException() throws Exception {
+ Handler handler = new Handler();
+ Method method = ResolvableMethod.on(Handler.class).argTypes(Throwable.class).resolveMethod();
Throwable expected = null;
try {
expected = new RuntimeException("error");
- getInvocable("handleWithException", Throwable.class).invoke(this.message, expected);
+ invoke(handler, method, expected);
fail("Expected exception");
}
catch (RuntimeException actual) {
@@ -140,7 +148,7 @@ public class InvocableHandlerMethodTests {
}
try {
expected = new Error("error");
- getInvocable("handleWithException", Throwable.class).invoke(this.message, expected);
+ invoke(handler, method, expected);
fail("Expected exception");
}
catch (Error actual) {
@@ -148,15 +156,15 @@ public class InvocableHandlerMethodTests {
}
try {
expected = new Exception("error");
- getInvocable("handleWithException", Throwable.class).invoke(this.message, expected);
+ invoke(handler, method, expected);
fail("Expected exception");
}
catch (Exception actual) {
assertSame(expected, actual);
}
try {
- expected = new Throwable("error");
- getInvocable("handleWithException", Throwable.class).invoke(this.message, expected);
+ expected = new Throwable("error", expected);
+ invoke(handler, method, expected);
fail("Expected exception");
}
catch (IllegalStateException actual) {
@@ -168,9 +176,10 @@ public class InvocableHandlerMethodTests {
@Test // Based on SPR-13917 (spring-web)
public void invocationErrorMessage() throws Exception {
- this.composite.addResolver(new StubArgumentResolver(double.class));
+ this.resolvers.addResolver(new StubArgumentResolver(double.class));
try {
- getInvocable("handle", double.class).invoke(this.message);
+ Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0.0)).method();
+ invoke(new Handler(), method);
fail();
}
catch (IllegalStateException ex) {
@@ -178,15 +187,15 @@ public class InvocableHandlerMethodTests {
}
}
- 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;
+ @Nullable
+ private Object invoke(Object handler, Method method, Object... providedArgs) throws Exception {
+ InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(handler, method);
+ handlerMethod.setMessageMethodArgumentResolvers(this.resolvers);
+ return handlerMethod.invoke(this.message, providedArgs);
}
private StubArgumentResolver getStubResolver(int index) {
- return (StubArgumentResolver) this.composite.getResolvers().get(index);
+ return (StubArgumentResolver) this.resolvers.getResolvers().get(index);
}
diff --git a/spring-web/src/test/java/org/springframework/web/method/ResolvableMethod.java b/spring-web/src/test/java/org/springframework/web/method/ResolvableMethod.java
index 984649f0af..edf2ab4ba7 100644
--- a/spring-web/src/test/java/org/springframework/web/method/ResolvableMethod.java
+++ b/spring-web/src/test/java/org/springframework/web/method/ResolvableMethod.java
@@ -53,7 +53,6 @@ import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
-import org.springframework.web.bind.annotation.ValueConstants;
import static java.util.stream.Collectors.*;
@@ -131,11 +130,15 @@ public class ResolvableMethod {
private static final ParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
+ // Matches ValueConstants.DEFAULT_NONE (spring-web and spring-messaging)
+ private static final String DEFAULT_VALUE_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n";
+
+
private final Method method;
private ResolvableMethod(Method method) {
- Assert.notNull(method, "Method is required");
+ Assert.notNull(method, "'method' is required");
this.method = method;
}
@@ -183,7 +186,7 @@ public class ResolvableMethod {
/**
* Filter on method arguments with annotation.
- * See {@link MvcAnnotationPredicates}.
+ * See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final ArgResolver annot(Predicate... filter) {
@@ -204,6 +207,7 @@ public class ResolvableMethod {
return new ArgResolver().annotNotPresent(annotationTypes);
}
+
@Override
public String toString() {
return "ResolvableMethod=" + formatMethod();
@@ -227,7 +231,7 @@ public class ResolvableMethod {
private String formatAnnotation(Annotation annotation) {
Map map = AnnotationUtils.getAnnotationAttributes(annotation);
map.forEach((key, value) -> {
- if (value.equals(ValueConstants.DEFAULT_NONE)) {
+ if (value.equals(DEFAULT_VALUE_NONE)) {
map.put(key, "NONE");
}
});
@@ -264,11 +268,13 @@ public class ResolvableMethod {
private final List> filters = new ArrayList<>(4);
+
private Builder(Class> objectClass) {
Assert.notNull(objectClass, "Class must not be null");
this.objectClass = objectClass;
}
+
private void addFilter(String message, Predicate filter) {
this.filters.add(new LabeledPredicate<>(message, filter));
}
@@ -283,7 +289,6 @@ public class ResolvableMethod {
/**
* Filter on methods with the given parameter types.
- * @since 5.1
*/
public Builder argTypes(Class>... argTypes) {
addFilter("argTypes=" + Arrays.toString(argTypes), method ->
@@ -294,7 +299,7 @@ public class ResolvableMethod {
/**
* Filter on annotated methods.
- * See {@link MvcAnnotationPredicates}.
+ * See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final Builder annot(Predicate... filters) {
@@ -305,7 +310,7 @@ public class ResolvableMethod {
/**
* Filter on methods annotated with the given annotation type.
* @see #annot(Predicate[])
- * @see MvcAnnotationPredicates
+ * See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final Builder annotPresent(Class extends Annotation>... annotationTypes) {
@@ -395,6 +400,7 @@ public class ResolvableMethod {
return new ResolvableMethod(method);
}
+
// Build & resolve shortcuts...
/**
@@ -448,6 +454,7 @@ public class ResolvableMethod {
return returning(returnType).build().returnType();
}
+
@Override
public String toString() {
return "ResolvableMethod.Builder[\n" +
@@ -471,6 +478,7 @@ public class ResolvableMethod {
private final Predicate delegate;
+
private LabeledPredicate(String label, Predicate delegate) {
this.label = label;
this.delegate = delegate;
@@ -511,6 +519,7 @@ public class ResolvableMethod {
private final List> filters = new ArrayList<>(4);
+
@SafeVarargs
private ArgResolver(Predicate... filter) {
this.filters.addAll(Arrays.asList(filter));
@@ -518,7 +527,7 @@ public class ResolvableMethod {
/**
* Filter on method arguments with annotations.
- * See {@link MvcAnnotationPredicates}.
+ * See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final ArgResolver annot(Predicate... filters) {
@@ -530,7 +539,7 @@ public class ResolvableMethod {
* Filter on method arguments that have the given annotations.
* @param annotationTypes the annotation types
* @see #annot(Predicate[])
- * @see MvcAnnotationPredicates
+ * See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final ArgResolver annotPresent(Class extends Annotation>... annotationTypes) {
@@ -608,6 +617,7 @@ public class ResolvableMethod {
private Method invokedMethod;
+
Method getInvokedMethod() {
return this.invokedMethod;
}