Browse Source

Revise generated default name for @JmsListener subscription

The previous commit changed the generated default name for a JMS
subscription to <FQCN>#<method name> -- for example:

- org.example.MyListener#myListenerMethod

However, the JMS spec does not guarantee that '#' is a supported
character. This commit therefore changes '#' to '.' as the separator
between the class name and method name -- for example:

- org.example.MyListener.myListenerMethod

This commit also introduces tests and documentation for these changes.

See gh-29790
pull/29918/head
Sam Brannen 2 years ago
parent
commit
04321d0577
  1. 7
      spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java
  2. 38
      spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java
  3. 104
      spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterIntegrationTests.java

7
spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java

@ -79,6 +79,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping; @@ -79,6 +79,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
* <em>composed annotations</em> with attribute overrides.
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 4.1
* @see EnableJms
* @see JmsListenerAnnotationBeanPostProcessor
@ -113,6 +114,12 @@ public @interface JmsListener { @@ -113,6 +114,12 @@ public @interface JmsListener {
/**
* The name for the durable subscription, if any.
* <p>As of Spring Framework 6.0.5, if an explicit subscription name is not
* specified, a default subscription name will be generated based on the fully
* qualified name of the annotated listener method &mdash; for example,
* {@code "org.example.jms.ProductListener.processRequest"} for a
* {@code processRequest(...)} listener method in the
* {@code org.example.jms.ProductListener} class.
*/
String subscription() default "";

38
spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java

@ -43,6 +43,10 @@ import org.springframework.util.Assert; @@ -43,6 +43,10 @@ import org.springframework.util.Assert;
* are provided as additional arguments so that these can be injected as
* method arguments if necessary.
*
* <p>As of Spring Framework 6.0.5, {@code MessagingMessageListenerAdapter} implements
* {@link SubscriptionNameProvider} in order to provide a meaningful default
* subscription name. See {@link #getSubscriptionName()} for details.
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 4.1
@ -70,16 +74,6 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis @@ -70,16 +74,6 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
return this.handlerMethod;
}
@Override
public String getSubscriptionName() {
if (this.handlerMethod != null) {
return this.handlerMethod.getBeanType().getName() + "#" + this.handlerMethod.getMethod().getName();
}
else {
return this.getClass().getName();
}
}
@Override
public void onMessage(jakarta.jms.Message jmsMessage, @Nullable Session session) throws JMSException {
@ -145,4 +139,28 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis @@ -145,4 +139,28 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
.build();
}
/**
* Generate a subscription name for this {@code MessageListener} adapter based
* on the following rules.
* <ul>
* <li>If the {@link #setHandlerMethod(InvocableHandlerMethod) handlerMethod}
* has been set, the generated subscription name takes the form of
* {@code handlerMethod.getBeanType().getName() + "." + handlerMethod.getMethod().getName()}.</li>
* <li>Otherwise, the generated subscription name is the result of invoking
* {@code getClass().getName()}, which aligns with the default behavior of
* {@link org.springframework.jms.listener.AbstractMessageListenerContainer}.</li>
* </ul>
* @since 6.0.5
* @see SubscriptionNameProvider#getSubscriptionName()
*/
@Override
public String getSubscriptionName() {
if (this.handlerMethod != null) {
return this.handlerMethod.getBeanType().getName() + "." + this.handlerMethod.getMethod().getName();
}
else {
return getClass().getName();
}
}
}

104
spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterIntegrationTests.java

@ -0,0 +1,104 @@ @@ -0,0 +1,104 @@
/*
* Copyright 2002-2023 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
*
* https://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.jms.listener.adapter;
import java.lang.reflect.Method;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Named.named;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* Integration tests for {@link MessagingMessageListenerAdapter}.
*
* <p>These tests are similar to those in {@link MessagingMessageListenerAdapterTests},
* except that these tests have a different scope and do not use mocks.
*
* @author Sam Brannen
* @since 6.0.5
* @see MessagingMessageListenerAdapterTests
*/
class MessagingMessageListenerAdapterIntegrationTests {
@ParameterizedTest
@MethodSource("subscriptionNames")
void defaultSubscriptionName(Method method, String subscriptionName) {
MessagingMessageListenerAdapter messageListenerAdaptor = new MessagingMessageListenerAdapter();
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(new CustomListener(), method);
messageListenerAdaptor.setHandlerMethod(handlerMethod);
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
assertThat(listenerContainer.getSubscriptionName()).isNull();
listenerContainer.setMessageListener(messageListenerAdaptor);
assertThat(listenerContainer.getSubscriptionName()).isEqualTo(subscriptionName);
}
private static Stream<Arguments> subscriptionNames() {
String method1 = "toUpperCase";
String method2 = "toUpperCase(java.lang.String)";
String method3 = "toUpperCase(java.lang.String,int)";
String method4 = "toUpperCase(java.lang.String[])";
String expectedName = CustomListener.class.getName() + ".toUpperCase";
return Stream.of(
arguments(named(method1, findMethod()), expectedName),
arguments(named(method2, findMethod(String.class)), expectedName),
arguments(named(method3, findMethod(String.class, String.class)), expectedName),
arguments(named(method4, findMethod(byte[].class)), expectedName));
}
private static Method findMethod(Class<?>... paramTypes) {
return ReflectionUtils.findMethod(CustomListener.class, "toUpperCase", paramTypes);
}
@SuppressWarnings("unused")
private static class CustomListener {
// @JmsListener(...)
String toUpperCase() {
return "ENIGMA";
}
// @JmsListener(...)
String toUpperCase(String input) {
return "ENIGMA";
}
// @JmsListener(...)
String toUpperCase(String input, String customHeader) {
return "ENIGMA";
}
// @JmsListener(...)
String toUpperCase(byte[] input) {
return "ENIGMA";
}
}
}
Loading…
Cancel
Save