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:
committed by
Gary Russell
parent
a9f50d32e4
commit
c23aa70ec8
@@ -67,7 +67,7 @@ public class SimpleActiveIdleMessageSourceAdvice extends AbstractMessageSourceAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> aource) {
|
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
this.trigger.setPeriod(this.idlePollPeriod);
|
this.trigger.setPeriod(this.idlePollPeriod);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.aopalliance.aop.Advice;
|
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.TargetSource;
|
||||||
import org.springframework.aop.framework.Advised;
|
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.DefaultBeanFactoryPointcutAdvisor;
|
||||||
import org.springframework.aop.support.NameMatchMethodPointcut;
|
import org.springframework.aop.support.NameMatchMethodPointcut;
|
||||||
import org.springframework.beans.factory.ListableBeanFactory;
|
import org.springframework.beans.factory.ListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
|
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
|
||||||
import org.springframework.context.annotation.Bean;
|
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 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 List<String> messageHandlerAttributes = new ArrayList<String>();
|
||||||
|
|
||||||
protected final ConfigurableListableBeanFactory beanFactory;
|
protected final ConfigurableListableBeanFactory beanFactory;
|
||||||
@@ -122,6 +127,19 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations) {
|
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);
|
MessageHandler handler = createHandler(bean, method, annotations);
|
||||||
setAdviceChainIfPresent(beanName, annotations, handler);
|
setAdviceChainIfPresent(beanName, annotations, handler);
|
||||||
if (handler instanceof Orderable) {
|
if (handler instanceof Orderable) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ListableBeanFactory;
|
import org.springframework.beans.factory.ListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
@@ -59,7 +60,18 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
|
|||||||
String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
|
String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
|
||||||
Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty.");
|
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);
|
MessageChannel channel = this.channelResolver.resolveDestination(channelName);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.containsString;
|
|||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
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.BeanCreationException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
|
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.ExpressionParser;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.integration.aggregator.AggregatingMessageHandler;
|
import org.springframework.integration.aggregator.AggregatingMessageHandler;
|
||||||
import org.springframework.integration.aggregator.ExpressionEvaluatingCorrelationStrategy;
|
import org.springframework.integration.aggregator.ExpressionEvaluatingCorrelationStrategy;
|
||||||
import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy;
|
import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy;
|
||||||
import org.springframework.integration.aggregator.SimpleMessageGroupProcessor;
|
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.Filter;
|
||||||
import org.springframework.integration.annotation.InboundChannelAdapter;
|
import org.springframework.integration.annotation.InboundChannelAdapter;
|
||||||
import org.springframework.integration.annotation.Poller;
|
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.config.EnableMessageHistory;
|
||||||
import org.springframework.integration.core.MessageSelector;
|
import org.springframework.integration.core.MessageSelector;
|
||||||
import org.springframework.integration.core.MessageSource;
|
import org.springframework.integration.core.MessageSource;
|
||||||
|
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||||
import org.springframework.integration.filter.ExpressionEvaluatingSelector;
|
import org.springframework.integration.filter.ExpressionEvaluatingSelector;
|
||||||
import org.springframework.integration.history.MessageHistory;
|
import org.springframework.integration.history.MessageHistory;
|
||||||
@@ -93,6 +99,26 @@ public class MessagingAnnotationsWithBeanAnnotationTests {
|
|||||||
@Resource(name="collector")
|
@Resource(name="collector")
|
||||||
private List<Message<?>> 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
|
@Test
|
||||||
public void testMessagingAnnotationsFlow() {
|
public void testMessagingAnnotationsFlow() {
|
||||||
this.sourcePollingChannelAdapter.start();
|
this.sourcePollingChannelAdapter.start();
|
||||||
@@ -113,12 +139,18 @@ public class MessagingAnnotationsWithBeanAnnotationTests {
|
|||||||
assertThat(messageHistoryString, Matchers.containsString("serviceChannel"));
|
assertThat(messageHistoryString, Matchers.containsString("serviceChannel"));
|
||||||
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("discardChannel")));
|
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("discardChannel")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertNull(this.skippedServiceActivator);
|
||||||
|
assertNull(this.skippedMessageHandler);
|
||||||
|
assertNull(this.skippedChannel);
|
||||||
|
assertNull(this.skippedChannel2);
|
||||||
|
assertNull(this.skippedMessageSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidMessagingAnnotationsConfig() {
|
public void testInvalidMessagingAnnotationsConfig() {
|
||||||
try {
|
try {
|
||||||
new AnnotationConfigApplicationContext(InvalidContextConfiguration.class);
|
new AnnotationConfigApplicationContext(InvalidContextConfiguration.class).close();
|
||||||
fail("BeanCreationException expected");
|
fail("BeanCreationException expected");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
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
|
@Configuration
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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++) {
|
for (int i = 0; i < numRequests; i++) {
|
||||||
assertEquals("test-" + i + "!!!", results[i]);
|
assertEquals("test-" + i + "!!!", results[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
<beans:property name="serviceInterface" value="org.springframework.integration.gateway.TestService"/>
|
<beans:property name="serviceInterface" value="org.springframework.integration.gateway.TestService"/>
|
||||||
<beans:property name="defaultRequestChannel" ref="requestChannel"/>
|
<beans:property name="defaultRequestChannel" ref="requestChannel"/>
|
||||||
<beans:property name="defaultReplyChannel" ref="replyChannel"/>
|
<beans:property name="defaultReplyChannel" ref="replyChannel"/>
|
||||||
<beans:property name="defaultRequestTimeout" value="5000"/>
|
<beans:property name="defaultRequestTimeout" value="10000"/>
|
||||||
<beans:property name="defaultReplyTimeout" value="5000"/>
|
<beans:property name="defaultReplyTimeout" value="10000"/>
|
||||||
</beans:bean>
|
</beans:bean>
|
||||||
|
|
||||||
<beans:bean id="handler" class="org.springframework.integration.gateway.TestHandler"/>
|
<beans:bean id="handler" class="org.springframework.integration.gateway.TestHandler"/>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Direct usage of the API is of course always an option, but we expect that most u
|
|||||||
=== Namespace Support
|
=== Namespace Support
|
||||||
|
|
||||||
Spring Integration components can be configured with XML elements that map directly to the terminology and concepts of enterprise integration.
|
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:
|
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.
|
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
|
==== 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.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user