diff --git a/src/main/java/org/springframework/data/redis/listener/adapter/MessageListenerAdapter.java b/src/main/java/org/springframework/data/redis/listener/adapter/MessageListenerAdapter.java index 8dcadd215..adcd95e15 100644 --- a/src/main/java/org/springframework/data/redis/listener/adapter/MessageListenerAdapter.java +++ b/src/main/java/org/springframework/data/redis/listener/adapter/MessageListenerAdapter.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.connection.Message; @@ -35,6 +36,7 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.util.ReflectionUtils.MethodFilter; +import org.springframework.util.StringUtils; /** * Message listener adapter that delegates the handling of messages to target @@ -42,6 +44,14 @@ import org.springframework.util.ReflectionUtils.MethodFilter; * Allows listener methods to operate on message content types, completely * independent from the Redis API. * + *

Make sure to call {@link #afterPropertiesSet()} after setting all the parameters + * on the adapter. + * + *

Note that if the underlying "delegate" is implementing {@link MessageListener}, the adapter + * will delegate to it and allow an invalid method to be specified. However if it is not, the method + * becomes mandatory. + * This lenient behavior allows the adapter to be used uniformly across existing listeners and message POJOs. + * *

* Modeled as much as possible after the JMS MessageListenerAdapter in Spring * Framework. @@ -97,14 +107,20 @@ import org.springframework.util.ReflectionUtils.MethodFilter; * @author Costin Leau * @see org.springframework.jms.listener.adapter.MessageListenerAdapter */ -public class MessageListenerAdapter implements MessageListener { +public class MessageListenerAdapter implements InitializingBean, MessageListener { private class MethodInvoker { private final Object delegate; - List methods; + private String methodName; + + private List methods; + private boolean lenient = false; MethodInvoker(Object delegate, final String methodName) { this.delegate = delegate; + this.methodName = methodName; + + lenient = delegate instanceof MessageListener; Class c = delegate.getClass(); @@ -112,43 +128,46 @@ public class MessageListenerAdapter implements MessageListener { ReflectionUtils.doWithMethods(c, new MethodCallback() { - public void doWith(Method method) - throws IllegalArgumentException, IllegalAccessException { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { ReflectionUtils.makeAccessible(method); methods.add(method); } }, new MethodFilter() { public boolean matches(Method method) { - if (Modifier.isPublic(method.getModifiers()) - && methodName.equals(method.getName())) { + if (Modifier.isPublic(method.getModifiers()) && methodName.equals(method.getName())) { // check out the argument numbers Class[] parameterTypes = method.getParameterTypes(); - return ((parameterTypes.length == 2 && String.class - .equals(parameterTypes[1])) || parameterTypes.length == 1); + return ((parameterTypes.length == 2 && String.class.equals(parameterTypes[1])) || parameterTypes.length == 1); } return false; } }); - Assert.isTrue(!methods.isEmpty(), - "Cannot find a suitable method named [" - + c.getName() - + "#" - + methodName - + "] - is the method public and has the proper arguments?"); + Assert.isTrue(lenient || !methods.isEmpty(), "Cannot find a suitable method named [" + c.getName() + "#" + + methodName + + "] - is the method public and has the proper arguments?"); } void invoke(Object[] arguments) throws InvocationTargetException, IllegalAccessException { - Object[] message = new Object[] {arguments[0]}; + Object[] message = new Object[] { arguments[0] }; for (Method m : methods) { Class[] types = m.getParameterTypes(); - Object[] args = (types.length == 2 ? arguments: message); + Object[] args = (types.length == 2 ? arguments : message); m.invoke(delegate, args); } } + + /** + * Returns the current methodName. + * + * @return the methodName + */ + public String getMethodName() { + return methodName; + } } /** @@ -159,9 +178,9 @@ public class MessageListenerAdapter implements MessageListener { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); - private Object delegate; + private volatile Object delegate; - private MethodInvoker invoker; + private volatile MethodInvoker invoker; private String defaultListenerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD; @@ -202,7 +221,6 @@ public class MessageListenerAdapter implements MessageListener { public void setDelegate(Object delegate) { Assert.notNull(delegate, "Delegate must not be null"); this.delegate = delegate; - this.invoker = null; } /** @@ -224,7 +242,6 @@ public class MessageListenerAdapter implements MessageListener { */ public void setDefaultListenerMethod(String defaultListenerMethod) { this.defaultListenerMethod = defaultListenerMethod; - this.invoker = null; } /** @@ -258,6 +275,19 @@ public class MessageListenerAdapter implements MessageListener { this.stringSerializer = serializer; } + @Override + public void afterPropertiesSet() { + String methodName = getDefaultListenerMethod(); + + if (!StringUtils.hasText(methodName)) { + throw new InvalidDataAccessApiUsageException("No default listener method specified: " + + "Either specify a non-null value for the 'defaultListenerMethod' property or " + + "override the 'getListenerMethodName' method."); + } + + invoker = new MethodInvoker(delegate, methodName); + } + /** * Standard Redis {@link MessageListener} entry point. *

@@ -284,24 +314,9 @@ public class MessageListenerAdapter implements MessageListener { Object convertedMessage = extractMessage(message); String convertedChannel = stringSerializer.deserialize(pattern); // Invoke the handler method with appropriate arguments. - Object[] listenerArguments = new Object[] { convertedMessage, - convertedChannel }; + Object[] listenerArguments = new Object[] { convertedMessage, convertedChannel }; - String methodName = getListenerMethodName(message, convertedMessage); - - if (methodName == null) { - throw new InvalidDataAccessApiUsageException( - "No default listener method specified: " - + "Either specify a non-null value for the 'defaultListenerMethod' property or " - + "override the 'getListenerMethodName' method."); - } - - if (invoker == null) { - invoker = new MethodInvoker(delegate, methodName); - } - - - invokeListenerMethod(methodName, listenerArguments); + invokeListenerMethod(invoker.getMethodName(), listenerArguments); } catch (Throwable th) { handleListenerException(th); } @@ -360,8 +375,7 @@ public class MessageListenerAdapter implements MessageListener { * @return the name of the listener method (never null) * @see #setDefaultListenerMethod */ - protected String getListenerMethodName(Message originalMessage, - Object extractedMessage) { + protected String getListenerMethodName(Message originalMessage, Object extractedMessage) { return getDefaultListenerMethod(); } @@ -373,7 +387,6 @@ public class MessageListenerAdapter implements MessageListener { * @param arguments * the message arguments to be passed in * @see #getListenerMethodName - * @see #buildListenerArguments */ protected void invokeListenerMethod(String methodName, Object[] arguments) { try { @@ -382,16 +395,14 @@ public class MessageListenerAdapter implements MessageListener { Throwable targetEx = ex.getTargetException(); if (targetEx instanceof DataAccessException) { throw (DataAccessException) targetEx; - } else { - throw new RedisListenerExecutionFailedException( - "Listener method '" + methodName + "' threw exception", + } + else { + throw new RedisListenerExecutionFailedException("Listener method '" + methodName + "' threw exception", targetEx); } } catch (Throwable ex) { - throw new RedisListenerExecutionFailedException( - "Failed to invoke target method '" + methodName - + "' with arguments " - + ObjectUtils.nullSafeToString(arguments), ex); + throw new RedisListenerExecutionFailedException("Failed to invoke target method '" + methodName + + "' with arguments " + ObjectUtils.nullSafeToString(arguments), ex); } } } \ No newline at end of file diff --git a/src/test/java/org/springframework/data/redis/listener/PubSubTests.java b/src/test/java/org/springframework/data/redis/listener/PubSubTests.java index 46728a6aa..68b50ed5a 100644 --- a/src/test/java/org/springframework/data/redis/listener/PubSubTests.java +++ b/src/test/java/org/springframework/data/redis/listener/PubSubTests.java @@ -15,7 +15,8 @@ */ package org.springframework.data.redis.listener; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.Collection; @@ -34,8 +35,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.springframework.data.redis.ConnectionFactoryTracker; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.support.collections.ObjectFactory; @@ -56,7 +55,7 @@ public class PubSubTests { private final BlockingDeque bag = new LinkedBlockingDeque(99); private final Object handler = new Object() { - void handleMessage(String message) { + public void handleMessage(String message) { bag.add(message); } }; @@ -66,6 +65,7 @@ public class PubSubTests { @Before public void setUp() throws Exception { adapter.setSerializer(template.getValueSerializer()); + adapter.afterPropertiesSet(); container = new RedisMessageListenerContainer(); container.setConnectionFactory(template.getConnectionFactory()); diff --git a/src/test/java/org/springframework/data/redis/listener/adapter/MessageListenerTest.java b/src/test/java/org/springframework/data/redis/listener/adapter/MessageListenerTest.java index ba584acc7..ca75c80cc 100644 --- a/src/test/java/org/springframework/data/redis/listener/adapter/MessageListenerTest.java +++ b/src/test/java/org/springframework/data/redis/listener/adapter/MessageListenerTest.java @@ -15,8 +15,10 @@ */ package org.springframework.data.redis.listener.adapter; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import org.junit.Before; import org.junit.Test; @@ -25,7 +27,6 @@ import org.mockito.MockitoAnnotations; import org.springframework.data.redis.connection.DefaultMessage; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; -import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -97,6 +98,7 @@ public class MessageListenerTest { @Test public void testRawMessage() throws Exception { MessageListenerAdapter adapter = new MessageListenerAdapter(target); + adapter.afterPropertiesSet(); adapter.onMessage(STRING_MSG, null); verify(target).handleMessage(PAYLOAD); @@ -106,6 +108,8 @@ public class MessageListenerTest { public void testCustomMethod() throws Exception { MessageListenerAdapter adapter = new MessageListenerAdapter(target); adapter.setDefaultListenerMethod("customMethod"); + adapter.afterPropertiesSet(); + adapter.onMessage(STRING_MSG, null); verify(target).customMethod(PAYLOAD); @@ -115,8 +119,10 @@ public class MessageListenerTest { public void testCustomMethodWithChannel() throws Exception { MessageListenerAdapter adapter = new MessageListenerAdapter(target); adapter.setDefaultListenerMethod("customMethodWithChannel"); + adapter.afterPropertiesSet(); + adapter.onMessage(STRING_MSG, RAW_CHANNEL); verify(target).customMethodWithChannel(PAYLOAD, CHANNEL); } -} \ No newline at end of file +}