diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java index 1210052b275..60268191113 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregator.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -39,6 +40,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(Aggregators.class) public @interface Aggregator { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregators.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregators.java new file mode 100644 index 00000000000..1899d71bf78 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Aggregators.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link Aggregator} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Aggregators { + + Aggregator[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java index 325f05abc03..83a21561d0b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFrom.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -45,6 +46,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(BridgeFromRepeatable.class) public @interface BridgeFrom { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFromRepeatable.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFromRepeatable.java new file mode 100644 index 00000000000..71b13460938 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeFromRepeatable.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link BridgeFrom} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface BridgeFromRepeatable { + + BridgeFrom[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java index 836d47eb076..66c85f95f90 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeTo.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -50,6 +51,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(BridgeToRepeatable.class) public @interface BridgeTo { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeToRepeatable.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeToRepeatable.java new file mode 100644 index 00000000000..43db2e971b9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/BridgeToRepeatable.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link BridgeTo} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface BridgeToRepeatable { + + BridgeTo[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java index 6d3696bd825..248ce12f139 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filter.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -47,6 +48,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(Filters.class) public @interface Filter { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filters.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filters.java new file mode 100644 index 00000000000..c4a091471d3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Filters.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link Filter} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Filters { + + Filter[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java index 6c8e2d0d9b2..54b5a5be3b0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapter.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -53,6 +54,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(InboundChannelAdapters.class) public @interface InboundChannelAdapter { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapters.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapters.java new file mode 100644 index 00000000000..e21f26b8961 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/InboundChannelAdapters.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link InboundChannelAdapter} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface InboundChannelAdapters { + + InboundChannelAdapter[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java index db551375647..d1d62030d80 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -50,6 +51,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(Routers.class) public @interface Router { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Routers.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Routers.java new file mode 100644 index 00000000000..2a1583ca440 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Routers.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link Router} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Routers { + + Router[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java index 7d64d8e25e7..ea7755d59f6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivator.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -48,6 +49,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(ServiceActivators.class) public @interface ServiceActivator { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivators.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivators.java new file mode 100644 index 00000000000..50526a44a89 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/ServiceActivators.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link ServiceActivator} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ServiceActivators { + + ServiceActivator[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java index 09f1be27c83..8a4d12e908f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -48,6 +49,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(Splitters.class) public @interface Splitter { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitters.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitters.java new file mode 100644 index 00000000000..a81227e0c80 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitters.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link Splitter} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Splitters { + + Splitter[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java index 2277febb07a..2898a5dee2f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformer.java @@ -18,6 +18,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -36,6 +37,7 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(Transformers.class) public @interface Transformer { /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformers.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformers.java new file mode 100644 index 00000000000..caa0d51d9c6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Transformers.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.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; + +/** + * The repeatable container for {@link Transformer} annotations. + * + * @author Artem Bilan + * + * @since 6.0 + */ +@Documented +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Transformers { + + Transformer[] value(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java index 414bc0ba86b..f9f7261e123 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -25,7 +25,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -42,13 +41,13 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.integration.annotation.Aggregator; import org.springframework.integration.annotation.BridgeFrom; import org.springframework.integration.annotation.BridgeTo; +import org.springframework.integration.annotation.EndpointId; import org.springframework.integration.annotation.Filter; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.annotation.Role; @@ -161,28 +160,34 @@ public Object postProcessAfterInitialization(final Object bean, final String bea } private void doWithMethod(Method method, Object bean, String beanName, Class beanClass) { - Map, List> annotationChains = new HashMap<>(); - for (Class annotationType : - this.postProcessors.keySet()) { - if (AnnotatedElementUtils.isAnnotated(method, annotationType.getName())) { - List annotationChain = getAnnotationChain(method, annotationType); - if (annotationChain.size() > 0) { - annotationChains.put(annotationType, annotationChain); - } - } + MergedAnnotations mergedAnnotations = + MergedAnnotations.search(MergedAnnotations.SearchStrategy.DIRECT) + .from(method); + + List messagingAnnotations = new ArrayList<>(); + + for (Class annotationType : this.postProcessors.keySet()) { + mergedAnnotations.stream() + .filter((ann) -> ann.getType().equals(annotationType)) + .map(MergedAnnotation::getRoot) + .map(MergedAnnotation::synthesize) + .map((ann) -> new MessagingMetaAnnotation(ann, annotationType)) + .forEach(messagingAnnotations::add); } - if (StringUtils.hasText(MessagingAnnotationUtils.endpointIdValue(method)) - && annotationChains.keySet().size() > 1) { + if (mergedAnnotations.get(EndpointId.class, (ann) -> ann.hasNonDefaultValue("value")).isPresent() + && messagingAnnotations.size() > 1) { + throw new IllegalStateException("@EndpointId on " + method.toGenericString() - + " can only have one EIP annotation, found: " + annotationChains.keySet().size()); + + " can only have one EIP annotation, found: " + messagingAnnotations.size()); } - for (Entry, List> entry : annotationChains.entrySet()) { - Class annotationType = entry.getKey(); - List annotations = entry.getValue(); - processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotations); + + for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) { + Class annotationType = messagingAnnotation.messagingAnnotationType; + List annotationChain = getAnnotationChain(messagingAnnotation.annotation, annotationType); + processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain); } - if (annotationChains.size() == 0) { + if (messagingAnnotations.size() == 0) { this.noAnnotationsCache.add(beanClass); } } @@ -226,10 +231,8 @@ private void postProcessMethodAndRegisterEndpointIfAny(Object bean, String beanN Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); BeanDefinitionRegistry definitionRegistry = (BeanDefinitionRegistry) beanFactory; - if (result instanceof AbstractEndpoint) { - AbstractEndpoint endpoint = (AbstractEndpoint) result; - String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup", - String.class); + if (result instanceof AbstractEndpoint endpoint) { + String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup", String.class); if (StringUtils.hasText(autoStartup)) { autoStartup = beanFactory.resolveEmbeddedValue(autoStartup); if (StringUtils.hasText(autoStartup)) { @@ -259,20 +262,19 @@ private void postProcessMethodAndRegisterEndpointIfAny(Object bean, String beanN } /** - * @param method the method. + * @param messagingAnnotation the {@link Annotation} to take a chain for its meta-annotations. * @param annotationType the annotation type. * @return the hierarchical list of annotations in top-bottom order. */ - protected List getAnnotationChain(Method method, Class annotationType) { + protected List getAnnotationChain(Annotation messagingAnnotation, + Class annotationType) { + List annotationChain = new LinkedList<>(); Set visited = new HashSet<>(); - for (MergedAnnotation mergedAnnotation : MergedAnnotations.from(method)) { - recursiveFindAnnotation(annotationType, mergedAnnotation.synthesize(), annotationChain, visited); - if (annotationChain.size() > 0) { - Collections.reverse(annotationChain); - return annotationChain; - } + recursiveFindAnnotation(annotationType, messagingAnnotation, annotationChain, visited); + if (annotationChain.size() > 0) { + Collections.reverse(annotationChain); } return annotationChain; @@ -280,6 +282,7 @@ protected List getAnnotationChain(Method method, Class annotationType, Annotation ann, List annotationChain, Set visited) { + if (ann.annotationType().equals(annotationType)) { annotationChain.add(ann); return true; @@ -299,6 +302,7 @@ protected boolean recursiveFindAnnotation(Class annotation protected String generateBeanName(String originalBeanName, Method method, Class annotationType) { + String name = MessagingAnnotationUtils.endpointIdValue(method); if (!StringUtils.hasText(name)) { String baseName = originalBeanName + "." + method.getName() + "." @@ -316,4 +320,8 @@ protected Map, MethodAnnotationPostProcessor> get return this.postProcessors; } + private record MessagingMetaAnnotation(Annotation annotation, Class messagingAnnotationType) { + + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java index a0e52359b9d..7a48b4b6c28 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java @@ -298,6 +298,12 @@ public void testTransformer() { inputChannel.send(new GenericMessage<>("foo")); Message reply = outputChannel.receive(0); assertThat(reply.getPayload()).isEqualTo("FOO"); + + MessageChannel inputChannel2 = context.getBean("inputChannel2", MessageChannel.class); + inputChannel2.send(new GenericMessage<>("test2")); + reply = outputChannel.receive(0); + assertThat(reply.getPayload()).isEqualTo("TEST2"); + context.close(); } @@ -386,6 +392,7 @@ public Boolean getInvoked() { public static class TransformerAnnotationTestBean { @Transformer(inputChannel = "inputChannel", outputChannel = "outputChannel") + @Transformer(inputChannel = "inputChannel2", outputChannel = "outputChannel") public String transformBefore(String input) { return input.toUpperCase(); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java index d77a4f2eb51..56effb430a9 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. @@ -385,9 +385,9 @@ public MessageHandler service() { @Bean @ServiceActivator(inputChannel = "skippedChannel") - @Splitter(inputChannel = "skippedChannel2") + @ServiceActivator(inputChannel = "skippedChannel2") @Router(inputChannel = "skippedChannel3") - @Transformer(inputChannel = "skippedChannel4") + @Filter(inputChannel = "skippedChannel4") @Filter(inputChannel = "skippedChannel5") @Profile("foo") public MessageHandler skippedMessageHandler() { @@ -396,6 +396,7 @@ public MessageHandler skippedMessageHandler() { } @Bean + @BridgeFrom("skippedChannel5") @BridgeFrom("skippedChannel6") @Profile("foo") public MessageChannel skippedChannel1() { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java index 4d24c8aa0ec..072c3c061fa 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java @@ -250,6 +250,9 @@ public class EnableIntegrationTests { @Autowired private PollableChannel messageChannel; + @Autowired + private PollableChannel messageChannel2; + @Autowired private PollableChannel fooChannel; @@ -432,6 +435,12 @@ public void testAnnotatedServiceActivator() throws Exception { assertThat(message.getHeaders().containsKey("foo")).isTrue(); assertThat(message.getHeaders().get("foo")).isEqualTo("FOO"); + message = this.messageChannel2.receive(10_000); + assertThat(message).isNotNull(); + assertThat(message.getPayload()).isEqualTo("bar"); + assertThat(message.getHeaders().containsKey("foo")).isTrue(); + assertThat(message.getHeaders().get("foo")).isEqualTo("FOO"); + MessagingTemplate messagingTemplate = new MessagingTemplate(this.controlBusChannel); assertThat(messagingTemplate.convertSendAndReceive("@pausable.isRunning()", Boolean.class)).isEqualTo(false); this.controlBusChannel.send(new GenericMessage<>("@pausable.start()")); @@ -1162,6 +1171,11 @@ public PollableChannel messageChannel() { return new QueueChannel(); } + @Bean + public PollableChannel messageChannel2() { + return new QueueChannel(); + } + @Bean public QueueChannel numberChannel() { QueueChannel channel = new QueueChannel(); @@ -1354,6 +1368,7 @@ public String foo() { @InboundChannelAdapter(value = "messageChannel", poller = @Poller(fixedDelay = "${poller.interval}", maxMessagesPerPoll = "1")) + @InboundChannelAdapter(value = "messageChannel2", poller = @Poller(fixedDelay = "100")) public Message message() { return MessageBuilder.withPayload("bar").setHeader("foo", "FOO").build(); } diff --git a/src/reference/asciidoc/configuration.adoc b/src/reference/asciidoc/configuration.adoc index 7785830b0bb..b428024050c 100644 --- a/src/reference/asciidoc/configuration.adoc +++ b/src/reference/asciidoc/configuration.adoc @@ -301,7 +301,7 @@ public class ThingService { ---- ==== -You can also use the `@Headers` annotation to provide all of the message headers as a `Map`, as the following example shows: +You can also use the `@Headers` annotation to provide all the message headers as a `Map`, as the following example shows: ==== [source,java] @@ -354,7 +354,7 @@ The `MessageHandler` instances (`MessageSource` instances) are also eligible to Starting with version 4.0, all messaging annotations provide `SmartLifecycle` options (`autoStartup` and `phase`) to allow endpoint lifecycle control on application context initialization. They default to `true` and `0`, respectively. -To change the state of an endpoint (such as ` start()` or `stop()`), you can obtain a reference to the endpoint bean by using the `BeanFactory` (or autowiring) and invoke the methods. +To change the state of an endpoint (such as `start()` or `stop()`), you can obtain a reference to the endpoint bean by using the `BeanFactory` (or autowiring) and invoke the methods. Alternatively, you can send a command message to the `Control Bus` (see <<./control-bus.adoc#control-bus,Control Bus>>). For these purposes, you should use the `beanName` mentioned earlier in the preceding paragraph. @@ -380,6 +380,18 @@ Thing1 dependsOnSPCA(@Qualifier("someInboundAdapter") @Lazy SourcePollingChannel ==== ===== +Starting with version 6.0, all the messaging annotations are `@Repeatable` now, so several of the same type can be declared on the same service method with the meaning to create as many endpoints as those annotations are repeated: +==== +[source, java] +---- +@Transformer(inputChannel = "inputChannel1", outputChannel = "outputChannel1") +@Transformer(inputChannel = "inputChannel2", outputChannel = "outputChannel2") +public String transform(String input) { + return input.toUpperCase(); +} +---- +==== + [[configuration-using-poller-annotation]] ==== Using the `@Poller` Annotation diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 46b678cd256..54b7ec8088d 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -26,8 +26,10 @@ See <<./graphql.adoc#graphql,GraphQL Support>> for more information. [[x6.0-general]] === General Changes -The messaging annotations don't require a poller attribute as an array of @Poller any more. -See <<./configuration.adoc#configuration-using-poller-annotation,Using the @Poller Annotation>> for more information. +The messaging annotations are now `@Repeatable` and the same type can be declared several times on the same service method. +The messaging annotations don't require a `poller` attribute as an array of `@Poller` anymore. + +See <<./configuration.adoc#annotations,Annotation Support>> for more information. [[x6.0-http]] === HTTP Changes