INT-3820: Fix MethodInvokerHelper for the Proxy

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

Some Proxy may be based on the specif non-user classes and advised with
the provided end-user interfaces.
The best sample is `@Repository` from Spring Data projects.
In this case the `MessagingMethodInvokerHelper` just missed the user interfaces
to consider for the candidate methods or thrown an `Exception` like `NoSuchMethodError`.

* Add `((Advised) targetObject).getProxiedInterfaces()` for the scanning algorithm
* Move the `UniqueMethodFilter` to the internal `Set<Method>` to allow iteration
and filtering for methods from the `targetClass` as well as from all those user interfaces
* Add Spring JPA repository test-case
* Polishing form some time-weak tests

Revert MessagingMethodInvokerHelper

INT-3820: Simple Proxy Interface Method Matching

No-risk solution for INT-3820.

Instead of considering all interfaces on the proxy, only look at proxy interfaces
for a single matching method name if we find no candidate or fallback methods.

Consider the complete solution for 4.3.

Add `Message<?>` method distinguishing
This commit is contained in:
Artem Bilan
2015-09-05 00:18:42 -04:00
committed by Gary Russell
parent ca62f18a7b
commit d8fc94602c
15 changed files with 139 additions and 37 deletions

View File

@@ -440,6 +440,12 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
}, methodFilter);
if (candidateMethods.isEmpty() && candidateMessageMethods.isEmpty() && fallbackMethods.isEmpty()
&& fallbackMessageMethods.isEmpty()) {
findSingleSpecifMethodOnInterfacesIfProxy(targetObject, methodName, candidateMessageMethods,
candidateMethods);
}
if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) {
handlerMethods.put(CANDIDATE_METHODS, candidateMethods);
handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
@@ -499,6 +505,62 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
return handlerMethods;
}
private void findSingleSpecifMethodOnInterfacesIfProxy(final Object targetObject, final String methodName,
Map<Class<?>, HandlerMethod> candidateMessageMethods,
Map<Class<?>, HandlerMethod> candidateMethods) {
if (AopUtils.isAopProxy(targetObject)) {
final AtomicReference<Method> targetMethod = new AtomicReference<Method>();
Class<?>[] interfaces = ((Advised) targetObject).getProxiedInterfaces();
for (Class<?> clazz : interfaces) {
ReflectionUtils.doWithMethods(clazz, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (targetMethod.get() != null) {
throw new IllegalStateException("Ambiguous method " + methodName + " on " + targetObject);
}
else {
targetMethod.set(method);
}
}
}, new MethodFilter() {
@Override
public boolean matches(Method method) {
return method.getName().equals(methodName);
}
});
}
Method method = targetMethod.get();
if (method != null) {
HandlerMethod handlerMethod = new HandlerMethod(method, this.canProcessMessageList);
Class<?> targetParameterType = handlerMethod.getTargetParameterType();
if (handlerMethod.isMessageMethod()) {
if (candidateMessageMethods.containsKey(targetParameterType)) {
throw new IllegalArgumentException("Found more than one method match for type " +
"[Message<" + targetParameterType + ">]");
}
candidateMessageMethods.put(targetParameterType, handlerMethod);
}
else {
if (candidateMethods.containsKey(targetParameterType)) {
String exceptionMessage = "Found more than one method match for ";
if (Void.class.equals(targetParameterType)) {
exceptionMessage += "empty parameter for 'payload'";
}
else {
exceptionMessage += "type [" + targetParameterType + "]";
}
throw new IllegalArgumentException(exceptionMessage);
}
candidateMethods.put(targetParameterType, handlerMethod);
}
}
}
}
private Class<?> getTargetClass(Object targetObject) {
Class<?> targetClass = targetObject.getClass();
if (AopUtils.isAopProxy(targetObject)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2015 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.
@@ -77,7 +77,7 @@ public class DispatchingChannelErrorHandlingTests {
});
Message<?> message = MessageBuilder.withPayload("test").build();
channel.send(message);
this.waitForLatch(1000);
this.waitForLatch(10000);
Message<?> errorMessage = resultHandler.lastMessage;
assertEquals(MessagingException.class, errorMessage.getPayload().getClass());
MessagingException exceptionPayload = (MessagingException) errorMessage.getPayload();
@@ -108,7 +108,7 @@ public class DispatchingChannelErrorHandlingTests {
});
Message<?> message = MessageBuilder.withPayload("test").build();
channel.send(message);
this.waitForLatch(1000);
this.waitForLatch(10000);
Message<?> errorMessage = resultHandler.lastMessage;
assertEquals(MessagingException.class, errorMessage.getPayload().getClass());
MessagingException exceptionPayload = (MessagingException) errorMessage.getPayload();

