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
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> aource) {
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
|
||||
if (result == null) {
|
||||
this.trigger.setPeriod(this.idlePollPeriod);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user