From c23aa70ec8b06d0b7b7db1fd6d8a7fdaff4c8eb5 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 22 Sep 2015 17:16:36 -0400 Subject: [PATCH] INT-3829: Honor `@Profile` on Messaging @Beans JIRA: https://jira.spring.io/browse/INT-3829 Alongside with the `@Bean` definition we can use, for example, `@Profile` `@Conditional` annotation. And the final bean won't be populated to the context. When messaging annotations are present on `@Bean`s, a second bean for the endpoint consumer is created. Previously, the `MessagingAnnotationPostProcessor` did not check if the `@Bean` was present; hence we ended up with a `NoSuchBeanDefinitionException` * Add appropriate `try...catch(NoSuchBeanDefinitionException)` to the `AbstractMethodAnnotationPostProcessor` and `InboundChannelAdapterAnnotationPostProcessor` to skip further endpoint processing if the bean does not exist. Add DEBUG message for the skipped beans Polishing --- .../SimpleActiveIdleMessageSourceAdvice.java | 2 +- ...AbstractMethodAnnotationPostProcessor.java | 18 +++++ ...ChannelAdapterAnnotationPostProcessor.java | 14 +++- ...ingAnnotationsWithBeanAnnotationTests.java | 66 ++++++++++++++++++- .../gateway/GatewayProxyFactoryBeanTests.java | 4 +- .../gateway/gatewayWithResponseCorrelator.xml | 4 +- src/reference/asciidoc/configuration.adoc | 16 ++++- 7 files changed, 116 insertions(+), 8 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleMessageSourceAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleMessageSourceAdvice.java index 96e40b287e..0696d0db7f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleMessageSourceAdvice.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleMessageSourceAdvice.java @@ -67,7 +67,7 @@ public class SimpleActiveIdleMessageSourceAdvice extends AbstractMessageSourceAd } @Override - public Message afterReceive(Message result, MessageSource aource) { + public Message afterReceive(Message result, MessageSource source) { if (result == null) { this.trigger.setPeriod(this.idlePollPeriod); } 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 60302199e6..524bd08c5e 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 @@ -24,6 +24,8 @@ import java.util.Collections; import java.util.List; import org.aopalliance.aop.Advice; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; @@ -31,6 +33,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; import org.springframework.aop.support.NameMatchMethodPointcut; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.context.annotation.Bean; @@ -87,6 +90,8 @@ public abstract class AbstractMethodAnnotationPostProcessor messageHandlerAttributes = new ArrayList(); protected final ConfigurableListableBeanFactory beanFactory; @@ -122,6 +127,19 @@ public abstract class AbstractMethodAnnotationPostProcessor annotations) { + if (this.beanAnnotationAware() && AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) { + try { + resolveTargetBeanFromMethodWithBeanAnnotation(method); + } + catch (NoSuchBeanDefinitionException e) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Skipping endpoint creation; " + + e.getMessage() + + "; perhaps due to some '@Conditional' annotation."); + } + return null; + } + } MessageHandler handler = createHandler(bean, method, annotations); setAdviceChainIfPresent(beanName, annotations, handler); if (handler instanceof Orderable) { 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 a3e0f19cb3..2810b64291 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,6 +21,7 @@ import java.lang.reflect.Method; import java.util.List; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; @@ -59,7 +60,18 @@ public class InboundChannelAdapterAnnotationPostProcessor extends String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class); Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty."); - MessageSource messageSource = this.createMessageSource(bean, beanName, method); + MessageSource messageSource = null; + 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; + } MessageChannel channel = this.channelResolver.resolveDestination(channelName); 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 2a3e8d53a7..315054d892 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 @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -36,16 +37,20 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.aggregator.AggregatingMessageHandler; import org.springframework.integration.aggregator.ExpressionEvaluatingCorrelationStrategy; import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy; import org.springframework.integration.aggregator.SimpleMessageGroupProcessor; +import org.springframework.integration.annotation.BridgeFrom; +import org.springframework.integration.annotation.BridgeTo; import org.springframework.integration.annotation.Filter; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.annotation.Poller; @@ -59,6 +64,7 @@ import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.config.EnableMessageHistory; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.core.MessageSource; +import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.filter.ExpressionEvaluatingSelector; import org.springframework.integration.history.MessageHistory; @@ -93,6 +99,26 @@ public class MessagingAnnotationsWithBeanAnnotationTests { @Resource(name="collector") private List> collector; + @Autowired(required = false) + @Qualifier("messagingAnnotationsWithBeanAnnotationTests.ContextConfiguration.skippedMessageHandler.serviceActivator") + private EventDrivenConsumer skippedServiceActivator; + + @Autowired(required = false) + @Qualifier("skippedMessageHandler") + private MessageHandler skippedMessageHandler; + + @Autowired(required = false) + @Qualifier("skippedChannel") + private MessageChannel skippedChannel; + + @Autowired(required = false) + @Qualifier("skippedChannel2") + private MessageChannel skippedChannel2; + + @Autowired(required = false) + @Qualifier("skippedMessageSource") + private MessageSource skippedMessageSource; + @Test public void testMessagingAnnotationsFlow() { this.sourcePollingChannelAdapter.start(); @@ -113,12 +139,18 @@ public class MessagingAnnotationsWithBeanAnnotationTests { assertThat(messageHistoryString, Matchers.containsString("serviceChannel")); assertThat(messageHistoryString, Matchers.not(Matchers.containsString("discardChannel"))); } + + assertNull(this.skippedServiceActivator); + assertNull(this.skippedMessageHandler); + assertNull(this.skippedChannel); + assertNull(this.skippedChannel2); + assertNull(this.skippedMessageSource); } @Test public void testInvalidMessagingAnnotationsConfig() { try { - new AnnotationConfigApplicationContext(InvalidContextConfiguration.class); + new AnnotationConfigApplicationContext(InvalidContextConfiguration.class).close(); fail("BeanCreationException expected"); } catch (Exception e) { @@ -237,6 +269,38 @@ public class MessagingAnnotationsWithBeanAnnotationTests { }; } + @Bean + @ServiceActivator(inputChannel = "skippedChannel") + @Splitter(inputChannel = "skippedChannel2") + @Router(inputChannel = "skippedChannel3") + @Transformer(inputChannel = "skippedChannel4") + @Filter(inputChannel = "skippedChannel5") + @Profile("foo") + public MessageHandler skippedMessageHandler() { + return System.out::println; + } + + @Bean + @BridgeFrom("skippedChannel6") + @Profile("foo") + public MessageChannel skippedChannel1() { + return new DirectChannel(); + } + + @Bean + @BridgeTo + @Profile("foo") + public MessageChannel skippedChannel2() { + return new DirectChannel(); + } + + @Bean + @InboundChannelAdapter("serviceChannel") + @Profile("foo") + public MessageSource skippedMessageSource() { + return () -> new GenericMessage<>("foo"); + } + } @Configuration diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayProxyFactoryBeanTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayProxyFactoryBeanTests.java index 3072d4e858..973e84a083 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayProxyFactoryBeanTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayProxyFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -206,7 +206,7 @@ public class GatewayProxyFactoryBeanTests { } }); } - latch.await(10, TimeUnit.SECONDS); + latch.await(30, TimeUnit.SECONDS); for (int i = 0; i < numRequests; i++) { assertEquals("test-" + i + "!!!", results[i]); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/gatewayWithResponseCorrelator.xml b/spring-integration-core/src/test/java/org/springframework/integration/gateway/gatewayWithResponseCorrelator.xml index 086a88cd50..4f329ba835 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/gatewayWithResponseCorrelator.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/gatewayWithResponseCorrelator.xml @@ -22,8 +22,8 @@ - - + + diff --git a/src/reference/asciidoc/configuration.adoc b/src/reference/asciidoc/configuration.adoc index 55ef08ac47..a154a0929f 100644 --- a/src/reference/asciidoc/configuration.adoc +++ b/src/reference/asciidoc/configuration.adoc @@ -16,7 +16,7 @@ Direct usage of the API is of course always an option, but we expect that most u === Namespace Support Spring Integration components can be configured with XML elements that map directly to the terminology and concepts of enterprise integration. -In many cases, the element names match those of thehttp://www.eaipatterns.com[Enterprise Integration Patterns]. +In many cases, the element names match those of the http://www.eaipatterns.com[Enterprise Integration Patterns]. To enable Spring Integration's core namespace support within your Spring configuration files, add the following namespace reference and schema mapping in your top-level 'beans' element: @@ -532,6 +532,20 @@ For example the endpoint (`SourcePollingChannelAdapter`) for the `consoleSource( IMPORTANT: When using these annotations on `@Bean` definitions, the `inputChannel` must reference a declared bean; channels are not automatically declared in this case. +NOTE: With Java & Annotation configuration we can use any `@Conditional` (e.g. `@Profile`) definition on the `@Bean` +method level, meaning to skip the bean registration by some condition reason: +[source,java] +---- +@Bean +@ServiceActivator(inputChannel = "skippedChannel") +@Profile("foo") +public MessageHandler skipped() { + return System.out::println; +} +---- +Together with the existing Spring Container logic, the Messaging Endpoint bean, based on the `@ServiceActivator` + annotation, won't be registered as well. + ==== Creating a Bridge with Annotations Starting with _version 4.0_, the Messaging Annotation and Java configuration provides `@BridgeFrom` and `@BridgeTo` `@Bean` method annotations to mark `MessageChannel` beans in `@Configuration` classes.