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
This commit is contained in:
Artem Bilan
2015-09-22 17:16:36 -04:00
committed by Gary Russell
parent a9f50d32e4
commit c23aa70ec8
7 changed files with 116 additions and 8 deletions

View File

@@ -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);
}

View File

@@ -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<T extends Annotation
protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout";
protected final Log logger = LogFactory.getLog(this.getClass());
protected final List<String> messageHandlerAttributes = new ArrayList<String>();
protected final ConfigurableListableBeanFactory beanFactory;
@@ -122,6 +127,19 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
@Override
public Object postProcess(Object bean, String beanName, Method method, List<Annotation> 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) {

View File

@@ -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);

View File

@@ -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<Message<?>> 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

View File

@@ -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]);
}

View File

@@ -22,8 +22,8 @@
<beans:property name="serviceInterface" value="org.springframework.integration.gateway.TestService"/>
<beans:property name="defaultRequestChannel" ref="requestChannel"/>
<beans:property name="defaultReplyChannel" ref="replyChannel"/>
<beans:property name="defaultRequestTimeout" value="5000"/>
<beans:property name="defaultReplyTimeout" value="5000"/>
<beans:property name="defaultRequestTimeout" value="10000"/>
<beans:property name="defaultReplyTimeout" value="10000"/>
</beans:bean>
<beans:bean id="handler" class="org.springframework.integration.gateway.TestHandler"/>

View File

@@ -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.