diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/EndpointId.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/EndpointId.java
new file mode 100644
index 0000000000..c0d111236c
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/EndpointId.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://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.integration.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * When used alongside an EIP annotation (and no {@code @Bean}), specifies the bean name of
+ * the consumer bean with the handler bean being {@code id.handler} (for a consuming
+ * endpoint) or {@code id.source} for a message source (e.g. inbound channel adapter).
+ *
+ * When there is also a {@code @Bean} annotation, this is the name of the consumer or
+ * source polling bean (the handler or source gets the normal {@code @Bean} name). When
+ * using on a {@code MessageHandler @Bean}, it is recommended to name the bean
+ * {@code foo.handler} when using {@code @EndpointId("foo"}. This will align with
+ * conventions in the framework. Similarly, for a message source, use
+ * {@code @Bean("bar.source"} and {@code @EndpointId("bar")}.
+ *
+ * This is not allowed if there are multiple EIP annotations on the same method.
+ *
+ * @author Gary Russell
+ *
+ * @since 5.0.4
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EndpointId {
+
+ /**
+ * @return the id
+ */
+ String value();
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java
index 6384ffb5c0..401a4cc310 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -59,6 +59,7 @@ import org.springframework.integration.endpoint.ReactiveStreamsConsumer;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper;
import org.springframework.integration.handler.advice.HandleMessageAdvice;
import org.springframework.integration.router.AbstractMessageRouter;
import org.springframework.integration.scheduling.PollerMetadata;
@@ -176,6 +177,10 @@ public abstract class AbstractMethodAnnotationPostProcessor, MethodAnnotationPostProcessor>> postProcessors =
new HashMap, MethodAnnotationPostProcessor>>();
- private final MultiValueMap lazyLifecycleRoles = new LinkedMultiValueMap();
-
private ConfigurableListableBeanFactory beanFactory;
private final Set> noAnnotationsCache =
@@ -151,7 +147,11 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
}
}
}
-
+ if (StringUtils.hasText(MessagingAnnotationUtils.endpointIdValue(method))
+ && annotationChains.keySet().size() > 1) {
+ throw new IllegalStateException("@EndpointId on " + method.toGenericString()
+ + " can only have one EIP annotation, found: " + annotationChains.keySet().size());
+ }
for (Entry, List> entry : annotationChains.entrySet()) {
Class extends Annotation> annotationType = entry.getKey();
List annotations = entry.getValue();
@@ -225,7 +225,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
List annotationChain = new LinkedList();
Set visited = new HashSet();
for (Annotation ann : annotations) {
- this.recursiveFindAnnotation(annotationType, ann, annotationChain, visited);
+ recursiveFindAnnotation(annotationType, ann, annotationChain, visited);
if (annotationChain.size() > 0) {
Collections.reverse(annotationChain);
return annotationChain;
@@ -244,7 +244,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
if (!ann.equals(metaAnn) && !visited.contains(metaAnn)
&& !(metaAnn.annotationType().getPackage().getName().startsWith("java.lang"))) {
visited.add(metaAnn); // prevent infinite recursion if the same annotation is found again
- if (this.recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) {
+ if (recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) {
annotationChain.add(ann);
return true;
}
@@ -255,12 +255,15 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
protected String generateBeanName(String originalBeanName, Method method,
Class extends Annotation> annotationType) {
- String baseName = originalBeanName + "." + method.getName() + "."
- + ClassUtils.getShortNameAsProperty(annotationType);
- String name = baseName;
- int count = 1;
- while (this.beanFactory.containsBean(name)) {
- name = baseName + "#" + (++count);
+ String name = MessagingAnnotationUtils.endpointIdValue(method);
+ if (!StringUtils.hasText(name)) {
+ String baseName = originalBeanName + "." + method.getName() + "."
+ + ClassUtils.getShortNameAsProperty(annotationType);
+ name = baseName;
+ int count = 1;
+ while (this.beanFactory.containsBean(name)) {
+ name = baseName + "#" + (++count);
+ }
}
return name;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingAnnotationUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingAnnotationUtils.java
index c25e110fdf..493e5582ca 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingAnnotationUtils.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingAnnotationUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2016 the original author or authors.
+ * Copyright 2014-2018 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.
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.integration.annotation.EndpointId;
import org.springframework.integration.annotation.Payloads;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.handler.annotation.Header;
@@ -117,6 +118,17 @@ public final class MessagingAnnotationUtils {
return match;
}
+ /**
+ * Return the {@link EndpointId#value()} property, if present.
+ * @param method the methods.
+ * @return the id, or null.
+ * @since 5.0.4
+ */
+ public static String endpointIdValue(Method method) {
+ EndpointId endpointId = AnnotationUtils.findAnnotation(method, EndpointId.class);
+ return endpointId != null ? endpointId.value() : null;
+ }
+
private static Class> getTargetClass(Object targetObject) {
Class> targetClass = targetObject.getClass();
if (AopUtils.isAopProxy(targetObject)) {
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/BeanNameTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/BeanNameTests.java
new file mode 100644
index 0000000000..27b003f81d
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/BeanNameTests.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://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.integration.endpoint;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.EndpointId;
+import org.springframework.integration.annotation.InboundChannelAdapter;
+import org.springframework.integration.annotation.Poller;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.config.EnableIntegration;
+import org.springframework.integration.config.EnableIntegrationManagement;
+import org.springframework.integration.core.MessageSource;
+import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+
+/**
+ * @author Gary Russell
+ * @since 5.0.4
+ *
+ */
+@RunWith(SpringRunner.class)
+@DirtiesContext
+public class BeanNameTests {
+
+ @SuppressWarnings("unused")
+ @Autowired
+ private EventDrivenConsumer eipMethod;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ @Qualifier("eipMethod.handler")
+ private MessageHandler eipMethodHandler;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ private EventDrivenConsumer eipBean;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ @Qualifier("eipBean.handler")
+ private MessageHandler eipBeanHandler;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ private EventDrivenConsumer eipBean2;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ @Qualifier("eipBean2.handler")
+ private MessageHandler eipBean2Handler;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ private SourcePollingChannelAdapter eipMethodSource;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ @Qualifier("eipMethodSource.source")
+ private MessageSource> eipMethodSourceSource;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ private SourcePollingChannelAdapter eipSource;
+
+ @SuppressWarnings("unused")
+ @Autowired
+ @Qualifier("eipSource.source")
+ private MessageSource> eipSourceSource;
+
+ @Test
+ public void contextLoads() {
+
+ }
+
+ @Configuration
+ @EnableIntegration
+ @EnableIntegrationManagement
+ public static class Config {
+
+ @Bean
+ public MeterRegistry meterRegistry() {
+ return new SimpleMeterRegistry();
+ }
+
+ @ServiceActivator(inputChannel = "channel")
+ @EndpointId("eipMethod")
+ public void service(String in) {
+ if ("bar".equals(in)) {
+ throw new RuntimeException("testErrorCount");
+ }
+ }
+
+ @Bean("eipBean.handler")
+ @EndpointId("eipBean")
+ @ServiceActivator(inputChannel = "channel2")
+ public MessageHandler replyingHandler() {
+ return new AbstractReplyProducingMessageHandler() {
+
+ @Override
+ protected Object handleRequestMessage(Message> requestMessage) {
+ return null;
+ }
+
+ };
+ }
+
+ @Bean("eipBean2.handler")
+ @EndpointId("eipBean2")
+ @ServiceActivator(inputChannel = "channel3")
+ public MessageHandler handler() {
+ return m -> { };
+ }
+
+ @EndpointId("eipMethodSource")
+ @InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
+ public String pojoSource() {
+ return null;
+ }
+
+ @Bean("eipSource.source")
+ @EndpointId("eipSource")
+ @InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
+ public MessageSource> source() {
+ return () -> null;
+ }
+
+ }
+
+}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/support/management/micrometer/MicrometerMetricsTests.java b/spring-integration-core/src/test/java/org/springframework/integration/support/management/micrometer/MicrometerMetricsTests.java
index dc317cdbde..89b51450bb 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/support/management/micrometer/MicrometerMetricsTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/support/management/micrometer/MicrometerMetricsTests.java
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.EndpointId;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.AbstractPollableChannel;
@@ -38,7 +39,9 @@ import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.EnableIntegrationManagement;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.AbstractMessageSource;
+import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
@@ -67,6 +70,9 @@ public class MicrometerMetricsTests {
@Autowired
private AbstractMessageChannel channel;
+ @Autowired
+ private AbstractMessageChannel channel2;
+
@Autowired
private MessageSource> source;
@@ -90,6 +96,7 @@ public class MicrometerMetricsTests {
catch (MessagingException e) {
assertThat(e.getCause().getMessage()).isEqualTo("testErrorCount");
}
+ this.channel2.send(message);
this.queue.send(message);
this.queue.send(message);
this.queue.receive();
@@ -103,8 +110,8 @@ public class MicrometerMetricsTests {
}
nullChannel.send(message);
MeterRegistry registry = this.meterRegistry;
- assertThat(registry.get("spring.integration.channels").gauge().value()).isEqualTo(5);
- assertThat(registry.get("spring.integration.handlers").gauge().value()).isEqualTo(2);
+ assertThat(registry.get("spring.integration.channels").gauge().value()).isEqualTo(6);
+ assertThat(registry.get("spring.integration.handlers").gauge().value()).isEqualTo(3);
assertThat(registry.get("spring.integration.sources").gauge().value()).isEqualTo(1);
assertThat(registry.get("spring.integration.receive")
@@ -118,7 +125,12 @@ public class MicrometerMetricsTests {
.counter().count()).isEqualTo(1);
assertThat(registry.get("spring.integration.send")
- .tag("name", "micrometerMetricsTests.Config.service.serviceActivator.handler")
+ .tag("name", "eipBean.handler")
+ .tag("result", "success")
+ .timer().count()).isEqualTo(1);
+
+ assertThat(registry.get("spring.integration.send")
+ .tag("name", "eipMethod.handler")
.tag("result", "success")
.timer().count()).isEqualTo(1);
@@ -133,7 +145,7 @@ public class MicrometerMetricsTests {
.timer().count()).isEqualTo(1);
assertThat(registry.get("spring.integration.send")
- .tag("name", "micrometerMetricsTests.Config.service.serviceActivator.handler")
+ .tag("name", "eipMethod.handler")
.tag("result", "failure")
.timer().count()).isEqualTo(1);
@@ -171,12 +183,27 @@ public class MicrometerMetricsTests {
}
@ServiceActivator(inputChannel = "channel")
+ @EndpointId("eipMethod")
public void service(String in) {
if ("bar".equals(in)) {
throw new RuntimeException("testErrorCount");
}
}
+ @Bean("eipBean.handler")
+ @EndpointId("eipBean")
+ @ServiceActivator(inputChannel = "channel2")
+ public MessageHandler replyingHandler() {
+ return new AbstractReplyProducingMessageHandler() {
+
+ @Override
+ protected Object handleRequestMessage(Message> requestMessage) {
+ return null;
+ }
+
+ };
+ }
+
@Bean
public MessageSource> source() {
return new AbstractMessageSource() {
diff --git a/src/reference/asciidoc/configuration.adoc b/src/reference/asciidoc/configuration.adoc
index 587708a2dc..2288725a5c 100644
--- a/src/reference/asciidoc/configuration.adoc
+++ b/src/reference/asciidoc/configuration.adoc
@@ -598,6 +598,7 @@ public class MyFlowConfiguration {
public Supplier> messageSupplier() {
return () -> new GenericMessage<>("foo");
}
+}
----
The meta-annotation rules work on `@Bean` methods as well (`@MyServiceActivator` above can be applied to a `@Bean` definition).
@@ -613,11 +614,13 @@ This works like there is no Messaging Annotation on the `@Bean` method.
`[configurationComponentName].[methodName].[decapitalizedAnnotationClassShortName]`.
For example the endpoint (`SourcePollingChannelAdapter`) for the `consoleSource()` definition above gets a bean name like:
`myFlowConfiguration.consoleSource.inboundChannelAdapter`.
+Also see <>.
IMPORTANT: When using these annotations on `@Bean` definitions, the `inputChannel` must reference a declared bean; channels are not automatically declared in this case.
NOTE: With Java & Annotation configuration we can use any `@Conditional` (e.g. `@Profile`) definition on the `@Bean`
method level, meaning to skip the bean registration by some condition reason:
+
[source,java]
----
@Bean
diff --git a/src/reference/asciidoc/overview.adoc b/src/reference/asciidoc/overview.adoc
index 3a090b26fc..7643771866 100644
--- a/src/reference/asciidoc/overview.adoc
+++ b/src/reference/asciidoc/overview.adoc
@@ -211,6 +211,167 @@ image::images/target-endpoint.jpg[align="center", scaledwidth=100%]
NOTE: As discussed in <> above, channels can be _Pollable_ or _Subscribable_; in this diagram, this is depicted by the "clock" symbol and the solid arrow (poll) and the dotted arrow (subscribe).
+[[endpoint-bean-names]]
+==== Endpoint Bean Names
+
+Consuming endpoints (anything with an `inputChannel`) consist of two beans, the consumer and message handler.
+The consumer has a reference to the message handler and invokes it as messages arrive.
+
+When configuring with XML:
+
+[source, xml]
+----
+
+----
+
+the bean names will be as follows:
+
+- Consumer: `someService` (the `id`)
+- Handler: `someService.handler`
+
+When using EIP annotations, the names depend on several factors.
+
+**When Annotating POJO Methods**
+
+[source, java]
+----
+@Component
+public class SomeComponent {
+
+ @ServiceActivator(inputChannel = ...)
+ public String someMethod(...) {
+ ...
+ }
+
+}
+----
+
+the bean names will be as follows:
+
+- Consumer: `someComponent.someMethod.serviceActivator`
+- Handler: `someComponent.someMethod.serviceActivator.handler`
+
+Starting with _version 5.0.4_, these names can be modified using the `@EndpointId` annotation:
+
+[source, java]
+----
+@Component
+public class SomeComponent {
+
+ @EndpointId("someService")
+ @ServiceActivator(inputChannel = ...)
+ public String someMethod(...) {
+ ...
+ }
+
+}
+----
+
+the bean names will be as follows:
+
+- Consumer: `someService`
+- Handler: `someService.handler`
+
+i.e. `@EndpointId` creates names as created by the `id` attribute with XML configuration.
+
+
+**When Annotating @Beans**
+
+[source, java]
+----
+@Configuratiom
+public class SomeConfiguration {
+
+ @Bean
+ @ServiceActivator(inputChannel = ...)
+ public MessageHandler someHandler() {
+ ...
+ }
+
+}
+----
+
+the bean names will be as follows:
+
+- Consumer: `someConfiguration.someHandler.serviceActivator`
+- Handler: `someHandler` (the `@Bean` name)
+
+Starting with _version 5.0.4_, these names can be modified using the `@EndpointId` annotation:
+
+[source, java]
+----
+@Configuratiom
+public class SomeConfiguration {
+
+ @Bean("someService.handler")
+ @EndpointId("someService")
+ @ServiceActivator(inputChannel = ...)
+ public MessageHandler someHandler() {
+ ...
+ }
+
+}
+----
+
+- Consumer: `someService`
+- Handler: `someService.handler`
+
+i.e. `@EndpointId` creates names as created by the `id` attribute with XML configuration, as long as you use the convention of appending `.handler` to the `@Bean` name.
+
+There is one special case where a third bean is created; for architectural reasons, if a `MessageHandler` `@Bean` does not define an `AbstractReplyProducingMessageHandler`, the framework wraps the provided bean in a `ReplyProducingMessageHandlerWrapper`.
+This wrapper supports request handler advice handling as well as emitting the normal 'produced no reply' debug log messages.
+Its bean name is the handler bean name plus `.wrapper` (when there is an `@EndpointId`, otherwise it's the normal generated handler name).
+
+**Message Sources**
+
+Similarly <> create two beans, a `SourcePollingChannelAdapter` (SPCA) and a `MessageSource`.
+
+When configuring with XML:
+
+[source, xml]
+----
+
+----
+
+the bean names will be as follows:
+
+- SPCA: `someAdapter` (the `id`)
+- Handler: `someAdapter.source`
+
+Using `@EndpointId` with Java configuration:
+
+[source, java]
+----
+@EndpointId("someAdapter")
+@InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
+public String pojoSource() {
+ ...
+}
+----
+
+the bean names will be as follows:
+
+- SPCA: `someAdapter`
+- Handler: `someAdapter.source`
+
+[source, java]
+----
+@Bean("someAdapter.source")
+@EndpointId("someAdapter")
+@InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
+public MessageSource> source() {
+ return () -> {
+ ...
+ };
+}
+----
+
+the bean names will be as follows:
+
+- SPCA: `someAdapter`
+- Handler: `someAdapter.source` (as long as you use the convention of appending `.source` to the `@Bean` name)
+
+
[[configuration-enable-integration]]
=== Configuration and @EnableIntegration
diff --git a/src/reference/asciidoc/polling-consumer.adoc b/src/reference/asciidoc/polling-consumer.adoc
index 475c8ac46b..3b28964284 100644
--- a/src/reference/asciidoc/polling-consumer.adoc
+++ b/src/reference/asciidoc/polling-consumer.adoc
@@ -22,6 +22,7 @@ You can find a description of the pattern on the book's website at:
http://www.enterpriseintegrationpatterns.com/PollingConsumer.html[http://www.enterpriseintegrationpatterns.com/PollingConsumer.html]
+[[pollable-message-source]]
==== Pollable Message Source
Furthermore, in Spring Integration a second variation of the Polling Consumer pattern exists.
diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc
index 4d7fd77886..cece8b94fc 100644
--- a/src/reference/asciidoc/whats-new.adoc
+++ b/src/reference/asciidoc/whats-new.adoc
@@ -308,3 +308,9 @@ http://micrometer.io/[Micrometer] application monitoring is now supported (since
See <> for more information.
IMPORTANT: Changes were made to the Micrometer `Meters` in _version 5.0.3_ to make them more suitable for use in dimensional systems.
+
+
+==== @EndpointId Annotations
+
+Introduced in _version 5.0.4_, this annotation provides control over bean naming when using Java configuration.
+See <> for more information.