INT-3825: Messaging Ann. on Non-public Methods

JIRA: https://jira.spring.io/browse/INT-3825

Rework `MessagingMethodInvokerHelper` logic to accept non-public methods with Messaging annotations as candidates for invocation
Adjust SpEL configuration in the `MessagingMethodInvokerHelper` to deal with `declaredMethods()`, not only `public`

Honor caching in the `MethodReference`
This commit is contained in:
Artem Bilan
2016-10-07 16:36:37 -04:00
committed by Gary Russell
parent 8f0fa3468f
commit 8d94bd5e3e
3 changed files with 53 additions and 18 deletions

View File

@@ -34,6 +34,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -51,6 +52,7 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.annotation.Payloads;
import org.springframework.integration.annotation.ServiceActivator;
@@ -119,7 +121,6 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
private Method method;
public MessagingMethodInvokerHelper(Object targetObject, Method method, Class<?> expectedType,
boolean canProcessMessageList) {
this(targetObject, null, method, expectedType, canProcessMessageList);
@@ -223,7 +224,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
this.targetObject = targetObject;
this.requiresReply = expectedType != null;
Map<String, Map<Class<?>, HandlerMethod>> handlerMethodsForTarget =
this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, this.requiresReply);
findHandlerMethodsForTarget(targetObject, annotationType, methodName, this.requiresReply);
Map<Class<?>, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS);
Map<Class<?>, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS);
if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty()) ||
@@ -242,13 +243,13 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
this.handlerMethod = null;
this.handlerMethods = handlerMethods;
this.handlerMessageMethods = handlerMessageMethods;
this.handlerMethodsList = new LinkedList<Map<Class<?>, HandlerMethod>>();
this.handlerMethodsList = new LinkedList<>();
//TODO Consider to use global option to determine a precedence of methods
this.handlerMethodsList.add(this.handlerMethods);
this.handlerMethodsList.add(this.handlerMessageMethods);
}
this.setDisplayString(targetObject, methodName);
setDisplayString(targetObject, methodName);
}
private void setDisplayString(Object targetObject, Object targetMethod) {
@@ -267,8 +268,22 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
private void prepareEvaluationContext() {
StandardEvaluationContext context = getEvaluationContext(false);
Class<?> targetType = AopUtils.getTargetClass(this.targetObject);
ReflectiveMethodResolver declaredMethodResolver = new ReflectiveMethodResolver() {
@Override
protected Method[] getMethods(Class<?> type) {
return Stream.of(type.getMethods(), type.getDeclaredMethods())
.flatMap(Stream::of)
.toArray(Method[]::new);
}
};
if (this.method != null) {
context.registerMethodFilter(targetType, new FixedMethodFilter(this.method));
FixedMethodFilter fixedMethodFilter = new FixedMethodFilter(this.method);
context.registerMethodFilter(targetType, fixedMethodFilter);
declaredMethodResolver.registerMethodFilter(targetType, fixedMethodFilter);
if (this.expectedType != null) {
Assert.state(context.getTypeConverter()
.canConvert(TypeDescriptor.valueOf((this.method).getReturnType()),
@@ -277,13 +292,15 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
}
else {
AnnotatedMethodFilter filter = new AnnotatedMethodFilter(this.annotationType, this.methodName,
this.requiresReply);
Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()),
AnnotatedMethodFilter annotatedMethodFilter = new AnnotatedMethodFilter(this.annotationType,
this.methodName, this.requiresReply);
Assert.state(canReturnExpectedType(annotatedMethodFilter, targetType, context.getTypeConverter()),
"Cannot convert to expected type (" + this.expectedType + ") from " + this.method);
context.registerMethodFilter(targetType, filter);
context.registerMethodFilter(targetType, annotatedMethodFilter);
declaredMethodResolver.registerMethodFilter(targetType, annotatedMethodFilter);
}
context.setVariable("target", this.targetObject);
context.setMethodResolvers(Collections.singletonList(declaredMethodResolver));
}
private boolean canReturnExpectedType(AnnotatedMethodFilter filter, Class<?> targetType,
@@ -316,7 +333,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
Class<?> expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType();
try {
@SuppressWarnings("unchecked")
T result = (T) this.evaluateExpression(expression, parameters, expectedType);
T result = (T) evaluateExpression(expression, parameters, expectedType);
if (this.requiresReply) {
Assert.notNull(result,
"Expression evaluation result was null, but this processor requires a reply.");
@@ -348,7 +365,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
final Map<Class<?>, HandlerMethod> fallbackMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final AtomicReference<Class<?>> ambiguousFallbackType = new AtomicReference<Class<?>>();
final AtomicReference<Class<?>> ambiguousFallbackMessageGenericType = new AtomicReference<Class<?>>();
final Class<?> targetClass = this.getTargetClass(targetObject);
final Class<?> targetClass = getTargetClass(targetObject);
MethodFilter methodFilter = new UniqueMethodFilter(targetClass);
ReflectionUtils.doWithMethods(targetClass, method1 -> {
boolean matchesAnnotation = false;
@@ -361,7 +378,10 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
if (method1.getDeclaringClass().equals(Proxy.class)) {
return;
}
if (!Modifier.isPublic(method1.getModifiers())) {
if (annotationType != null && AnnotationUtils.findAnnotation(method1, annotationType) != null) {
matchesAnnotation = true;
}
else if (!Modifier.isPublic(method1.getModifiers())) {
return;
}
if (requiresReply && void.class.equals(method1.getReturnType())) {
@@ -374,9 +394,6 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
&& ObjectUtils.containsElement(new String[] { "start", "stop", "isRunning" }, method1.getName())) {
return;
}
if (annotationType != null && AnnotationUtils.findAnnotation(method1, annotationType) != null) {
matchesAnnotation = true;
}
HandlerMethod handlerMethod1 = null;
try {
handlerMethod1 = new HandlerMethod(method1, this.canProcessMessageList);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2016 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.
@@ -18,13 +18,14 @@ package org.springframework.integration.util;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class UniqueMethodFilter implements MethodFilter {
@@ -33,7 +34,7 @@ public class UniqueMethodFilter implements MethodFilter {
public UniqueMethodFilter(Class<?> targetClass) {
ArrayList<Method> allMethods = new ArrayList<Method>(Arrays.asList(targetClass.getMethods()));
Method[] allMethods = ReflectionUtils.getAllDeclaredMethods(targetClass);
for (Method method : allMethods) {
this.uniqueMethods.add(org.springframework.util.ClassUtils.getMostSpecificMethod(method, targetClass));
}

View File

@@ -550,6 +550,23 @@ public class MethodInvokingMessageProcessorTests {
assertEquals("FOO", targetObject.arguments.get("foo2"));
}
@Test
public void testPrivateMethod() throws Exception {
class Foo {
@ServiceActivator
private String service(String payload) {
return payload.toUpperCase();
}
}
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new Foo(), ServiceActivator.class, false);
assertEquals("FOO", helper.process(new GenericMessage<>("foo")));
assertEquals("BAR", helper.process(new GenericMessage<>("bar")));
}
private static class ExceptionCauseMatcher extends TypeSafeMatcher<Exception> {
private Throwable cause;