View File

@@ -386,7 +386,7 @@ public class GatewayParserTests {
public <T> Future<T> submit(Callable<T> task) {
try {
Future<?> result = super.submit(task);
Message<?> message = (Message<?>) result.get(1, TimeUnit.SECONDS);
Message<?> message = (Message<?>) result.get(10, TimeUnit.SECONDS);
Message<?> modifiedMessage;
if (message == null) {
modifiedMessage = MessageBuilder.withPayload("foo")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
@@ -47,7 +47,7 @@ public class PollerWithErrorChannelTests {
* the ErrorMessage will still be forwarded to the 'errorChannel' since exception occurs on
* receive() and not on send()
*/
public void testWithErrorChannelAsHeader() throws Exception{
public void testWithErrorChannelAsHeader() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("withErrorHeader", SourcePollingChannelAdapter.class);
@@ -61,49 +61,52 @@ public class PollerWithErrorChannelTests {
}
@Test
public void testWithErrorChannel() throws Exception{
public void testWithErrorChannel() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannel", SourcePollingChannelAdapter.class);
adapter.start();
PollableChannel errorChannel = ac.getBean("eChannel", PollableChannel.class);
assertNotNull(errorChannel.receive(1000));
assertNotNull(errorChannel.receive(10000));
adapter.stop();
}
@Test
public void testWithErrorChannelAndHeader() throws Exception{
public void testWithErrorChannelAndHeader() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannelAndHeader", SourcePollingChannelAdapter.class);
adapter.start();
PollableChannel errorChannel = ac.getBean("eChannel", PollableChannel.class);
assertNotNull(errorChannel.receive(1000));
assertNotNull(errorChannel.receive(10000));
adapter.stop();
}
@Test
// config the same as above but the error wil come from the send
public void testWithErrorChannelAndHeaderWithSendFailure() throws Exception{
public void testWithErrorChannelAndHeaderWithSendFailure() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannelAndHeaderErrorOnSend", SourcePollingChannelAdapter.class);
adapter.start();
PollableChannel errorChannel = ac.getBean("errChannel", PollableChannel.class);
assertNotNull(errorChannel.receive(1000));
assertNotNull(errorChannel.receive(10000));
adapter.stop();
}
@Test
// INT-1952
public void testWithErrorChannelAndPollingConsumer() throws Exception{
public void testWithErrorChannelAndPollingConsumer() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass());
MessageChannel serviceWithPollerChannel = ac.getBean("serviceWithPollerChannel", MessageChannel.class);
QueueChannel errChannel = ac.getBean("serviceErrorChannel", QueueChannel.class);
QueueChannel errorChannel = ac.getBean("serviceErrorChannel", QueueChannel.class);
serviceWithPollerChannel.send(new GenericMessage<String>(""));
assertNotNull(errChannel.receive(1000));
assertNotNull(errorChannel.receive(10000));
}
public static class SampleService{
public static class SampleService {
public String withSuccess(){
return "hello";
}
}
}

View File

@@ -243,6 +243,7 @@ public class DelayHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
try {
@@ -253,9 +254,10 @@ public class DelayHandlerTests {
// won't countDown
}
}
}).start();
latch.await(50, TimeUnit.MILLISECONDS);
assertEquals(0, latch.getCount());
assertTrue(latch.await(1, TimeUnit.SECONDS));
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.