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"/>