From ca138c0c061c12ab5fef516fb97c1ade2744ba5f Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 22 Aug 2022 12:38:23 -0400 Subject: [PATCH] GH-3844: Rework messaging annotation with @Bean (#3877) * GH-3844: Rework messaging annotation with @Bean Fixes https://github.com/spring-projects/spring-integration/issues/3844 * Make `MessagingAnnotationPostProcessor` as a `BeanDefinitionRegistryPostProcessor` to process bean definitions as early as possible and register respective messaging components at that early phase * Make bean definitions parsing logic optional for AOT and native mode since beans have bean parsed during AOT building phase * Introduce a `BeanDefinitionPropertiesMapper` for easier mapping of the annotation attributes to the target `BeanDefinition` * Remove `@Bean`-related logic from method parsing process * Change the logic for `@Bean`-based endpoint bean names: since we don't deal with methods on the bean definition phase, then method name does not make sense. It even may mislead if we `@Bean` name is based on a method by default, so we end up with duplicated word in the target endpoint bean name. Now we don't * Fix `configuration.adoc` respectively for a new endpoint bean name logic * In the end the new logic in the `AbstractMethodAnnotationPostProcessor` is similar to XML parsers: we feed annotation attributes to the `AbstractStandardMessageHandlerFactoryBean` impls * * Fix language in docs and exception message --- .../AmqpMessageSourceIntegrationTests.java | 45 +- ...ntReceiverAutoProxyCreatorInitializer.java | 13 +- .../integration/config/RouterFactoryBean.java | 52 +- ...AbstractMethodAnnotationPostProcessor.java | 459 +++++++++++++----- .../AggregatorAnnotationPostProcessor.java | 11 +- .../BridgeFromAnnotationPostProcessor.java | 68 ++- .../BridgeToAnnotationPostProcessor.java | 69 +-- .../FilterAnnotationPostProcessor.java | 65 ++- ...ChannelAdapterAnnotationPostProcessor.java | 130 ++--- .../MessagingAnnotationPostProcessor.java | 241 +++++---- .../MethodAnnotationPostProcessor.java | 33 +- .../RouterAnnotationPostProcessor.java | 101 ++-- ...rviceActivatorAnnotationPostProcessor.java | 80 ++- .../SplitterAnnotationPostProcessor.java | 69 +-- .../TransformerAnnotationPostProcessor.java | 59 +-- .../util/MessagingAnnotationUtils.java | 72 ++- .../bus/DirectChannelSubscriptionTests.java | 31 +- .../CustomMessagingAnnotationTests.java | 16 +- .../FilterAnnotationPostProcessorTests.java | 27 +- ...MessagingAnnotationPostProcessorTests.java | 18 +- ...ingAnnotationsWithBeanAnnotationTests.java | 9 +- .../RouterAnnotationPostProcessorTests.java | 27 +- .../SplitterAnnotationPostProcessorTests.java | 27 +- .../configuration/EnableIntegrationTests.java | 14 +- .../integration/endpoint/BeanNameTests.java | 18 +- .../graph/IntegrationGraphServerTests.java | 8 +- .../micrometer/MicrometerMetricsTests.java | 9 +- ...castOutboundChannelAdapterConfigTests.java | 17 +- .../jdbc/StoredProcJavaConfigTests.java | 4 +- .../integration/jms/dsl/JmsTests.java | 36 +- .../ScatterGatherHandlerIntegrationTests.java | 112 +---- .../test/mock/MockMessageSourceTests.java | 4 +- src/reference/asciidoc/configuration.adoc | 11 +- 33 files changed, 1136 insertions(+), 819 deletions(-) diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java index b59ef85fba..f58614bcc2 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/inbound/AmqpMessageSourceIntegrationTests.java @@ -22,12 +22,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.aopalliance.intercept.MethodInterceptor; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Queue; @@ -36,7 +33,7 @@ import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.junit.BrokerRunning; +import org.springframework.amqp.rabbit.junit.RabbitAvailable; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +56,7 @@ import org.springframework.integration.support.AbstractIntegrationMessageBuilder import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.support.MessageBuilder; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** * @author Gary Russell @@ -67,23 +64,24 @@ import org.springframework.test.context.junit4.SpringRunner; * @since 5.0.1 * */ -@RunWith(SpringRunner.class) +@SpringJUnitConfig @DirtiesContext +@RabbitAvailable({ + AmqpMessageSourceIntegrationTests.DSL_QUEUE, + AmqpMessageSourceIntegrationTests.INTERCEPT_QUEUE, + AmqpMessageSourceIntegrationTests.DLQ, + AmqpMessageSourceIntegrationTests.NOAUTOACK_QUEUE }) public class AmqpMessageSourceIntegrationTests { - private static final String DSL_QUEUE = "AmqpMessageSourceIntegrationTests"; + static final String DSL_QUEUE = "AmqpMessageSourceIntegrationTests"; - private static final String QUEUE_WITH_DLQ = "AmqpMessageSourceIntegrationTests.withDLQ"; + static final String QUEUE_WITH_DLQ = "AmqpMessageSourceIntegrationTests.withDLQ"; - private static final String DLQ = QUEUE_WITH_DLQ + ".dlq"; + static final String DLQ = QUEUE_WITH_DLQ + ".dlq"; - private static final String INTERCEPT_QUEUE = "AmqpMessageSourceIntegrationTests.channel"; + static final String INTERCEPT_QUEUE = "AmqpMessageSourceIntegrationTests.channel"; - private static final String NOAUTOACK_QUEUE = "AmqpMessageSourceIntegrationTests.noAutoAck"; - - @ClassRule - public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues(DSL_QUEUE, INTERCEPT_QUEUE, DLQ, - NOAUTOACK_QUEUE); + static final String NOAUTOACK_QUEUE = "AmqpMessageSourceIntegrationTests.noAutoAck"; @Autowired private Config config; @@ -91,7 +89,7 @@ public class AmqpMessageSourceIntegrationTests { @Autowired private ConfigurableApplicationContext context; - @Before + @BeforeEach public void before() { RabbitAdmin admin = new RabbitAdmin(this.config.connectionFactory()); Queue queue = QueueBuilder.nonDurable(QUEUE_WITH_DLQ) @@ -103,16 +101,11 @@ public class AmqpMessageSourceIntegrationTests { this.context.start(); } - @After + @AfterEach public void after() { this.context.stop(); } - @AfterClass - public static void afterClass() { - brokerRunning.removeTestQueues(QUEUE_WITH_DLQ); - } - @Test public void testImplicitNackThenAck() throws Exception { RabbitTemplate template = new RabbitTemplate(this.config.connectionFactory()); @@ -256,7 +249,7 @@ public class AmqpMessageSourceIntegrationTests { @Bean public ConnectionFactory connectionFactory() { - return new CachingConnectionFactory(brokerRunning.getConnectionFactory()); + return new CachingConnectionFactory("localhost"); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.java index 14554be548..f6d5fd5054 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IdempotentReceiverAutoProxyCreatorInitializer.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. @@ -95,8 +95,7 @@ public class IdempotentReceiverAutoProxyCreatorInitializer implements Integratio List> idempotentEndpointsMapping, String beanName, BeanDefinition beanDefinition) throws LinkageError { - if (beanDefinition.getSource() instanceof MethodMetadata) { - MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource(); + if (beanDefinition.getSource() instanceof MethodMetadata beanMethod) { String annotationType = IdempotentReceiver.class.getName(); if (beanMethod.isAnnotated(annotationType)) { // NOSONAR never null Object value = beanMethod.getAnnotationAttributes(annotationType).get("value"); // NOSONAR @@ -120,13 +119,7 @@ public class IdempotentReceiverAutoProxyCreatorInitializer implements Integratio String endpoint = beanName; if (!MessageHandler.class.isAssignableFrom(returnType)) { - /* - MessageHandler beans, populated from @Bean methods, have a complex id, - including @Configuration bean name, method name and the Messaging annotation name. - The following pattern matches the bean name, regardless of the annotation name. - */ - endpoint = beanDefinition.getFactoryBeanName() + "." + beanName + - ".*" + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX; + endpoint = beanName + ".*" + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX; } String[] interceptors = (String[]) value; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java index 3ae23dede5..41fe20ca66 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -19,6 +19,7 @@ package org.springframework.integration.config; import java.util.Map; import org.springframework.expression.Expression; +import org.springframework.integration.JavaUtils; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.handler.AbstractMessageProducingHandler; import org.springframework.integration.router.AbstractMappingMessageRouter; @@ -49,6 +50,10 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean private String defaultOutputChannelName; + private String prefix; + + private String suffix; + private Boolean resolutionRequired; private Boolean applySequence; @@ -63,6 +68,14 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean this.defaultOutputChannelName = defaultOutputChannelName; } + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + public void setResolutionRequired(Boolean resolutionRequired) { this.resolutionRequired = resolutionRequired; } @@ -106,7 +119,7 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean @Override protected MessageHandler createExpressionEvaluatingHandler(Expression expression) { - return this.configureRouter(new ExpressionEvaluatingRouter(expression)); + return configureRouter(new ExpressionEvaluatingRouter(expression)); } protected AbstractMappingMessageRouter createMethodInvokingRouter(Object targetObject, String targetMethodName) { @@ -116,34 +129,25 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean } protected AbstractMessageRouter configureRouter(AbstractMessageRouter router) { - if (this.defaultOutputChannel != null) { - router.setDefaultOutputChannel(this.defaultOutputChannel); - } - if (this.defaultOutputChannelName != null) { - router.setDefaultOutputChannelName(this.defaultOutputChannelName); - } - if (getSendTimeout() != null) { - router.setSendTimeout(getSendTimeout()); - } - if (this.applySequence != null) { - router.setApplySequence(this.applySequence); - } - if (this.ignoreSendFailures != null) { - router.setIgnoreSendFailures(this.ignoreSendFailures); - } + JavaUtils.INSTANCE + .acceptIfNotNull(this.defaultOutputChannel, router::setDefaultOutputChannel) + .acceptIfNotNull(this.defaultOutputChannelName, router::setDefaultOutputChannelName) + .acceptIfNotNull(getSendTimeout(), router::setSendTimeout) + .acceptIfNotNull(this.applySequence, router::setApplySequence) + .acceptIfNotNull(this.ignoreSendFailures, router::setIgnoreSendFailures); + if (router instanceof AbstractMappingMessageRouter) { - this.configureMappingRouter((AbstractMappingMessageRouter) router); + configureMappingRouter((AbstractMappingMessageRouter) router); } return router; } protected void configureMappingRouter(AbstractMappingMessageRouter router) { - if (this.channelMappings != null) { - router.setChannelMappings(this.channelMappings); - } - if (this.resolutionRequired != null) { - router.setResolutionRequired(this.resolutionRequired); - } + JavaUtils.INSTANCE + .acceptIfNotNull(this.channelMappings, router::setChannelMappings) + .acceptIfNotNull(this.resolutionRequired, router::setResolutionRequired) + .acceptIfHasText(this.prefix, router::setPrefix) + .acceptIfHasText(this.suffix, router::setSuffix); } @Override 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 e699f50c08..f9bb263020 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 @@ -20,43 +20,63 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; import org.aopalliance.aop.Advice; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; -import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; import org.springframework.aop.support.NameMatchMethodPointcut; import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.ComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionValidationException; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.core.GenericTypeResolver; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.Order; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.task.TaskExecutor; +import org.springframework.core.type.MethodMetadata; +import org.springframework.core.type.StandardMethodMetadata; import org.springframework.integration.annotation.IdempotentReceiver; import org.springframework.integration.annotation.Poller; import org.springframework.integration.annotation.Reactive; +import org.springframework.integration.annotation.Role; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.MessagePublishingErrorHandler; +import org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; import org.springframework.integration.config.IntegrationConfigUtils; +import org.springframework.integration.config.RouterFactoryBean; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.context.Orderable; import org.springframework.integration.endpoint.AbstractEndpoint; @@ -68,7 +88,6 @@ import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.handler.AbstractMessageProducingHandler; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.LambdaMessageProcessor; -import org.springframework.integration.handler.MessageProcessor; import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper; import org.springframework.integration.handler.advice.HandleMessageAdvice; @@ -106,67 +125,300 @@ import reactor.core.publisher.Flux; * @author Chris Bono */ public abstract class AbstractMethodAnnotationPostProcessor - implements MethodAnnotationPostProcessor { + implements MethodAnnotationPostProcessor, BeanFactoryAware { private static final String UNCHECKED = "unchecked"; - private static final String INPUT_CHANNEL_ATTRIBUTE = "inputChannel"; - - private static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain"; - - protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout"; - protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR + protected static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain"; // NOSONAR + + protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout"; // NOSONAR + protected final List messageHandlerAttributes = new ArrayList<>(); // NOSONAR - protected final ConfigurableListableBeanFactory beanFactory; // NOSONAR - - protected final BeanDefinitionRegistry definitionRegistry; // NOSONAR - - protected final ConversionService conversionService; // NOSONAR - - protected final DestinationResolver channelResolver; // NOSONAR - protected final Class annotationType; // NOSONAR + private ConfigurableListableBeanFactory beanFactory; + + private BeanDefinitionRegistry definitionRegistry; + + private ConversionService conversionService; + + private DestinationResolver channelResolver; + @SuppressWarnings(UNCHECKED) - public AbstractMethodAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - Assert.notNull(beanFactory, "'beanFactory' must not be null"); + public AbstractMethodAnnotationPostProcessor() { this.messageHandlerAttributes.add(SEND_TIMEOUT_ATTRIBUTE); - this.beanFactory = beanFactory; + this.annotationType = + (Class) GenericTypeResolver.resolveTypeArgument(getClass(), MethodAnnotationPostProcessor.class); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.definitionRegistry = (BeanDefinitionRegistry) beanFactory; this.conversionService = this.beanFactory.getConversionService() != null ? this.beanFactory.getConversionService() : DefaultConversionService.getSharedInstance(); this.channelResolver = ChannelResolverUtils.getChannelResolver(beanFactory); - this.annotationType = - (Class) GenericTypeResolver.resolveTypeArgument(this.getClass(), - MethodAnnotationPostProcessor.class); + } + + protected ConfigurableListableBeanFactory getBeanFactory() { + return this.beanFactory; + } + + protected BeanDefinitionRegistry getDefinitionRegistry() { + return this.definitionRegistry; + } + + protected ConversionService getConversionService() { + return this.conversionService; + } + + protected DestinationResolver getChannelResolver() { + return this.channelResolver; + } + + @Override + public void processBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + List annotations) { + + ResolvableType handlerBeanType = getHandlerBeanClass(beanDefinition); + + String handlerBeanName = beanName; + + BeanDefinition handlerBeanDefinition = + resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); + MergedAnnotations mergedAnnotations = beanDefinition.getFactoryMethodMetadata().getAnnotations(); + + if (handlerBeanDefinition != null) { + if (handlerBeanDefinition != beanDefinition) { + Class handlerBeanClass = + org.springframework.util.ClassUtils.resolveClassName(handlerBeanDefinition.getBeanClassName(), + this.beanFactory.getBeanClassLoader()); + + if (isClassIn(handlerBeanClass, Orderable.class, AbstractSimpleMessageHandlerFactoryBean.class)) { + mergedAnnotations.get(Order.class) + .getValue(AnnotationUtils.VALUE, String.class). + ifPresent((order) -> handlerBeanDefinition.getPropertyValues().add("order", order)); + } + + if (isClassIn(handlerBeanClass, AbstractMessageProducingHandler.class, AbstractMessageRouter.class, + AbstractSimpleMessageHandlerFactoryBean.class)) { + + new BeanDefinitionPropertiesMapper(handlerBeanDefinition, annotations) + .setPropertyValue(SEND_TIMEOUT_ATTRIBUTE) + .setPropertyReference("outputChannel") + .setPropertyReference("defaultOutputChannel"); + } + + if (isClassIn(handlerBeanClass, AbstractMessageProducingHandler.class, + AbstractSimpleMessageHandlerFactoryBean.class) + && !RouterFactoryBean.class.isAssignableFrom(handlerBeanClass)) { + + applyAdviceChainIfAny(handlerBeanDefinition, annotations); + } + + handlerBeanName = generateHandlerBeanName(beanName, mergedAnnotations); + this.definitionRegistry.registerBeanDefinition(handlerBeanName, handlerBeanDefinition); + } + } + else { + throw new BeanDefinitionValidationException( + "The messaging annotation processor for '" + this.annotationType + + "' cannot create a target handler bean. " + + "The bean definition with the problem is: " + beanName); + } + + BeanDefinition endpointBeanDefinition = + createEndpointBeanDefinition(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName), + new BeanComponentDefinition(beanDefinition, beanName), annotations); + + new BeanDefinitionPropertiesMapper(endpointBeanDefinition, annotations) + .setPropertyValue("autoStartup") + .setPropertyValue("phase"); + + Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class); + Reactive reactive = MessagingAnnotationUtils.resolveAttribute(annotations, "reactive", Reactive.class); + Assert.state(reactive == null || poller == null, + () -> "The 'poller' and 'reactive' attributes are mutually exclusive." + + "The bean definition with the problem is: " + beanName); + if (poller != null) { + applyPollerForEndpoint(endpointBeanDefinition, poller); + } + else if (reactive != null) { + BeanMetadataElement reactiveCustomizer; + String reactiveCustomizerBean = reactive.value(); + if (StringUtils.hasText(reactiveCustomizerBean)) { + reactiveCustomizer = new RuntimeBeanReference(reactiveCustomizerBean); + } + else { + reactiveCustomizer = + BeanDefinitionBuilder.genericBeanDefinition(Function.class) + .setFactoryMethod("identity") + .getBeanDefinition(); + } + endpointBeanDefinition.getPropertyValues().add("reactiveCustomizer", reactiveCustomizer); + } + + mergedAnnotations.get(Role.class) + .getValue(AnnotationUtils.VALUE, String.class). + ifPresent((role) -> endpointBeanDefinition.getPropertyValues().add("role", role)); + + String endpointBeanName = + generateHandlerBeanName(beanName, mergedAnnotations) + .replaceFirst("\\.(handler|source)$", ""); + this.definitionRegistry.registerBeanDefinition(endpointBeanName, endpointBeanDefinition); + } + + private static boolean isClassIn(Class classToVerify, Class... classesToMatch) { + for (Class toMatch : classesToMatch) { + if (toMatch.isAssignableFrom(classToVerify)) { + return true; + } + } + return false; + } + + protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition, + ComponentDefinition beanDefinition, List annotations) { + + BeanDefinitionBuilder endpointDefinitionBuilder = + BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class) + .addPropertyReference("handler", handlerBeanDefinition.getName()); + + String inputChannelAttribute = getInputChannelAttribute(); + String inputChannelName = + MessagingAnnotationUtils.resolveAttribute(annotations, inputChannelAttribute, String.class); + Assert.hasText(inputChannelName, + () -> "The '" + inputChannelAttribute + "' attribute is required on '" + this.annotationType + + "' on @Bean method"); + if (!this.definitionRegistry.containsBeanDefinition(inputChannelName)) { + this.definitionRegistry.registerBeanDefinition(inputChannelName, + new RootBeanDefinition(DirectChannel.class)); + } + BeanDefinition endpointBeanDefinition = + endpointDefinitionBuilder.addPropertyReference("inputChannel", inputChannelName) + .getBeanDefinition(); + + applyAdviceChainIfAny(endpointBeanDefinition, annotations); + + return endpointBeanDefinition; + } + + private static void applyAdviceChainIfAny(BeanDefinition beanDefinition, List annotations) { + String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE, + String[].class); + + if (!ObjectUtils.isEmpty(adviceChainNames)) { + ManagedList adviceChain = + Arrays.stream(adviceChainNames) + .map(RuntimeBeanReference::new) + .collect(Collectors.toCollection(ManagedList::new)); + + beanDefinition.getPropertyValues().addPropertyValue("adviceChain", adviceChain); + } + } + + private static void applyPollerForEndpoint(BeanDefinition endpointBeanDefinition, Poller poller) { + BeanMetadataElement pollerMetadataBeanDefinition; + String ref = poller.value(); + if (StringUtils.hasText(ref)) { + pollerMetadataBeanDefinition = new RuntimeBeanReference(ref); + } + else { + BeanDefinitionBuilder pollerBeanDefinitionBuilder = + BeanDefinitionBuilder.genericBeanDefinition(PollerMetadata.class); + + new BeanDefinitionPropertiesMapper(pollerBeanDefinitionBuilder.getRawBeanDefinition(), List.of(poller)) + .setPropertyReference("trigger") + .setPropertyReference("taskExecutor") + .setPropertyValue("receiveTimeout") + .setPropertyValue("maxMessagesPerPoll"); + + String errorChannel = poller.errorChannel(); + if (StringUtils.hasText(errorChannel)) { + BeanDefinitionBuilder errorHandler = + BeanDefinitionBuilder.genericBeanDefinition(MessagePublishingErrorHandler.class) + .addPropertyReference("defaultErrorChannel", errorChannel); + pollerBeanDefinitionBuilder.addPropertyValue("errorHandler", errorHandler.getBeanDefinition()); + } + + String fixedDelay = poller.fixedDelay(); + String fixedRate = poller.fixedRate(); + String cron = poller.cron(); + BeanDefinition triggerBeanDefinition = null; + + if (StringUtils.hasText(cron)) { + triggerBeanDefinition = triggerBeanDefinition(CronTrigger.class, cron); + } + else if (StringUtils.hasText(fixedDelay)) { + triggerBeanDefinition = triggerBeanDefinition(PeriodicTrigger.class, fixedDelay); + } + else if (StringUtils.hasText(fixedRate)) { + triggerBeanDefinition = triggerBeanDefinition(PeriodicTrigger.class, fixedRate); + triggerBeanDefinition.getPropertyValues().addPropertyValue("fixedRate", true); + } + + if (triggerBeanDefinition != null) { + pollerBeanDefinitionBuilder.addPropertyValue("trigger", triggerBeanDefinition); + } + + pollerMetadataBeanDefinition = pollerBeanDefinitionBuilder.getBeanDefinition(); + } + + endpointBeanDefinition.getPropertyValues() + .addPropertyValue("pollerMetadata", pollerMetadataBeanDefinition); + } + + private static BeanDefinition triggerBeanDefinition(Class triggerClass, String triggerValue) { + return BeanDefinitionBuilder.genericBeanDefinition(triggerClass) + .addConstructorArgValue(triggerValue) + .getBeanDefinition(); + } + + private ResolvableType getHandlerBeanClass(AnnotatedBeanDefinition beanDefinition) { + MethodMetadata factoryMethodMetadata = beanDefinition.getFactoryMethodMetadata(); + if (factoryMethodMetadata instanceof StandardMethodMetadata standardMethodMetadata) { + return ResolvableType.forMethodReturnType(standardMethodMetadata.getIntrospectedMethod()); + } + else { + String typeName = factoryMethodMetadata.getReturnTypeName(); + Class beanClass = + org.springframework.util.ClassUtils.resolveClassName(typeName, + this.beanFactory.getBeanClassLoader()); + return ResolvableType.forClass(beanClass); + } + } + + @Nullable + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotations) { + + Class classToCheck = handlerBeanType.toClass(); + + if (FactoryBean.class.isAssignableFrom(classToCheck)) { + classToCheck = this.beanFactory.getType(beanName); + } + + if (isClassIn(classToCheck, AbstractReplyProducingMessageHandler.class, AbstractMessageRouter.class)) { + checkMessageHandlerAttributes(beanName, annotations); + return beanDefinition; + } + + return null; } @Override public Object postProcess(Object bean, String beanName, Method method, List annotations) { - Object sourceHandler = null; - if (beanAnnotationAware() && AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - if (!this.beanFactory.containsBeanDefinition(resolveTargetBeanName(method))) { - this.logger.debug("Skipping endpoint creation; perhaps due to some '@Conditional' annotation."); - return null; - } - else { - sourceHandler = resolveTargetBeanFromMethodWithBeanAnnotation(method); - } - } - MessageHandler handler = createHandler(bean, method, annotations); if (!(handler instanceof ReactiveMessageHandlerAdapter)) { orderable(method, handler); producerOrRouter(annotations, handler); - if (!handler.equals(sourceHandler)) { - handler = registerHandlerBean(beanName, method, handler); - } + handler = registerHandlerBean(beanName, method, handler); handler = annotated(method, handler); handler = adviceChain(beanName, annotations, handler); @@ -270,27 +522,6 @@ public abstract class AbstractMethodAnnotationPostProcessor annotations) { - String inputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), - String.class); - boolean createEndpoint = StringUtils.hasText(inputChannel); - if (!createEndpoint && beanAnnotationAware()) { - boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName()); - Assert.isTrue(!isBean, () -> "A channel name in '" + getInputChannelAttribute() + "' is required when " + - this.annotationType + " is used on '@Bean' methods."); - } - return createEndpoint; - } - - protected String getInputChannelAttribute() { - return INPUT_CHANNEL_ATTRIBUTE; - } - - protected boolean beanAnnotationAware() { - return true; - } - protected List extractAdviceChain(String beanName, List annotations) { List adviceChain = null; String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE, @@ -519,9 +750,19 @@ public abstract class AbstractMethodAnnotationPostProcessor annotations, - AbstractReplyProducingMessageHandler handler) { - String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", - String.class); - if (StringUtils.hasText(outputChannelName)) { - handler.setOutputChannelName(outputChannelName); - } - } + protected static void setOutputChannelIfPresent(List annotations, + AbstractMessageProducingHandler handler) { - protected Object resolveTargetBeanFromMethodWithBeanAnnotation(Method method) { - String id = resolveTargetBeanName(method); - return this.beanFactory.getBean(id); - } - - protected String resolveTargetBeanName(Method method) { - String id = method.getName(); - String[] names = AnnotationUtils.getAnnotation(method, Bean.class).name(); // NOSONAR never null - if (!ObjectUtils.isEmpty(names)) { - id = names[0]; + String outputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class); + if (StringUtils.hasText(outputChannel)) { + handler.setOutputChannelName(outputChannel); } - return id; - } - - @SuppressWarnings(UNCHECKED) - protected H extractTypeIfPossible(@Nullable Object targetObject, Class expectedType) { - if (targetObject == null) { - return null; - } - if (expectedType.isAssignableFrom(targetObject.getClass())) { - return (H) targetObject; - } - if (targetObject instanceof Advised) { - TargetSource targetSource = ((Advised) targetObject).getTargetSource(); - try { - return extractTypeIfPossible(targetSource.getTarget(), expectedType); - } - catch (Exception e) { - throw new IllegalStateException(e); - } - } - return null; } protected void checkMessageHandlerAttributes(String handlerBeanName, List annotations) { @@ -596,18 +803,23 @@ public abstract class AbstractMethodAnnotationPostProcessor buildLambdaMessageProcessorForBeanMethod(Method method, Object target) { - if ((target instanceof Function || target instanceof Consumer) && ClassUtils.isLambda(target.getClass()) - || ClassUtils.isKotlinFunction1(target.getClass())) { + protected static BeanDefinition buildLambdaMessageProcessor(ResolvableType beanType, + AnnotatedBeanDefinition beanDefinition) { - ResolvableType methodReturnType = ResolvableType.forMethodReturnType(method); - Class expectedPayloadType = methodReturnType.getGeneric(0).toClass(); - return new LambdaMessageProcessor(target, expectedPayloadType); - } - else { - return null; + Class beanClass = beanType.toClass(); + + if (Function.class.isAssignableFrom(beanClass) + || Consumer.class.isAssignableFrom(beanClass) + || ClassUtils.isKotlinFunction1(beanClass)) { + + Class expectedPayloadType = beanType.getGeneric(0).toClass(); + return BeanDefinitionBuilder.genericBeanDefinition(LambdaMessageProcessor.class) + .addConstructorArgValue(beanDefinition) + .addConstructorArgValue(expectedPayloadType) + .getBeanDefinition(); } + + return null; } private static void orderable(Method method, MessageHandler handler) { @@ -628,4 +840,33 @@ public abstract class AbstractMethodAnnotationPostProcessor annotations); + protected record BeanDefinitionPropertiesMapper(BeanDefinition beanDefinition, List annotations) { + + public BeanDefinitionPropertiesMapper setPropertyValue(String property) { + return setPropertyValue(property, null); + } + + public BeanDefinitionPropertiesMapper setPropertyValue(String property, @Nullable String propertyName) { + String value = MessagingAnnotationUtils.resolveAttribute(this.annotations, property, String.class); + if (StringUtils.hasText(value)) { + this.beanDefinition.getPropertyValues().add(propertyName != null ? propertyName : property, value); + } + return this; + } + + public BeanDefinitionPropertiesMapper setPropertyReference(String property) { + return setPropertyReference(property, null); + } + + public BeanDefinitionPropertiesMapper setPropertyReference(String property, @Nullable String propertyName) { + String value = MessagingAnnotationUtils.resolveAttribute(this.annotations, property, String.class); + if (StringUtils.hasText(value)) { + this.beanDefinition.getPropertyValues() + .add(propertyName != null ? propertyName : property, new RuntimeBeanReference(value)); + } + return this; + } + + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java index d5c0f0b50d..f2ff210f04 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -20,7 +20,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.integration.aggregator.AggregatingMessageHandler; import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy; import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor; @@ -43,11 +42,6 @@ import org.springframework.util.StringUtils; */ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public AggregatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); - } - - @Override protected MessageHandler createHandler(Object bean, Method method, List annotations) { MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method); @@ -86,7 +80,8 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP return handler; } - protected boolean beanAnnotationAware() { + @Override + public boolean beanAnnotationAware() { return false; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeFromAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeFromAnnotationPostProcessor.java index 85c12ab6f7..8e782e0f2a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeFromAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeFromAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 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. @@ -20,18 +20,17 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.integration.annotation.BridgeFrom; import org.springframework.integration.annotation.BridgeTo; import org.springframework.integration.handler.BridgeHandler; -import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Post-processor for the {@link BridgeFrom @BridgeFrom} annotation. @@ -42,46 +41,37 @@ import org.springframework.util.StringUtils; */ public class BridgeFromAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public BridgeFromAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); - } - @Override - public boolean shouldCreateEndpoint(Method method, List annotations) { - boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName()); - Assert.isTrue(isBean, "'@BridgeFrom' is eligible only for '@Bean' methods"); - - boolean isMessageChannelBean = MessageChannel.class.isAssignableFrom(method.getReturnType()); - Assert.isTrue(isMessageChannelBean, "'@BridgeFrom' is eligible only for 'MessageChannel' '@Bean' methods"); - - String channel = MessagingAnnotationUtils.resolveAttribute(annotations, "value", String.class); - Assert.isTrue(StringUtils.hasText(channel), "'@BridgeFrom.value()' (inputChannelName) must not be empty"); - - boolean hasBridgeTo = AnnotatedElementUtils.isAnnotated(method, BridgeTo.class.getName()); - - Assert.isTrue(!hasBridgeTo, "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' " + - "'@Bean' method annotations"); - - - return true; - } - - @Override - protected String getInputChannelAttribute() { + public String getInputChannelAttribute() { return AnnotationUtils.VALUE; } @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - BridgeHandler handler = new BridgeHandler(); - String outputChannelName = resolveTargetBeanName(method); - handler.setOutputChannelName(outputChannelName); - return handler; + public boolean supportsPojoMethod() { + return false; } @Override - protected Object resolveTargetBeanFromMethodWithBeanAnnotation(Method method) { - return null; + public boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List annotations) { + Assert.isTrue(super.shouldCreateEndpoint(mergedAnnotations, annotations), + "'@BridgeFrom.value()' (inputChannelName) must not be empty"); + Assert.isTrue(!mergedAnnotations.isPresent(BridgeTo.class), + "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' '@Bean' method annotations"); + return true; + } + + @Override + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotationChain) { + + return BeanDefinitionBuilder.genericBeanDefinition(BridgeHandler.class) + .addPropertyReference("outputChannel", beanName) + .getBeanDefinition(); + } + + @Override + protected MessageHandler createHandler(Object bean, Method method, List annotations) { + throw new UnsupportedOperationException(); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeToAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeToAnnotationPostProcessor.java index f140217777..f8238a2da8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeToAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/BridgeToAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 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. @@ -20,15 +20,20 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.ComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.integration.annotation.BridgeFrom; import org.springframework.integration.annotation.BridgeTo; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.handler.BridgeHandler; import org.springframework.integration.util.MessagingAnnotationUtils; -import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -37,45 +42,55 @@ import org.springframework.util.StringUtils; * Post-processor for the {@link BridgeTo @BridgeTo} annotation. * * @author Artem Bilan + * * @since 4.0 */ public class BridgeToAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public BridgeToAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + @Override + public boolean supportsPojoMethod() { + return false; } @Override - public boolean shouldCreateEndpoint(Method method, List annotations) { - boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName()); - Assert.isTrue(isBean, "'@BridgeTo' is eligible only for '@Bean' methods"); - - boolean isMessageChannelBean = MessageChannel.class.isAssignableFrom(method.getReturnType()); - Assert.isTrue(isMessageChannelBean, "'@BridgeTo' is eligible only for 'MessageChannel' '@Bean' methods"); - - boolean hasBridgeFrom = AnnotatedElementUtils.isAnnotated(method, BridgeFrom.class.getName()); - - Assert.isTrue(!hasBridgeFrom, "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' " + - "'@Bean' method annotations"); - + public boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List annotations) { + Assert.isTrue(!mergedAnnotations.isPresent(BridgeFrom.class), + "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' '@Bean' method annotations"); return true; } @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - BridgeHandler handler = new BridgeHandler(); - String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "value", String.class); + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotationChain) { + + GenericBeanDefinition bridgeHandlerBeanDefinition = new GenericBeanDefinition(); + bridgeHandlerBeanDefinition.setBeanClass(BridgeHandler.class); + String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotationChain, "value", String.class); if (StringUtils.hasText(outputChannelName)) { - handler.setOutputChannelName(outputChannelName); + bridgeHandlerBeanDefinition.getPropertyValues() + .addPropertyValue("outputChannel", new RuntimeBeanReference(outputChannelName)); } - return handler; + return bridgeHandlerBeanDefinition; + } + + @Override + protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition, + ComponentDefinition beanDefinition, List annotations) { + + return BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class) + .addPropertyReference("handler", handlerBeanDefinition.getName()) + .addPropertyReference("inputChannel", beanDefinition.getName()) + .getBeanDefinition(); } @Override protected AbstractEndpoint createEndpoint(MessageHandler handler, Method method, List annotations) { - Object inputChannel = this.resolveTargetBeanFromMethodWithBeanAnnotation(method); - Assert.isInstanceOf(MessageChannel.class, inputChannel); - return doCreateEndpoint(handler, (MessageChannel) inputChannel, annotations); + throw new UnsupportedOperationException(); + } + + @Override + protected MessageHandler createHandler(Object bean, Method method, List annotations) { + throw new UnsupportedOperationException(); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java index bc6bb89af8..f2006ac2bf 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -21,10 +21,14 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.ResolvableType; import org.springframework.integration.annotation.Filter; +import org.springframework.integration.config.FilterFactoryBean; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.filter.MessageFilter; import org.springframework.integration.filter.MethodInvokingSelector; @@ -43,34 +47,45 @@ import org.springframework.util.StringUtils; */ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public FilterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + public FilterAnnotationPostProcessor() { this.messageHandlerAttributes.addAll(Arrays.asList("discardChannel", "throwExceptionOnRejection", "adviceChain", "discardWithinAdvice")); } + @Override + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotations) { + + BeanDefinition handlerBeanDefinition = + super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); + + if (handlerBeanDefinition != null) { + return handlerBeanDefinition; + } + + BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); + if (targetObjectBeanDefinition == null) { + targetObjectBeanDefinition = new RuntimeBeanReference(beanName); + } + + BeanDefinition filterBeanDefinition = + BeanDefinitionBuilder.genericBeanDefinition(FilterFactoryBean.class) + .addPropertyValue("targetObject", targetObjectBeanDefinition) + .getBeanDefinition(); + + new BeanDefinitionPropertiesMapper(filterBeanDefinition, annotations) + .setPropertyValue("discardWithinAdvice") + .setPropertyValue("throwExceptionOnRejection") + .setPropertyReference("discardChannel"); + + return filterBeanDefinition; + } @Override protected MessageHandler createHandler(Object bean, Method method, List annotations) { - MessageSelector selector; - if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method); - if (target instanceof MessageSelector) { - selector = (MessageSelector) target; - } - else if (extractTypeIfPossible(target, MessageFilter.class) != null) { - checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations); - return (MessageHandler) target; - } - else { - selector = new MethodInvokingSelector(target); - } - } - else { - Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()), - "The Filter annotation may only be applied to methods with a boolean return type."); - selector = new MethodInvokingSelector(bean, method); - } + Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()), + "The Filter annotation may only be applied to methods with a boolean return type."); + MessageSelector selector = new MethodInvokingSelector(bean, method); MessageFilter filter = new MessageFilter(selector); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/InboundChannelAdapterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/InboundChannelAdapterAnnotationPostProcessor.java index 2b62c45ec5..5dc0e16ebf 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/InboundChannelAdapterAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/InboundChannelAdapterAnnotationPostProcessor.java @@ -21,20 +21,24 @@ import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.ComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.annotation.Poller; import org.springframework.integration.config.IntegrationConfigUtils; +import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; import org.springframework.integration.core.MessageSource; import org.springframework.integration.endpoint.MethodInvokingMessageSource; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.util.ClassUtils; import org.springframework.integration.util.MessagingAnnotationUtils; +import org.springframework.lang.Nullable; import org.springframework.messaging.MessageHandler; import org.springframework.util.Assert; @@ -51,14 +55,55 @@ import org.springframework.util.Assert; public class InboundChannelAdapterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - - public InboundChannelAdapterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + @Override + public String getInputChannelAttribute() { + return "value"; } @Override - protected String getInputChannelAttribute() { - return AnnotationUtils.VALUE; + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotationChain) { + + Class handlerBeanClass = handlerBeanType.toClass(); + + if (MessageSource.class.isAssignableFrom(handlerBeanClass)) { + return beanDefinition; + } + + Method method = null; + if (Supplier.class.isAssignableFrom(handlerBeanClass)) { + method = ClassUtils.SUPPLIER_GET_METHOD; + } + else if (ClassUtils.isKotlinFunction0(handlerBeanClass)) { + method = ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD; + } + + if (method != null) { + return BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageSource.class) + .addPropertyReference("object", beanName) + .addPropertyValue("method", method) + .getBeanDefinition(); + } + else { + throw new IllegalArgumentException( + "The '" + this.annotationType + "' on @Bean method level is allowed only for: " + + MessageSource.class.getName() + ", or " + Supplier.class.getName() + + (ClassUtils.KOTLIN_FUNCTION_0_CLASS != null + ? ", or " + ClassUtils.KOTLIN_FUNCTION_0_CLASS.getName() + : "") + " beans"); + } + } + + @Override + protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition, + ComponentDefinition beanDefinition, List annotations) { + + String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class); + Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty."); + return BeanDefinitionBuilder.rootBeanDefinition(SourcePollingChannelAdapterFactoryBean.class) + .addPropertyValue("outputChannelName", channelName) + .addPropertyReference("source", handlerBeanDefinition.getName()) + .getBeanDefinition(); } @Override @@ -67,18 +112,7 @@ public class InboundChannelAdapterAnnotationPostProcessor extends MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class); Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty."); - MessageSource messageSource; - try { - messageSource = createMessageSource(bean, beanName, method); - } - catch (NoSuchBeanDefinitionException e) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Skipping endpoint creation; " - + e.getMessage() - + "; perhaps due to some '@Conditional' annotation."); - } - return null; - } + MessageSource messageSource = createMessageSource(bean, beanName, method); SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter(); adapter.setOutputChannelName(channelName); @@ -89,51 +123,21 @@ public class InboundChannelAdapterAnnotationPostProcessor extends return adapter; } - private MessageSource createMessageSource(Object beanArg, String beanNameArg, Method methodArg) { - MessageSource messageSource = null; - Object bean = beanArg; - Method method = methodArg; - String beanName = beanNameArg; - if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method); - Class targetClass = target.getClass(); - Assert.isTrue(MessageSource.class.isAssignableFrom(targetClass) || - Supplier.class.isAssignableFrom(targetClass) || - ClassUtils.isKotlinFunction0(targetClass), - () -> "The '" + this.annotationType + "' on @Bean method " + "level is allowed only for: " + - MessageSource.class.getName() + ", or " + Supplier.class.getName() + - (ClassUtils.KOTLIN_FUNCTION_0_CLASS != null - ? ", or " + ClassUtils.KOTLIN_FUNCTION_0_CLASS.getName() - : "") + " beans"); - if (target instanceof MessageSource) { - messageSource = (MessageSource) target; - } - else if (target instanceof Supplier) { - method = ClassUtils.SUPPLIER_GET_METHOD; - bean = target; - beanName += '.' + methodArg.getName(); - } - else if (ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD != null) { - method = ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD; - bean = target; - beanName += '.' + methodArg.getName(); - } - } - if (messageSource == null) { - MethodInvokingMessageSource methodInvokingMessageSource = new MethodInvokingMessageSource(); - methodInvokingMessageSource.setObject(bean); - methodInvokingMessageSource.setMethod(method); - String messageSourceBeanName = generateHandlerBeanName(beanName, method); - this.definitionRegistry.registerBeanDefinition(messageSourceBeanName, - new RootBeanDefinition(MethodInvokingMessageSource.class, () -> methodInvokingMessageSource)); - messageSource = this.beanFactory.getBean(messageSourceBeanName, MessageSource.class); - } - return messageSource; + private MessageSource createMessageSource(Object bean, String beanName, Method method) { + MethodInvokingMessageSource methodInvokingMessageSource = new MethodInvokingMessageSource(); + methodInvokingMessageSource.setObject(bean); + methodInvokingMessageSource.setMethod(method); + String messageSourceBeanName = generateHandlerBeanName(beanName, method); + getDefinitionRegistry().registerBeanDefinition(messageSourceBeanName, + new RootBeanDefinition(MethodInvokingMessageSource.class, () -> methodInvokingMessageSource)); + return getBeanFactory().getBean(messageSourceBeanName, MessageSource.class); } @Override - protected String generateHandlerBeanName(String originalBeanName, Method method) { - return super.generateHandlerBeanName(originalBeanName, method) + protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations, + @Nullable String methodName) { + + return super.generateHandlerBeanName(originalBeanName, mergedAnnotations, methodName) .replaceFirst(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX + '$', ".source"); } 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 f9f7261e12..7b8a3d4f2b 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 @@ -21,8 +21,6 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -32,18 +30,25 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.support.AopUtils; +import org.springframework.aot.AotDetector; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.MethodMetadata; +import org.springframework.core.type.StandardMethodMetadata; import org.springframework.integration.annotation.Aggregator; import org.springframework.integration.annotation.BridgeFrom; import org.springframework.integration.annotation.BridgeTo; @@ -73,50 +78,135 @@ import org.springframework.util.StringUtils; * @author Gary Russell * @author Rick Hogge */ -public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, - SmartInitializingSingleton { +public class MessagingAnnotationPostProcessor + implements BeanDefinitionRegistryPostProcessor, BeanPostProcessor, SmartInitializingSingleton { protected final Log logger = LogFactory.getLog(this.getClass()); // NOSONAR private final Map, MethodAnnotationPostProcessor> postProcessors = new HashMap<>(); - private ConfigurableListableBeanFactory beanFactory; - private final Set> noAnnotationsCache = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); private final List methodsToPostProcessAfterContextInitialization = new ArrayList<>(); + private BeanDefinitionRegistry registry; + + private ConfigurableListableBeanFactory beanFactory; + private volatile boolean initialized; @Override - public void setBeanFactory(BeanFactory beanFactory) { - Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(), - "a ConfigurableListableBeanFactory is required"); - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + this.registry = registry; + this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor()); + this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor()); + this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor()); + this.postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor()); + this.postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor()); + this.postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor()); + this.postProcessors.put(InboundChannelAdapter.class, new InboundChannelAdapterAnnotationPostProcessor()); + this.postProcessors.put(BridgeFrom.class, new BridgeFromAnnotationPostProcessor()); + this.postProcessors.put(BridgeTo.class, new BridgeToAnnotationPostProcessor()); + Map, MethodAnnotationPostProcessor> customPostProcessors = + setupCustomPostProcessors(); + if (!CollectionUtils.isEmpty(customPostProcessors)) { + this.postProcessors.putAll(customPostProcessors); + } + this.postProcessors.values().stream() + .filter(BeanFactoryAware.class::isInstance) + .map(BeanFactoryAware.class::cast) + .forEach((processor) -> processor.setBeanFactory((BeanFactory) this.registry)); + + if (!AotDetector.useGeneratedArtifacts()) { + String[] beanNames = registry.getBeanDefinitionNames(); + + for (String beanName : beanNames) { + BeanDefinition beanDef = registry.getBeanDefinition(beanName); + if (beanDef instanceof AnnotatedBeanDefinition annotatedBeanDefinition + && annotatedBeanDefinition.getFactoryMethodMetadata() != null) { + + processCandidate(beanName, annotatedBeanDefinition); + } + } + } + } + + private void processCandidate(String beanName, AnnotatedBeanDefinition beanDefinition) { + MethodMetadata methodMetadata = beanDefinition.getFactoryMethodMetadata(); + MergedAnnotations annotations = methodMetadata.getAnnotations(); + if (methodMetadata instanceof StandardMethodMetadata standardMethodMetadata) { + annotations = MergedAnnotations.from(standardMethodMetadata.getIntrospectedMethod()); + } + + List messagingAnnotations = obtainMessagingAnnotations(annotations, beanName); + + for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) { + Class annotationType = messagingAnnotation.annotationType; + List annotationChain = + MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation, annotationType); + + processMessagingAnnotationOnBean(beanName, beanDefinition, annotationType, annotationChain); + } + } + + private List obtainMessagingAnnotations(MergedAnnotations annotations, String identified) { + List messagingAnnotations = new ArrayList<>(); + + for (Class annotationType : this.postProcessors.keySet()) { + annotations.stream() + .filter((ann) -> ann.getType().equals(annotationType)) + .map(MergedAnnotation::getRoot) + .map(MergedAnnotation::synthesize) + .map((ann) -> new MessagingMetaAnnotation(ann, annotationType)) + .forEach(messagingAnnotations::add); + } + + if (annotations.get(EndpointId.class, (ann) -> ann.hasNonDefaultValue("value")).isPresent() + && messagingAnnotations.size() > 1) { + + throw new IllegalStateException("@EndpointId on " + identified + + " can only have one EIP annotation, found: " + messagingAnnotations.size()); + } + + return messagingAnnotations; + } + + private void processMessagingAnnotationOnBean(String beanName, AnnotatedBeanDefinition beanDefinition, + Class annotationType, List annotationChain) { + + MethodAnnotationPostProcessor messagingAnnotationProcessor = this.postProcessors.get(annotationType); + if (messagingAnnotationProcessor != null) { + if (messagingAnnotationProcessor.beanAnnotationAware()) { + if (messagingAnnotationProcessor.shouldCreateEndpoint( + beanDefinition.getFactoryMethodMetadata().getAnnotations(), annotationChain)) { + + messagingAnnotationProcessor.processBeanDefinition(beanName, beanDefinition, annotationChain); + } + else { + throw new BeanDefinitionValidationException( + "The input channel for endpoint on '@Bean' method must be set for the " + + annotationType + ". The bean definition with the problem is: " + beanName); + } + } + else { + throw new BeanDefinitionValidationException( + "The messaging annotation '" + annotationType + "' cannot be declared on '@Bean'. " + + "The bean definition with the problem is: " + beanName); + } + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; } protected ConfigurableListableBeanFactory getBeanFactory() { return this.beanFactory; } - @Override - public void afterPropertiesSet() { - Assert.notNull(this.beanFactory, "BeanFactory must not be null"); - this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(InboundChannelAdapter.class, - new InboundChannelAdapterAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(BridgeFrom.class, new BridgeFromAnnotationPostProcessor(this.beanFactory)); - this.postProcessors.put(BridgeTo.class, new BridgeToAnnotationPostProcessor(this.beanFactory)); - Map, MethodAnnotationPostProcessor> customPostProcessors = - setupCustomPostProcessors(); - if (!CollectionUtils.isEmpty(customPostProcessors)) { - this.postProcessors.putAll(customPostProcessors); - } + protected BeanDefinitionRegistry getBeanDefinitionRegistry() { + return this.registry; } protected Map, MethodAnnotationPostProcessor> setupCustomPostProcessors() { @@ -160,34 +250,23 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean } private void doWithMethod(Method method, Object bean, String beanName, Class beanClass) { - MergedAnnotations mergedAnnotations = - MergedAnnotations.search(MergedAnnotations.SearchStrategy.DIRECT) - .from(method); + MergedAnnotations mergedAnnotations = MergedAnnotations.from(method); - List messagingAnnotations = new ArrayList<>(); + boolean noMessagingAnnotations = true; - 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 (!mergedAnnotations.isPresent(Bean.class)) { // See postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) + List messagingAnnotations = + obtainMessagingAnnotations(mergedAnnotations, method.toGenericString()); + + for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) { + noMessagingAnnotations = false; + Class annotationType = messagingAnnotation.annotationType; + List annotationChain = + MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation, annotationType); + processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain); + } } - 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: " + messagingAnnotations.size()); - } - - for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) { - Class annotationType = messagingAnnotation.messagingAnnotationType; - List annotationChain = getAnnotationChain(messagingAnnotation.annotation, annotationType); - processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain); - } - - if (messagingAnnotations.size() == 0) { + if (noMessagingAnnotations) { this.noAnnotationsCache.add(beanClass); } } @@ -197,7 +276,9 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean MethodAnnotationPostProcessor postProcessor = MessagingAnnotationPostProcessor.this.postProcessors.get(annotationType); - if (postProcessor != null && postProcessor.shouldCreateEndpoint(method, annotations)) { + if (postProcessor != null && postProcessor.supportsPojoMethod() + && postProcessor.shouldCreateEndpoint(method, annotations)) { + Method targetMethod = method; if (AopUtils.isJdkDynamicProxy(bean)) { try { @@ -230,7 +311,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); - BeanDefinitionRegistry definitionRegistry = (BeanDefinitionRegistry) beanFactory; if (result instanceof AbstractEndpoint endpoint) { String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup", String.class); if (StringUtils.hasText(autoStartup)) { @@ -255,51 +335,13 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean String endpointBeanName = generateBeanName(beanName, method, annotationType); endpoint.setBeanName(endpointBeanName); - definitionRegistry.registerBeanDefinition(endpointBeanName, - new RootBeanDefinition((Class) endpoint.getClass(), () -> endpoint)); + getBeanDefinitionRegistry() + .registerBeanDefinition(endpointBeanName, + new RootBeanDefinition((Class) endpoint.getClass(), () -> endpoint)); beanFactory.getBean(endpointBeanName); } } - /** - * @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(Annotation messagingAnnotation, - Class annotationType) { - - List annotationChain = new LinkedList<>(); - Set visited = new HashSet<>(); - - recursiveFindAnnotation(annotationType, messagingAnnotation, annotationChain, visited); - if (annotationChain.size() > 0) { - Collections.reverse(annotationChain); - } - - return annotationChain; - } - - protected boolean recursiveFindAnnotation(Class annotationType, Annotation ann, - List annotationChain, Set visited) { - - if (ann.annotationType().equals(annotationType)) { - annotationChain.add(ann); - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - 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 (recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) { - annotationChain.add(ann); - return true; - } - } - } - return false; - } - protected String generateBeanName(String originalBeanName, Method method, Class annotationType) { @@ -309,7 +351,8 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean + ClassUtils.getShortNameAsProperty(annotationType); name = baseName; int count = 1; - while (this.beanFactory.containsBean(name)) { + ConfigurableListableBeanFactory beanFactory = getBeanFactory(); + while (beanFactory.containsBean(name)) { name = baseName + "#" + (++count); } } @@ -320,7 +363,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean return this.postProcessors; } - private record MessagingMetaAnnotation(Annotation annotation, Class messagingAnnotationType) { + protected record MessagingMetaAnnotation(Annotation annotation, Class annotationType) { } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.java index d7ab9e7bba..2ea1ed5112 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/MethodAnnotationPostProcessor.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. @@ -20,6 +20,11 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.integration.util.MessagingAnnotationUtils; +import org.springframework.util.StringUtils; + /** * Strategy interface for post-processing annotated methods. * @@ -31,8 +36,12 @@ import java.util.List; */ public interface MethodAnnotationPostProcessor { + String INPUT_CHANNEL_ATTRIBUTE = "inputChannel"; + Object postProcess(Object bean, String beanName, Method method, List annotations); + void processBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, List annotations); + /** * Determine if the provided {@code method} and its {@code annotations} are eligible * to create an {@link org.springframework.integration.endpoint.AbstractEndpoint}. @@ -42,6 +51,26 @@ public interface MethodAnnotationPostProcessor { * {@link org.springframework.integration.endpoint.AbstractEndpoint} * @since 4.0 */ - boolean shouldCreateEndpoint(Method method, List annotations); + default boolean shouldCreateEndpoint(Method method, List annotations) { + return shouldCreateEndpoint(MergedAnnotations.from(method), annotations); + } + + default boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List annotations) { + String inputChannel = + MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), String.class); + return StringUtils.hasText(inputChannel); + } + + default String getInputChannelAttribute() { + return INPUT_CHANNEL_ATTRIBUTE; + } + + default boolean beanAnnotationAware() { + return true; + } + + default boolean supportsPojoMethod() { + return true; + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessor.java index 339a80cc0c..23eb8df0ef 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -20,18 +20,24 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.integration.annotation.Router; +import org.springframework.integration.config.RouterFactoryBean; import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.router.MethodInvokingRouter; import org.springframework.integration.util.MessagingAnnotationUtils; import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -44,41 +50,62 @@ import org.springframework.util.StringUtils; */ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public RouterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + public RouterAnnotationPostProcessor() { this.messageHandlerAttributes.addAll(Arrays.asList("defaultOutputChannel", "applySequence", "ignoreSendFailures", "resolutionRequired", "channelMappings", "prefix", "suffix")); } + @Override + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotations) { + + BeanDefinition handlerBeanDefinition = + super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); + + if (handlerBeanDefinition != null) { + return handlerBeanDefinition; + } + + BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); + if (targetObjectBeanDefinition == null) { + targetObjectBeanDefinition = new RuntimeBeanReference(beanName); + } + + BeanDefinition routerBeanDefinition = + BeanDefinitionBuilder.genericBeanDefinition(RouterFactoryBean.class) + .addPropertyValue("targetObject", targetObjectBeanDefinition) + .getBeanDefinition(); + + new BeanDefinitionPropertiesMapper(routerBeanDefinition, annotations) + .setPropertyValue("applySequence") + .setPropertyValue("ignoreSendFailures") + .setPropertyValue("resolutionRequired") + .setPropertyValue("prefix") + .setPropertyValue("suffix"); + + String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, "channelMappings", + String[].class); + if (!ObjectUtils.isEmpty(channelMappings)) { + Map mappings = + Arrays.stream(channelMappings) + .map((mapping) -> { + String[] keyValue = mapping.split("="); + return Map.entry(keyValue[0], keyValue[1]); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + routerBeanDefinition.getPropertyValues() + .addPropertyValue("channelMappings", mappings); + } + + return routerBeanDefinition; + } + @Override protected MessageHandler createHandler(Object bean, Method method, List annotations) { - AbstractMessageRouter router; - if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method); - router = extractTypeIfPossible(target, AbstractMessageRouter.class); - if (router == null) { - if (target instanceof MessageHandler) { - Assert.isTrue(routerAttributesProvided(annotations), - "'defaultOutputChannel', 'applySequence', 'ignoreSendFailures', 'resolutionRequired', " + - "'channelMappings', 'prefix' and 'suffix' " + - "can be applied to 'AbstractMessageRouter' implementations, but target handler is: " + - target.getClass()); - return (MessageHandler) target; - } - else { - router = new MethodInvokingRouter(target); - } - } - else { - checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations); - return router; - } - } - else { - router = new MethodInvokingRouter(bean, method); - } - String defaultOutputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, - "defaultOutputChannel", String.class); + AbstractMessageRouter router = new MethodInvokingRouter(bean, method); + String defaultOutputChannelName = + MessagingAnnotationUtils.resolveAttribute(annotations, "defaultOutputChannel", String.class); if (StringUtils.hasText(defaultOutputChannelName)) { router.setDefaultOutputChannelName(defaultOutputChannelName); } @@ -110,14 +137,16 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP methodInvokingRouter.setResolutionRequired(resolveAttributeToBoolean(resolutionRequired)); } + ConfigurableListableBeanFactory beanFactory = getBeanFactory(); + String prefix = MessagingAnnotationUtils.resolveAttribute(annotations, "prefix", String.class); if (StringUtils.hasText(prefix)) { - methodInvokingRouter.setPrefix(this.beanFactory.resolveEmbeddedValue(prefix)); + methodInvokingRouter.setPrefix(beanFactory.resolveEmbeddedValue(prefix)); } String suffix = MessagingAnnotationUtils.resolveAttribute(annotations, "suffix", String.class); if (StringUtils.hasText(suffix)) { - methodInvokingRouter.setSuffix(this.beanFactory.resolveEmbeddedValue(suffix)); + methodInvokingRouter.setSuffix(beanFactory.resolveEmbeddedValue(suffix)); } String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, "channelMappings", @@ -127,7 +156,7 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP for (String channelMapping : channelMappings) { mappings.append(channelMapping).append("\n"); } - Properties properties = (Properties) this.conversionService.convert(mappings.toString(), + Properties properties = (Properties) getConversionService().convert(mappings.toString(), TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Properties.class)); methodInvokingRouter.replaceChannelMappings(properties); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java index c88270e57e..9659c31e33 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -21,18 +21,18 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.ResolvableType; import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.config.ServiceActivatorFactoryBean; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.handler.ReactiveMessageHandlerAdapter; -import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper; import org.springframework.integration.handler.ServiceActivatingHandler; import org.springframework.integration.util.MessagingAnnotationUtils; import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.ReactiveMessageHandler; import org.springframework.util.StringUtils; /** @@ -45,47 +45,41 @@ import org.springframework.util.StringUtils; */ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public ServiceActivatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + public ServiceActivatorAnnotationPostProcessor() { this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "requiresReply", "adviceChain")); } + @Override + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotations) { + + BeanDefinition handlerBeanDefinition = + super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); + + if (handlerBeanDefinition != null) { + return handlerBeanDefinition; + } + + BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); + if (targetObjectBeanDefinition == null) { + targetObjectBeanDefinition = new RuntimeBeanReference(beanName); + } + + BeanDefinition serviceActivatorBeanDefinition = + BeanDefinitionBuilder.genericBeanDefinition(ServiceActivatorFactoryBean.class) + .addPropertyValue("targetObject", targetObjectBeanDefinition) + .getBeanDefinition(); + + new BeanDefinitionPropertiesMapper(serviceActivatorBeanDefinition, annotations) + .setPropertyValue("requiresReply") + .setPropertyValue("async"); + + return serviceActivatorBeanDefinition; + } @Override protected MessageHandler createHandler(Object bean, Method method, List annotations) { - AbstractReplyProducingMessageHandler serviceActivator; - if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method); - serviceActivator = extractTypeIfPossible(target, AbstractReplyProducingMessageHandler.class); - if (serviceActivator == null) { - if (target instanceof ReactiveMessageHandler) { - return new ReactiveMessageHandlerAdapter((ReactiveMessageHandler) target); - } - if (target instanceof MessageHandler) { - /* - * Return a reply-producing message handler so that we still get 'produced no reply' messages - * and the super class will inject the advice chain to advise the handler method if needed. - */ - return new ReplyProducingMessageHandlerWrapper((MessageHandler) target); - } - else { - MessageProcessor messageProcessor = buildLambdaMessageProcessorForBeanMethod(method, target); - if (messageProcessor != null) { - serviceActivator = new ServiceActivatingHandler(messageProcessor); - } - else { - serviceActivator = new ServiceActivatingHandler(target); - } - } - } - else { - checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations); - return (MessageHandler) target; - } - } - else { - serviceActivator = new ServiceActivatingHandler(bean, method); - } + AbstractReplyProducingMessageHandler serviceActivator = new ServiceActivatingHandler(bean, method); String requiresReply = MessagingAnnotationUtils.resolveAttribute(annotations, "requiresReply", String.class); if (StringUtils.hasText(requiresReply)) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessor.java index abe6e28da4..8769ba5cba 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -21,15 +21,18 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.ResolvableType; import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.config.SplitterFactoryBean; import org.springframework.integration.splitter.AbstractMessageSplitter; import org.springframework.integration.splitter.MethodInvokingSplitter; import org.springframework.integration.util.MessagingAnnotationUtils; import org.springframework.messaging.MessageHandler; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -41,43 +44,47 @@ import org.springframework.util.StringUtils; */ public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public SplitterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + public SplitterAnnotationPostProcessor() { this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "applySequence", "adviceChain")); } @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotations) { + + BeanDefinition handlerBeanDefinition = + super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); + + if (handlerBeanDefinition != null) { + return handlerBeanDefinition; + } + + BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); + if (targetObjectBeanDefinition == null) { + targetObjectBeanDefinition = new RuntimeBeanReference(beanName); + } + + BeanDefinitionBuilder splitterBeanDefinition = + BeanDefinitionBuilder.genericBeanDefinition(SplitterFactoryBean.class) + .addPropertyValue("targetObject", targetObjectBeanDefinition); + String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, "applySequence", String.class); - - AbstractMessageSplitter splitter; - if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method); - splitter = this.extractTypeIfPossible(target, AbstractMessageSplitter.class); - if (splitter == null) { - if (target instanceof MessageHandler) { - Assert.hasText(applySequence, "'applySequence' can be applied to 'AbstractMessageSplitter', but " + - "target handler is: " + target.getClass()); - return (MessageHandler) target; - } - else { - splitter = new MethodInvokingSplitter(target); - } - } - else { - checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations); - return splitter; - } - } - else { - splitter = new MethodInvokingSplitter(bean, method); + if (StringUtils.hasText(applySequence)) { + splitterBeanDefinition.addPropertyValue("applySequence", applySequence); } + return splitterBeanDefinition.getBeanDefinition(); + } + @Override + protected MessageHandler createHandler(Object bean, Method method, List annotations) { + AbstractMessageSplitter splitter = new MethodInvokingSplitter(bean, method); + + String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, "applySequence", String.class); if (StringUtils.hasText(applySequence)) { splitter.setApplySequence(resolveAttributeToBoolean(applySequence)); } - this.setOutputChannelIfPresent(annotations, splitter); + setOutputChannelIfPresent(annotations, splitter); return splitter; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/TransformerAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/TransformerAnnotationPostProcessor.java index 7fbf7ea111..c8f77e00da 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/TransformerAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/TransformerAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -21,11 +21,13 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.MessageProcessor; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.ResolvableType; +import org.springframework.integration.config.TransformerFactoryBean; import org.springframework.integration.transformer.MessageTransformingHandler; import org.springframework.integration.transformer.MethodInvokingTransformer; import org.springframework.integration.transformer.Transformer; @@ -42,35 +44,34 @@ import org.springframework.messaging.MessageHandler; public class TransformerAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - public TransformerAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); + public TransformerAnnotationPostProcessor() { this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "adviceChain")); } @Override - protected MessageHandler createHandler(Object bean, Method method, List annotations) { - Transformer transformer; - if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { - Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method); - transformer = extractTypeIfPossible(target, Transformer.class); - if (transformer == null) { - if (extractTypeIfPossible(target, AbstractReplyProducingMessageHandler.class) != null) { - checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations); - return (MessageHandler) target; - } - MessageProcessor messageProcessor = buildLambdaMessageProcessorForBeanMethod(method, target); - if (messageProcessor != null) { - transformer = new MethodInvokingTransformer(messageProcessor); - } - else { - transformer = new MethodInvokingTransformer(target); - } - } - } - else { - transformer = new MethodInvokingTransformer(bean, method); + protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, + ResolvableType handlerBeanType, List annotations) { + + BeanDefinition handlerBeanDefinition = + super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations); + + if (handlerBeanDefinition != null) { + return handlerBeanDefinition; } + BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition); + if (targetObjectBeanDefinition == null) { + targetObjectBeanDefinition = new RuntimeBeanReference(beanName); + } + + return BeanDefinitionBuilder.genericBeanDefinition(TransformerFactoryBean.class) + .addPropertyValue("targetObject", targetObjectBeanDefinition) + .getBeanDefinition(); + } + + @Override + protected MessageHandler createHandler(Object bean, Method method, List annotations) { + Transformer transformer = new MethodInvokingTransformer(bean, method); MessageTransformingHandler handler = new MessageTransformingHandler(transformer); setOutputChannelIfPresent(annotations, handler); return handler; 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 0524c533db..726fc8d552 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 @@ -18,14 +18,21 @@ package org.springframework.integration.util; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.springframework.aop.framework.AopProxyUtils; 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.EndpointId; import org.springframework.integration.annotation.Payloads; +import org.springframework.lang.Nullable; import org.springframework.messaging.MessagingException; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Headers; @@ -51,7 +58,6 @@ public final class MessagingAnnotationUtils { /** * Get the attribute value from the annotation hierarchy, returning the first * {@link MessagingAnnotationUtils#hasValue non-empty}) value closest to the annotated method. - * * @param annotations The meta-annotations in order (closest first). * @param name The attribute name. * @param requiredType The expected type. @@ -88,11 +94,8 @@ public final class MessagingAnnotationUtils { return false; } // Annotation with 'value' set to special 'none' string - if ((annotationValue instanceof Annotation) && - ValueConstants.DEFAULT_NONE.equals(AnnotationUtils.getValue((Annotation) annotationValue))) { - return false; - } - return true; + return (!(annotationValue instanceof Annotation)) || + !ValueConstants.DEFAULT_NONE.equals(AnnotationUtils.getValue((Annotation) annotationValue)); } public static Method findAnnotatedMethod(Object target, final Class annotationType) { @@ -143,9 +146,62 @@ public final class MessagingAnnotationUtils { * @return the id, or null. * @since 5.0.4 */ + @Nullable public static String endpointIdValue(Method method) { - EndpointId endpointId = AnnotationUtils.findAnnotation(method, EndpointId.class); - return endpointId != null ? endpointId.value() : null; + return endpointIdValue(MergedAnnotations.from(method)); + } + + /** + * Return the {@link EndpointId#value()} property, if present. + * @param mergedAnnotations the {@link MergedAnnotations} to analyze. + * @return the id, or null. + * @since 6.0 + */ + @Nullable + public static String endpointIdValue(MergedAnnotations mergedAnnotations) { + MergedAnnotation endpointIdAnnotation = mergedAnnotations.get(EndpointId.class); + return endpointIdAnnotation.getValue(AnnotationUtils.VALUE, String.class).orElse(null); + } + + /** + * Get a chain of its meta-annotations for the provided instance and expected type. + * @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. + * @since 6.0 + */ + public static List getAnnotationChain(Annotation messagingAnnotation, + Class annotationType) { + + List annotationChain = new LinkedList<>(); + Set visited = new HashSet<>(); + + recursiveFindAnnotation(annotationType, messagingAnnotation, annotationChain, visited); + if (annotationChain.size() > 0) { + Collections.reverse(annotationChain); + } + + return annotationChain; + } + + private static boolean recursiveFindAnnotation(Class annotationType, Annotation ann, + List annotationChain, Set visited) { + + if (ann.annotationType().equals(annotationType)) { + annotationChain.add(ann); + return true; + } + for (Annotation metaAnn : ann.annotationType().getAnnotations()) { + 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 (recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) { + annotationChain.add(ann); + return true; + } + } + } + return false; } private MessagingAnnotationUtils() { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/bus/DirectChannelSubscriptionTests.java b/spring-integration-core/src/test/java/org/springframework/integration/bus/DirectChannelSubscriptionTests.java index d86281c55a..b183b5e4c0 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/bus/DirectChannelSubscriptionTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/bus/DirectChannelSubscriptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -19,10 +19,11 @@ package org.springframework.integration.bus; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; @@ -45,20 +46,20 @@ import org.springframework.messaging.support.GenericMessage; */ public class DirectChannelSubscriptionTests { - private TestApplicationContext context = TestUtils.createTestApplicationContext(); + private final TestApplicationContext context = TestUtils.createTestApplicationContext(); - private DirectChannel sourceChannel = new DirectChannel(); + private final DirectChannel sourceChannel = new DirectChannel(); - private PollableChannel targetChannel = new QueueChannel(); + private final PollableChannel targetChannel = new QueueChannel(); - @Before + @BeforeEach public void setupChannels() { this.context.registerChannel("sourceChannel", this.sourceChannel); this.context.registerChannel("targetChannel", this.targetChannel); } - @After + @AfterEach public void tearDown() { this.context.close(); } @@ -80,8 +81,8 @@ public class DirectChannelSubscriptionTests { @Test public void sendAndReceiveForAnnotatedEndpoint() { MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); - postProcessor.setBeanFactory(this.context.getBeanFactory()); - postProcessor.afterPropertiesSet(); + postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory()); + postProcessor.postProcessBeanFactory(this.context.getBeanFactory()); postProcessor.afterSingletonsInstantiated(); TestEndpoint endpoint = new TestEndpoint(); postProcessor.postProcessAfterInitialization(endpoint, "testEndpoint"); @@ -91,7 +92,7 @@ public class DirectChannelSubscriptionTests { assertThat(response.getPayload()).isEqualTo("foo-from-annotated-endpoint"); } - @Test(expected = MessagingException.class) + @Test public void exceptionThrownFromRegisteredEndpoint() { AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @@ -104,7 +105,8 @@ public class DirectChannelSubscriptionTests { EventDrivenConsumer endpoint = new EventDrivenConsumer(sourceChannel, handler); this.context.registerEndpoint("testEndpoint", endpoint); this.context.refresh(); - this.sourceChannel.send(new GenericMessage<>("foo")); + assertThatExceptionOfType(MessagingException.class) + .isThrownBy(() -> this.sourceChannel.send(new GenericMessage<>("foo"))); } @Test @@ -112,8 +114,7 @@ public class DirectChannelSubscriptionTests { QueueChannel errorChannel = new QueueChannel(); this.context.registerChannel(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, errorChannel); MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); - postProcessor.setBeanFactory(this.context.getBeanFactory()); - postProcessor.afterPropertiesSet(); + postProcessor.postProcessBeanFactory(this.context.getBeanFactory()); postProcessor.afterSingletonsInstantiated(); FailingTestEndpoint endpoint = new FailingTestEndpoint(); postProcessor.postProcessAfterInitialization(endpoint, "testEndpoint"); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/CustomMessagingAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/CustomMessagingAnnotationTests.java index 2a50583519..12c34ed446 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/CustomMessagingAnnotationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/CustomMessagingAnnotationTests.java @@ -41,6 +41,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.log.LogAccessor; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.context.IntegrationContextUtils; @@ -105,7 +106,7 @@ public class CustomMessagingAnnotationTests { MessagingAnnotationPostProcessor messagingAnnotationPostProcessor = new MessagingAnnotationPostProcessor(); messagingAnnotationPostProcessor. - addMessagingAnnotationPostProcessor(Logging.class, new LogAnnotationPostProcessor(beanFactory)); + addMessagingAnnotationPostProcessor(Logging.class, new LogAnnotationPostProcessor()); return messagingAnnotationPostProcessor; } @@ -124,29 +125,24 @@ public class CustomMessagingAnnotationTests { String value(); - LoggingHandler.Level level() default LoggingHandler.Level.INFO; } private static class LogAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor { - LogAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) { - super(beanFactory); - } - @Override - protected String getInputChannelAttribute() { - return "value"; + public String getInputChannelAttribute() { + return AnnotationUtils.VALUE; } @Override protected MessageHandler createHandler(Object bean, Method method, List annotations) { LoggingHandler.Level level = MessagingAnnotationUtils.resolveAttribute(annotations, "level", LoggingHandler.Level.class); - LoggingHandler loggingHandler = new LoggingHandler(level.name()); + LoggingHandler loggingHandler = new LoggingHandler(level); MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor<>(bean, method); - processor.setBeanFactory(this.beanFactory); + processor.setBeanFactory(getBeanFactory()); loggingHandler.setLogExpression(new FunctionExpression<>(processor::processMessage)); return loggingHandler; } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java index 3f5bbd1bc2..41e9caa0e4 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -22,10 +22,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException import java.util.Collections; import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.integration.annotation.Filter; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.channel.DirectChannel; @@ -54,16 +55,16 @@ public class FilterAnnotationPostProcessorTests { private final QueueChannel outputChannel = new QueueChannel(); - @Before + @BeforeEach public void init() { this.context.registerChannel("input", this.inputChannel); this.context.registerChannel("output", this.outputChannel); - this.postProcessor.setBeanFactory(this.context.getBeanFactory()); - this.postProcessor.afterPropertiesSet(); + this.postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory()); + this.postProcessor.postProcessBeanFactory(this.context.getBeanFactory()); this.postProcessor.afterSingletonsInstantiated(); } - @After + @AfterEach public void tearDown() { this.context.close(); } @@ -111,7 +112,7 @@ public class FilterAnnotationPostProcessorTests { @Test public void filterAnnotationWithAdviceArray() { TestAdvice advice = new TestAdvice(); - context.registerBean("adviceChain", new TestAdvice[] { advice }); + context.registerBean("adviceChain", new TestAdvice[]{ advice }); testValidFilter(new TestFilterWithAdviceDiscardWithin()); EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); assertThat(TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0)).isSameAs(advice); @@ -122,10 +123,10 @@ public class FilterAnnotationPostProcessorTests { public void filterAnnotationWithAdviceArrayTwice() { TestAdvice advice1 = new TestAdvice(); TestAdvice advice2 = new TestAdvice(); - context.registerBean("adviceChain1", new TestAdvice[] { advice1, advice2 }); + context.registerBean("adviceChain1", new TestAdvice[]{ advice1, advice2 }); TestAdvice advice3 = new TestAdvice(); TestAdvice advice4 = new TestAdvice(); - context.registerBean("adviceChain2", new TestAdvice[] { advice3, advice4 }); + context.registerBean("adviceChain2", new TestAdvice[]{ advice3, advice4 }); testValidFilter(new TestFilterWithAdviceDiscardWithinTwice()); EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); List adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class); @@ -151,10 +152,10 @@ public class FilterAnnotationPostProcessorTests { public void filterAnnotationWithAdviceCollectionTwice() { TestAdvice advice1 = new TestAdvice(); TestAdvice advice2 = new TestAdvice(); - context.registerBean("adviceChain1", new TestAdvice[] { advice1, advice2 }); + context.registerBean("adviceChain1", new TestAdvice[]{ advice1, advice2 }); TestAdvice advice3 = new TestAdvice(); TestAdvice advice4 = new TestAdvice(); - context.registerBean("adviceChain2", new TestAdvice[] { advice3, advice4 }); + context.registerBean("adviceChain2", new TestAdvice[]{ advice3, advice4 }); testValidFilter(new TestFilterWithAdviceDiscardWithinTwice()); EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter"); List adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class); 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 7a48b4b6c2..eb68bc6d81 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 @@ -17,7 +17,6 @@ package org.springframework.integration.config.annotation; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -27,9 +26,10 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -149,14 +149,6 @@ public class MessagingAnnotationPostProcessorTests { context.close(); } - @Test - public void testPostProcessorWithoutBeanFactory() { - MessagingAnnotationPostProcessor postProcessor = - new MessagingAnnotationPostProcessor(); - assertThatIllegalArgumentException() - .isThrownBy(postProcessor::afterPropertiesSet); - } - @Test public void testChannelResolution() { TestApplicationContext context = TestUtils.createTestApplicationContext(); @@ -307,12 +299,12 @@ public class MessagingAnnotationPostProcessorTests { context.close(); } - private MessagingAnnotationPostProcessor prepareMessagingAnnotationPostProcessor( + private static MessagingAnnotationPostProcessor prepareMessagingAnnotationPostProcessor( ConfigurableApplicationContext context) { MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); - postProcessor.setBeanFactory(context.getBeanFactory()); - postProcessor.afterPropertiesSet(); + postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) context.getBeanFactory()); + postProcessor.postProcessBeanFactory(context.getBeanFactory()); postProcessor.afterSingletonsInstantiated(); return postProcessor; } 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 97d4d63205..3f2a781902 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 @@ -166,8 +166,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests { assertThat(receive.getPayload()).isInstanceOf(MessageRejectedException.class); MessageRejectedException exception = (MessageRejectedException) receive.getPayload(); assertThat(exception.getMessage()) - .contains("message has been rejected in filter: bean " + - "'messagingAnnotationsWithBeanAnnotationTests.ContextConfiguration.filter.filter.handler'"); + .contains("message has been rejected in filter: bean 'filter.filter.handler'"); } @@ -330,7 +329,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests { @Bean @ServiceActivator(inputChannel = "aggregatorChannel") - public MessageHandler aggregator() { + public AggregatingMessageHandler aggregator() { AggregatingMessageHandler handler = new AggregatingMessageHandler(MessageGroup::getMessages); handler.setCorrelationStrategy(new ExpressionEvaluatingCorrelationStrategy("1")); handler.setReleaseStrategy(new ExpressionEvaluatingReleaseStrategy("size() == 10")); @@ -355,7 +354,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests { @Bean @Splitter(inputChannel = "splitterChannel", reactive = @Reactive("reactiveCustomizer")) - public MessageHandler splitter() { + public DefaultMessageSplitter splitter() { DefaultMessageSplitter defaultMessageSplitter = new DefaultMessageSplitter(); defaultMessageSplitter.setOutputChannelName("serviceChannel"); return defaultMessageSplitter; @@ -470,7 +469,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests { @Bean @Splitter(inputChannel = "splitterChannel", applySequence = "false") - public MessageHandler splitter() { + public DefaultMessageSplitter splitter() { return new DefaultMessageSplitter(); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessorTests.java index c39dfe63ba..200bc5a7b7 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -21,10 +21,11 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Collections; import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.Router; import org.springframework.integration.channel.DirectChannel; @@ -42,6 +43,8 @@ public class RouterAnnotationPostProcessorTests { private final TestApplicationContext context = TestUtils.createTestApplicationContext(); + private final MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); + private final DirectChannel inputChannel = new DirectChannel(); private final QueueChannel outputChannel = new QueueChannel(); @@ -53,26 +56,26 @@ public class RouterAnnotationPostProcessorTests { private final QueueChannel stringChannel = new QueueChannel(); - @Before + + @BeforeEach public void init() { context.registerChannel("input", inputChannel); context.registerChannel("output", outputChannel); context.registerChannel("routingChannel", routingChannel); context.registerChannel("integerChannel", integerChannel); context.registerChannel("stringChannel", stringChannel); + this.postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory()); + this.postProcessor.postProcessBeanFactory(this.context.getBeanFactory()); + this.postProcessor.afterSingletonsInstantiated(); } - @After + @AfterEach public void tearDown() { this.context.close(); } @Test public void testRouter() { - MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); - postProcessor.setBeanFactory(context.getBeanFactory()); - postProcessor.afterPropertiesSet(); - postProcessor.afterSingletonsInstantiated(); TestRouter testRouter = new TestRouter(); postProcessor.postProcessAfterInitialization(testRouter, "test"); context.refresh(); @@ -84,10 +87,6 @@ public class RouterAnnotationPostProcessorTests { @Test public void testRouterWithListParam() { - MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); - postProcessor.setBeanFactory(context.getBeanFactory()); - postProcessor.afterPropertiesSet(); - postProcessor.afterSingletonsInstantiated(); TestRouter testRouter = new TestRouter(); postProcessor.postProcessAfterInitialization(testRouter, "test"); context.refresh(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessorTests.java index dc4a99ab21..55817f3cfd 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/SplitterAnnotationPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -18,10 +18,11 @@ package org.springframework.integration.config.annotation; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.Lifecycle; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.Splitter; @@ -39,20 +40,20 @@ import org.springframework.messaging.support.GenericMessage; */ public class SplitterAnnotationPostProcessorTests { - private TestApplicationContext context = TestUtils.createTestApplicationContext(); + private final TestApplicationContext context = TestUtils.createTestApplicationContext(); - private DirectChannel inputChannel = new DirectChannel(); + private final DirectChannel inputChannel = new DirectChannel(); - private QueueChannel outputChannel = new QueueChannel(); + private final QueueChannel outputChannel = new QueueChannel(); - @Before + @BeforeEach public void init() { - context.registerChannel("input", inputChannel); - context.registerChannel("output", outputChannel); + this.context.registerChannel("input", this.inputChannel); + this.context.registerChannel("output", this.outputChannel); } - @After + @AfterEach public void tearDown() { this.context.close(); } @@ -60,8 +61,8 @@ public class SplitterAnnotationPostProcessorTests { @Test public void testSplitterAnnotation() { MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(); - postProcessor.setBeanFactory(context.getBeanFactory()); - postProcessor.afterPropertiesSet(); + postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory()); + postProcessor.postProcessBeanFactory(this.context.getBeanFactory()); postProcessor.afterSingletonsInstantiated(); TestSplitter splitter = new TestSplitter(); postProcessor.postProcessAfterInitialization(splitter, "testSplitter"); 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 e94b01da99..32f76fe43f 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 @@ -298,11 +298,11 @@ public class EnableIntegrationTests { private CountDownLatch inputReceiveLatch; @Autowired - @Qualifier("enableIntegrationTests.ContextConfiguration2.sendAsyncHandler.serviceActivator") + @Qualifier("sendAsyncHandler.serviceActivator") private AbstractEndpoint sendAsyncHandler; @Autowired - @Qualifier("enableIntegrationTests.ChildConfiguration.autoCreatedChannelMessageSource.inboundChannelAdapter") + @Qualifier("autoCreatedChannelMessageSource.inboundChannelAdapter") private Lifecycle autoCreatedChannelMessageSourceAdapter; @Autowired @@ -669,8 +669,7 @@ public class EnableIntegrationTests { .isThrownBy(() -> this.metaBridgeInput.send(testMessage)) .withMessageContaining("Dispatcher has no subscribers"); - this.context.getBean("enableIntegrationTests.ContextConfiguration.metaBridgeOutput.bridgeFrom", - Lifecycle.class).start(); + this.context.getBean("metaBridgeOutput.bridgeFrom", Lifecycle.class).start(); this.metaBridgeInput.send(testMessage); receive = this.metaBridgeOutput.receive(10_000); @@ -697,8 +696,7 @@ public class EnableIntegrationTests { .isThrownBy(() -> this.myBridgeToInput.send(testMessage)) .withMessageContaining("Dispatcher has no subscribers"); - this.context.getBean("enableIntegrationTests.ContextConfiguration.myBridgeToInput.bridgeTo", - Lifecycle.class).start(); + this.context.getBean("myBridgeToInput.bridgeTo", Lifecycle.class).start(); this.myBridgeToInput.send(bridgeMessage); receive = replyChannel.receive(10_000); @@ -741,8 +739,7 @@ public class EnableIntegrationTests { assertThat(this.roleController.noEndpointsRunning("bar")).isFalse(); Map state = this.roleController.getEndpointsRunningStatus("foo"); assertThat(state.get("annotationTestService.handle.serviceActivator")).isEqualTo(Boolean.FALSE); - assertThat(state.get("enableIntegrationTests.ContextConfiguration2.sendAsyncHandler.serviceActivator")) - .isEqualTo(Boolean.TRUE); + assertThat(state.get("sendAsyncHandler.serviceActivator")).isEqualTo(Boolean.TRUE); this.roleController.startLifecyclesInRole("foo"); assertThat(this.roleController.allEndpointsRunning("foo")).isTrue(); this.roleController.stopLifecyclesInRole("foo"); @@ -1690,6 +1687,7 @@ public class EnableIntegrationTests { @InboundChannelAdapter(value = "counterChannel", autoStartup = "false", phase = "23") public @interface MyInboundChannelAdapter { + @AliasFor(annotation = InboundChannelAdapter.class, attribute = "value") String value() default ""; String autoStartup() default ""; 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 index c5db244de6..8fedebcb8b 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-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. @@ -67,7 +67,7 @@ public class BeanNameTests { @SuppressWarnings("unused") @Autowired - @Qualifier("eipBean.handler") + @Qualifier("eipBeanHandler") private MessageHandler eipBeanHandler; @SuppressWarnings("unused") @@ -76,11 +76,11 @@ public class BeanNameTests { @SuppressWarnings("unused") @Autowired - @Qualifier("eipBean2.handler") + @Qualifier("eipBean2Handler") private MessageHandler eipBean2Handler; @Autowired - @Qualifier("eipBean2.handler.wrapper") + @Qualifier("eipBean2.handler") private ReplyProducingMessageHandlerWrapper eipBean2HandlerWrapper; @SuppressWarnings("unused") @@ -98,7 +98,7 @@ public class BeanNameTests { @SuppressWarnings("unused") @Autowired - @Qualifier("eipSource.source") + @Qualifier("eipSourceSource") private MessageSource eipSourceSource; @Test @@ -124,10 +124,10 @@ public class BeanNameTests { } } - @Bean("eipBean.handler") + @Bean("eipBeanHandler") @EndpointId("eipBean") @ServiceActivator(inputChannel = "channel2") - public MessageHandler replyingHandler() { + public AbstractReplyProducingMessageHandler replyingHandler() { return new AbstractReplyProducingMessageHandler() { @Override @@ -138,7 +138,7 @@ public class BeanNameTests { }; } - @Bean("eipBean2.handler") + @Bean("eipBean2Handler") @EndpointId("eipBean2") @ServiceActivator(inputChannel = "channel3") public MessageHandler handler() { @@ -151,7 +151,7 @@ public class BeanNameTests { return null; } - @Bean("eipSource.source") + @Bean("eipSourceSource") @EndpointId("eipSource") @InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000")) public MessageSource source() { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/graph/IntegrationGraphServerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/graph/IntegrationGraphServerTests.java index 4ff419222c..f0dfc69057 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/graph/IntegrationGraphServerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/graph/IntegrationGraphServerTests.java @@ -139,7 +139,7 @@ public class IntegrationGraphServerTests { assertThat(links.size()).isEqualTo(34); jsonArray = - JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter')]"); + JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter.router')]"); Map expressionRouter = (Map) jsonArray.get(0); assertThat(((List) expressionRouter.get("routes")).size()).isEqualTo(0); @@ -151,7 +151,7 @@ public class IntegrationGraphServerTests { this.testSource.receive(); this.expressionRouterInput.send(MessageBuilder.withPayload("foo").setHeader("foo", "fizChannel").build()); - jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router')]"); + jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router.router')]"); String routerJson = jsonArray.toJSONString(); this.server.rebuild(); @@ -172,7 +172,7 @@ public class IntegrationGraphServerTests { assertThat(links).isNotNull(); assertThat(links.size()).isEqualTo(37); - jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router')]"); + jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router.router')]"); routerJson = jsonArray.toJSONString(); assertThat(routerJson).contains("\"sendTimers\":{\"successes\":{\"count\":4"); jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'toRouter')]"); @@ -200,7 +200,7 @@ public class IntegrationGraphServerTests { assertThat(sourceJson).contains("\"receiveCounters\":{\"successes\":2,\"failures\":1"); jsonArray = - JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter')]"); + JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter.router')]"); expressionRouter = (Map) jsonArray.get(0); JSONArray routes = (JSONArray) expressionRouter.get("routes"); 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 5e65d3620e..66a14cf275 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 @@ -48,7 +48,6 @@ import org.springframework.integration.gateway.MessagingGatewaySupport; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.test.util.TestUtils; 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; @@ -71,7 +70,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; */ @SpringJUnitConfig @DirtiesContext -@TestExecutionListeners(DependencyInjectionTestExecutionListener.class) +@TestExecutionListeners(DependencyInjectionTestExecutionListener.class) // To ignore other default listeners public class MicrometerMetricsTests { @Autowired @@ -149,7 +148,7 @@ public class MicrometerMetricsTests { .counter().count()).isEqualTo(1); assertThat(registry.get("spring.integration.send") - .tag("name", "eipBean.handler") + .tag("name", "eipBean") .tag("result", "success") .timer().count()).isEqualTo(1); @@ -277,7 +276,7 @@ public class MicrometerMetricsTests { SimpleMeterRegistry registry = new SimpleMeterRegistry(); registry.config().meterFilter(MeterFilter.deny(id -> "channel".equals(id.getTag("type")) && - "noMeters".equals(id.getTag("name")))); + "noMeters".equals(id.getTag("name")))); return registry; } @@ -292,7 +291,7 @@ public class MicrometerMetricsTests { @Bean("eipBean.handler") @EndpointId("eipBean") @ServiceActivator(inputChannel = "channel2") - public MessageHandler replyingHandler() { + public AbstractReplyProducingMessageHandler replyingHandler() { return new AbstractReplyProducingMessageHandler() { @Override diff --git a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java index 1afaaa1eef..a480090f68 100644 --- a/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java +++ b/spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/outbound/config/HazelcastOutboundChannelAdapterConfigTests.java @@ -20,9 +20,8 @@ import java.util.List; import java.util.Queue; import java.util.Set; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -31,9 +30,7 @@ import org.springframework.integration.hazelcast.HazelcastTestRequestHandlerAdvi import org.springframework.integration.hazelcast.outbound.util.HazelcastOutboundChannelAdapterTestUtils; import org.springframework.messaging.MessageChannel; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.hazelcast.instance.impl.HazelcastInstanceFactory; import com.hazelcast.map.IMap; @@ -45,11 +42,11 @@ import com.hazelcast.topic.ITopic; * Hazelcast Outbound Channel Adapter JavaConfig driven Unit Test Class * * @author Eren Avsarogullari + * @author Atem Bilan + * * @since 6.0 */ -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = HazelcastIntegrationOutboundTestConfiguration.class, - loader = AnnotationConfigContextLoader.class) +@SpringJUnitConfig(classes = HazelcastIntegrationOutboundTestConfiguration.class) @DirtiesContext public class HazelcastOutboundChannelAdapterConfigTests { @@ -141,7 +138,7 @@ public class HazelcastOutboundChannelAdapterConfigTests { @Qualifier("replicatedMapRequestHandlerAdvice") private HazelcastTestRequestHandlerAdvice replicatedMapRequestHandlerAdvice; - @AfterClass + @AfterAll public static void shutdown() { HazelcastInstanceFactory.terminateAll(); } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJavaConfigTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJavaConfigTests.java index 8d2e769eb1..a56685cf1b 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJavaConfigTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJavaConfigTests.java @@ -80,9 +80,9 @@ public class StoredProcJavaConfigTests { // verify maxMessagesPerPoll == 1 assertThat(received).isNull(); MessagingTemplate template = new MessagingTemplate(this.control); - template.convertAndSend("@'storedProcJavaConfigTests.Config.storedProc.inboundChannelAdapter'.stop()"); + template.convertAndSend("@'storedProc.inboundChannelAdapter'.stop()"); assertThat(template.convertSendAndReceive( - "@'storedProcJavaConfigTests.Config.storedProc.inboundChannelAdapter'.isRunning()", Boolean.class)) + "@'storedProc.inboundChannelAdapter'.isRunning()", Boolean.class)) .isFalse(); } diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java b/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java index 468e29267d..0581570ac7 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java @@ -139,14 +139,14 @@ public class JmsTests extends ActiveMQMultiContextTests { @Test public void testPollingFlow() { - this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.start()"); + this.controlBus.send("@'integerMessageSource.inboundChannelAdapter'.start()"); assertThat(this.beanFactory.getBean("integerChannel")).isInstanceOf(FixedSubscriberChannel.class); for (int i = 0; i < 5; i++) { Message message = this.outputChannel.receive(20000); assertThat(message).isNotNull(); assertThat(message.getPayload()).isEqualTo("" + i); } - this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.stop()"); + this.controlBus.send("@'integerMessageSource.inboundChannelAdapter'.stop()"); assertThat(((InterceptableChannel) this.outputChannel).getInterceptors()) .contains(this.testChannelInterceptor); @@ -353,7 +353,7 @@ public class JmsTests extends ActiveMQMultiContextTests { public IntegrationFlow jmsMessageDrivenFlow() { return IntegrationFlow .from(Jms.messageDrivenChannelAdapter(amqFactory, - DefaultMessageListenerContainer.class) + DefaultMessageListenerContainer.class) .outputChannel(jmsMessageDrivenInputChannel()) .destination("jmsMessageDriven") .configureListenerContainer(c -> c.clientId("foo"))) @@ -406,23 +406,23 @@ public class JmsTests extends ActiveMQMultiContextTests { @Bean public IntegrationFlow jmsInboundGatewayFlow() { return IntegrationFlow.from( - Jms.inboundGateway(amqFactory) - .requestChannel(jmsInboundGatewayInputChannel()) - .replyTimeout(1) - .errorOnTimeout(true) - .errorChannel(new FixedSubscriberChannel(new AbstractReplyProducingMessageHandler() { + Jms.inboundGateway(amqFactory) + .requestChannel(jmsInboundGatewayInputChannel()) + .replyTimeout(1) + .errorOnTimeout(true) + .errorChannel(new FixedSubscriberChannel(new AbstractReplyProducingMessageHandler() { - @Override - protected Object handleRequestMessage(Message requestMessage) { - return "error: " + - ((MessageTimeoutException) requestMessage.getPayload()) - .getFailedMessage().getPayload() + " is not convertible"; - } + @Override + protected Object handleRequestMessage(Message requestMessage) { + return "error: " + + ((MessageTimeoutException) requestMessage.getPayload()) + .getFailedMessage().getPayload() + " is not convertible"; + } - })) - .requestDestination("jmsPipelineTest") - .configureListenerContainer(c -> - c.transactionManager(mock(PlatformTransactionManager.class)))) + })) + .requestDestination("jmsPipelineTest") + .configureListenerContainer(c -> + c.transactionManager(mock(PlatformTransactionManager.class)))) .filter(payload -> !"junk".equals(payload)) .transform(String::toUpperCase) .get(); diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java index 0d50757b20..a21928e59d 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java @@ -34,13 +34,14 @@ import org.springframework.integration.aggregator.ExpressionEvaluatingMessageGro import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy; import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy; import org.springframework.integration.aggregator.MessageCountReleaseStrategy; +import org.springframework.integration.annotation.BridgeFrom; +import org.springframework.integration.annotation.BridgeTo; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; -import org.springframework.integration.handler.BridgeHandler; import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport; import org.springframework.integration.router.RecipientListRouter; import org.springframework.integration.scattergather.ScatterGatherHandler; @@ -58,6 +59,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** * @author Artem Bilan * @author Gary Russell + * * @since 4.1 */ @SpringJUnitConfig @@ -79,7 +81,6 @@ public class ScatterGatherHandlerIntegrationTests { @Test public void testSimpleAuction() { Message quoteMessage = MessageBuilder.withPayload("testQuote").build(); - this.inputAuctionWithoutGatherChannel.send(quoteMessage); Message bestQuoteMessage = this.output.receive(10000); assertThat(bestQuoteMessage).isNotNull(); @@ -92,7 +93,6 @@ public class ScatterGatherHandlerIntegrationTests { @Test public void testAuctionWithGatherChannel() { Message quoteMessage = MessageBuilder.withPayload("testQuote").build(); - this.inputAuctionWithGatherChannel.send(quoteMessage); Message bestQuoteMessage = this.output.receive(10000); assertThat(bestQuoteMessage).isNotNull(); @@ -104,7 +104,6 @@ public class ScatterGatherHandlerIntegrationTests { @Test public void testDistribution() { Message quoteMessage = MessageBuilder.withPayload("testQuote").build(); - this.distributionChannel.send(quoteMessage); Message bestQuoteMessage = this.output.receive(10000); assertThat(bestQuoteMessage).isNotNull(); @@ -145,51 +144,25 @@ public class ScatterGatherHandlerIntegrationTests { new ExpressionEvaluatingReleaseStrategy("size() == 2")); } - @Bean - public MessageChannel inputAuctionWithoutGatherChannel() { - return new DirectChannel(); - } - @Bean @ServiceActivator(inputChannel = "inputAuctionWithoutGatherChannel") - public MessageHandler scatterGatherAuctionWithoutGatherChannel() { + public ScatterGatherHandler scatterGatherAuctionWithoutGatherChannel() { ScatterGatherHandler handler = new ScatterGatherHandler(scatterAuctionWithoutGatherChannel(), gatherer1()); handler.setOutputChannel(output()); return handler; } @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel") - public MessageHandler auctionWithoutGatherChannelBridge1() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel1()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel") - public MessageHandler auctionWithoutGatherChannelBridge2() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel1()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel") - public MessageHandler auctionWithoutGatherChannelBridge3() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel1()); - return handler; - } - - @Bean + @BridgeFrom("scatterAuctionWithoutGatherChannel") + @BridgeFrom("scatterAuctionWithoutGatherChannel") + @BridgeFrom("scatterAuctionWithoutGatherChannel") public MessageChannel serviceChannel1() { return new DirectChannel(); } @Bean @ServiceActivator(inputChannel = "serviceChannel1") - public MessageHandler service1() { + public AbstractReplyProducingMessageHandler service1() { return new AbstractReplyProducingMessageHandler() { @Override @@ -201,7 +174,7 @@ public class ScatterGatherHandlerIntegrationTests { } @Bean - public MessageHandler distributor() { + public RecipientListRouter distributor() { RecipientListRouter router = new RecipientListRouter(); router.setApplySequence(true); router.setChannels(Arrays.asList(distributionChannel1(), distributionChannel2(), distributionChannel3())); @@ -209,16 +182,19 @@ public class ScatterGatherHandlerIntegrationTests { } @Bean + @BridgeTo("serviceChannel1") public MessageChannel distributionChannel1() { return new DirectChannel(); } @Bean + @BridgeTo("serviceChannel1") public MessageChannel distributionChannel2() { return new DirectChannel(); } @Bean + @BridgeTo("serviceChannel1") public MessageChannel distributionChannel3() { return new DirectChannel(); } @@ -230,36 +206,12 @@ public class ScatterGatherHandlerIntegrationTests { @Bean @ServiceActivator(inputChannel = "distributionChannel") - public MessageHandler scatterGatherDistribution() { + public ScatterGatherHandler scatterGatherDistribution() { ScatterGatherHandler handler = new ScatterGatherHandler(distributor(), gatherer1()); handler.setOutputChannel(output()); return handler; } - @Bean - @ServiceActivator(inputChannel = "distributionChannel1") - public MessageHandler distributionBridge1() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel1()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "distributionChannel2") - public MessageHandler distributionBridge2() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel1()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "distributionChannel3") - public MessageHandler distributionBridge3() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel1()); - return handler; - } - @Bean public MessageChannel inputAuctionWithGatherChannel() { return new DirectChannel(); @@ -288,7 +240,7 @@ public class ScatterGatherHandlerIntegrationTests { @Bean @ServiceActivator(inputChannel = "inputAuctionWithGatherChannel") - public MessageHandler scatterGatherAuctionWithGatherChannel() { + public ScatterGatherHandler scatterGatherAuctionWithGatherChannel() { ScatterGatherHandler handler = new ScatterGatherHandler(scatterAuctionWithGatherChannel(null), gatherer2()); handler.setGatherChannel(gatherChannel()); @@ -297,38 +249,10 @@ public class ScatterGatherHandlerIntegrationTests { } @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") - public MessageHandler auctionWithGatherChannelBridge1() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel2()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") - public MessageHandler auctionWithGatherChannelBridge2() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel2()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") - public MessageHandler auctionWithGatherChannelBridge3() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel2()); - return handler; - } - - @Bean - @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") - public MessageHandler auctionWithGatherChannelBridge4() { - BridgeHandler handler = new BridgeHandler(); - handler.setOutputChannel(serviceChannel2()); - return handler; - } - - @Bean + @BridgeFrom("scatterAuctionWithGatherChannel") + @BridgeFrom("scatterAuctionWithGatherChannel") + @BridgeFrom("scatterAuctionWithGatherChannel") + @BridgeFrom("scatterAuctionWithGatherChannel") public MessageChannel serviceChannel2() { return new DirectChannel(); } diff --git a/spring-integration-test/src/test/java/org/springframework/integration/test/mock/MockMessageSourceTests.java b/spring-integration-test/src/test/java/org/springframework/integration/test/mock/MockMessageSourceTests.java index 8da4c7d144..23c1e49525 100644 --- a/spring-integration-test/src/test/java/org/springframework/integration/test/mock/MockMessageSourceTests.java +++ b/spring-integration-test/src/test/java/org/springframework/integration/test/mock/MockMessageSourceTests.java @@ -100,9 +100,7 @@ public class MockMessageSourceTests { @Test public void testMockMessageSourceInConfig() { Lifecycle channelAdapter = - this.applicationContext - .getBean("mockMessageSourceTests.Config.testingMessageSource.inboundChannelAdapter", - Lifecycle.class); + this.applicationContext.getBean("testingMessageSource.inboundChannelAdapter", Lifecycle.class); channelAdapter.start(); Message receive = this.results.receive(10_000); diff --git a/src/reference/asciidoc/configuration.adoc b/src/reference/asciidoc/configuration.adoc index b428024050..23edb354ba 100644 --- a/src/reference/asciidoc/configuration.adoc +++ b/src/reference/asciidoc/configuration.adoc @@ -624,7 +624,7 @@ public class MyFlowConfiguration { @Bean @ServiceActivator(inputChannel = "httpChannel") - public MessageHandler httpHandler() { + public HttpRequestExecutingMessageHandler httpHandler() { HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("https://foo/service"); handler.setExpectedResponseType(String.class); handler.setOutputChannelName("outputChannel"); @@ -676,12 +676,15 @@ NOTE: The bean names are generated with the following algorithm: * The `MessageHandler` (`MessageSource`) `@Bean` gets its own standard name from the method name or `name` attribute on the `@Bean`. This works as though there were no messaging annotation on the `@Bean` method. -* The `AbstractEndpoint` bean name is generated with the following pattern: `[configurationComponentName].[methodName].[decapitalizedAnnotationClassShortName]`. -For example, the `SourcePollingChannelAdapter` endpoint for the `consoleSource()` definition <> gets a bean name of `myFlowConfiguration.consoleSource.inboundChannelAdapter`. +* The `AbstractEndpoint` bean name is generated with the following pattern: `[@Bean name].[decapitalizedAnnotationClassShortName]`. +For example, the `SourcePollingChannelAdapter` endpoint for the `consoleSource()` definition <> gets a bean name of `consoleSource.inboundChannelAdapter`. +Unlike with POJO methods, the bean method name is not included in the endpoint bean name. See also <<./overview.adoc#endpoint-bean-names,Endpoint Bean Names>>. +* If `@Bean` cannot be used directly in the target endpoint (not an instance of a `MessageSource`, `AbstractReplyProducingMessageHandler` or `AbstractMessageRouter`), a respective `AbstractStandardMessageHandlerFactoryBean` is registered to delegate to this `@Bean`. +The bean name for this wrapper is generated with the following pattern: `[@Bean name].[decapitalizedAnnotationClassShortName].[handler (or source)]`. IMPORTANT: When using these annotations on `@Bean` definitions, the `inputChannel` must reference a declared bean. -Channels are not automatically declared in this case. +Channels are automatically declared iif not present in the application context yet. [NOTE] =====