GH-3844: Rework messaging annotation with @Bean (#3877)

* GH-3844: Rework messaging annotation with @Bean

Fixes https://github.com/spring-projects/spring-integration/issues/3844

* Make `MessagingAnnotationPostProcessor` as a `BeanDefinitionRegistryPostProcessor`
to process bean definitions as early as possible and register respective messaging
components at that early phase
* Make bean definitions parsing logic optional for AOT and native mode since beans
have bean parsed during AOT building phase
* Introduce a `BeanDefinitionPropertiesMapper` for easier mapping
of the annotation attributes to the target `BeanDefinition`
* Remove `@Bean`-related logic from method parsing process
* Change the logic for `@Bean`-based endpoint bean names:
since we don't deal with methods on the bean definition phase, then method name
does not make sense.
It even may mislead if we `@Bean` name is based on a method by default, so we end up
with duplicated word in the target endpoint bean name.
Now we don't
* Fix `configuration.adoc` respectively for a new endpoint bean name logic
* In the end the new logic in the `AbstractMethodAnnotationPostProcessor`
is similar to XML parsers: we feed annotation attributes to the
`AbstractStandardMessageHandlerFactoryBean` impls

* * Fix language in docs and exception message
This commit is contained in:
Artem Bilan
2022-08-22 12:38:23 -04:00
committed by GitHub
parent c1dbb02c51
commit ca138c0c06
33 changed files with 1136 additions and 819 deletions

View File

@@ -22,12 +22,9 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
@@ -36,7 +33,7 @@ import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.junit.BrokerRunning;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,7 +56,7 @@ import org.springframework.integration.support.AbstractIntegrationMessageBuilder
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Gary Russell
@@ -67,23 +64,24 @@ import org.springframework.test.context.junit4.SpringRunner;
* @since 5.0.1
*
*/
@RunWith(SpringRunner.class)
@SpringJUnitConfig
@DirtiesContext
@RabbitAvailable({
AmqpMessageSourceIntegrationTests.DSL_QUEUE,
AmqpMessageSourceIntegrationTests.INTERCEPT_QUEUE,
AmqpMessageSourceIntegrationTests.DLQ,
AmqpMessageSourceIntegrationTests.NOAUTOACK_QUEUE })
public class AmqpMessageSourceIntegrationTests {
private static final String DSL_QUEUE = "AmqpMessageSourceIntegrationTests";
static final String DSL_QUEUE = "AmqpMessageSourceIntegrationTests";
private static final String QUEUE_WITH_DLQ = "AmqpMessageSourceIntegrationTests.withDLQ";
static final String QUEUE_WITH_DLQ = "AmqpMessageSourceIntegrationTests.withDLQ";
private static final String DLQ = QUEUE_WITH_DLQ + ".dlq";
static final String DLQ = QUEUE_WITH_DLQ + ".dlq";
private static final String INTERCEPT_QUEUE = "AmqpMessageSourceIntegrationTests.channel";
static final String INTERCEPT_QUEUE = "AmqpMessageSourceIntegrationTests.channel";
private static final String NOAUTOACK_QUEUE = "AmqpMessageSourceIntegrationTests.noAutoAck";
@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues(DSL_QUEUE, INTERCEPT_QUEUE, DLQ,
NOAUTOACK_QUEUE);
static final String NOAUTOACK_QUEUE = "AmqpMessageSourceIntegrationTests.noAutoAck";
@Autowired
private Config config;
@@ -91,7 +89,7 @@ public class AmqpMessageSourceIntegrationTests {
@Autowired
private ConfigurableApplicationContext context;
@Before
@BeforeEach
public void before() {
RabbitAdmin admin = new RabbitAdmin(this.config.connectionFactory());
Queue queue = QueueBuilder.nonDurable(QUEUE_WITH_DLQ)
@@ -103,16 +101,11 @@ public class AmqpMessageSourceIntegrationTests {
this.context.start();
}
@After
@AfterEach
public void after() {
this.context.stop();
}
@AfterClass
public static void afterClass() {
brokerRunning.removeTestQueues(QUEUE_WITH_DLQ);
}
@Test
public void testImplicitNackThenAck() throws Exception {
RabbitTemplate template = new RabbitTemplate(this.config.connectionFactory());
@@ -256,7 +249,7 @@ public class AmqpMessageSourceIntegrationTests {
@Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
return new CachingConnectionFactory("localhost");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 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.
@@ -95,8 +95,7 @@ public class IdempotentReceiverAutoProxyCreatorInitializer implements Integratio
List<Map<String, String>> idempotentEndpointsMapping, String beanName, BeanDefinition beanDefinition)
throws LinkageError {
if (beanDefinition.getSource() instanceof MethodMetadata) {
MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource();
if (beanDefinition.getSource() instanceof MethodMetadata beanMethod) {
String annotationType = IdempotentReceiver.class.getName();
if (beanMethod.isAnnotated(annotationType)) { // NOSONAR never null
Object value = beanMethod.getAnnotationAttributes(annotationType).get("value"); // NOSONAR
@@ -120,13 +119,7 @@ public class IdempotentReceiverAutoProxyCreatorInitializer implements Integratio
String endpoint = beanName;
if (!MessageHandler.class.isAssignableFrom(returnType)) {
/*
MessageHandler beans, populated from @Bean methods, have a complex id,
including @Configuration bean name, method name and the Messaging annotation name.
The following pattern matches the bean name, regardless of the annotation name.
*/
endpoint = beanDefinition.getFactoryBeanName() + "." + beanName +
".*" + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX;
endpoint = beanName + ".*" + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX;
}
String[] interceptors = (String[]) value;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -19,6 +19,7 @@ package org.springframework.integration.config;
import java.util.Map;
import org.springframework.expression.Expression;
import org.springframework.integration.JavaUtils;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.router.AbstractMappingMessageRouter;
@@ -49,6 +50,10 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean
private String defaultOutputChannelName;
private String prefix;
private String suffix;
private Boolean resolutionRequired;
private Boolean applySequence;
@@ -63,6 +68,14 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean
this.defaultOutputChannelName = defaultOutputChannelName;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public void setResolutionRequired(Boolean resolutionRequired) {
this.resolutionRequired = resolutionRequired;
}
@@ -106,7 +119,7 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean
@Override
protected MessageHandler createExpressionEvaluatingHandler(Expression expression) {
return this.configureRouter(new ExpressionEvaluatingRouter(expression));
return configureRouter(new ExpressionEvaluatingRouter(expression));
}
protected AbstractMappingMessageRouter createMethodInvokingRouter(Object targetObject, String targetMethodName) {
@@ -116,34 +129,25 @@ public class RouterFactoryBean extends AbstractStandardMessageHandlerFactoryBean
}
protected AbstractMessageRouter configureRouter(AbstractMessageRouter router) {
if (this.defaultOutputChannel != null) {
router.setDefaultOutputChannel(this.defaultOutputChannel);
}
if (this.defaultOutputChannelName != null) {
router.setDefaultOutputChannelName(this.defaultOutputChannelName);
}
if (getSendTimeout() != null) {
router.setSendTimeout(getSendTimeout());
}
if (this.applySequence != null) {
router.setApplySequence(this.applySequence);
}
if (this.ignoreSendFailures != null) {
router.setIgnoreSendFailures(this.ignoreSendFailures);
}
JavaUtils.INSTANCE
.acceptIfNotNull(this.defaultOutputChannel, router::setDefaultOutputChannel)
.acceptIfNotNull(this.defaultOutputChannelName, router::setDefaultOutputChannelName)
.acceptIfNotNull(getSendTimeout(), router::setSendTimeout)
.acceptIfNotNull(this.applySequence, router::setApplySequence)
.acceptIfNotNull(this.ignoreSendFailures, router::setIgnoreSendFailures);
if (router instanceof AbstractMappingMessageRouter) {
this.configureMappingRouter((AbstractMappingMessageRouter) router);
configureMappingRouter((AbstractMappingMessageRouter) router);
}
return router;
}
protected void configureMappingRouter(AbstractMappingMessageRouter router) {
if (this.channelMappings != null) {
router.setChannelMappings(this.channelMappings);
}
if (this.resolutionRequired != null) {
router.setResolutionRequired(this.resolutionRequired);
}
JavaUtils.INSTANCE
.acceptIfNotNull(this.channelMappings, router::setChannelMappings)
.acceptIfNotNull(this.resolutionRequired, router::setResolutionRequired)
.acceptIfHasText(this.prefix, router::setPrefix)
.acceptIfHasText(this.suffix, router::setSuffix);
}
@Override

View File

@@ -20,43 +20,63 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.integration.annotation.IdempotentReceiver;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.annotation.Reactive;
import org.springframework.integration.annotation.Role;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessagePublishingErrorHandler;
import org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean;
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.config.RouterFactoryBean;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.context.Orderable;
import org.springframework.integration.endpoint.AbstractEndpoint;
@@ -68,7 +88,6 @@ import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.LambdaMessageProcessor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper;
import org.springframework.integration.handler.advice.HandleMessageAdvice;
@@ -106,67 +125,300 @@ import reactor.core.publisher.Flux;
* @author Chris Bono
*/
public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation>
implements MethodAnnotationPostProcessor<T> {
implements MethodAnnotationPostProcessor<T>, BeanFactoryAware {
private static final String UNCHECKED = "unchecked";
private static final String INPUT_CHANNEL_ATTRIBUTE = "inputChannel";
private static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain";
protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout";
protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR
protected static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain"; // NOSONAR
protected static final String SEND_TIMEOUT_ATTRIBUTE = "sendTimeout"; // NOSONAR
protected final List<String> messageHandlerAttributes = new ArrayList<>(); // NOSONAR
protected final ConfigurableListableBeanFactory beanFactory; // NOSONAR
protected final BeanDefinitionRegistry definitionRegistry; // NOSONAR
protected final ConversionService conversionService; // NOSONAR
protected final DestinationResolver<MessageChannel> channelResolver; // NOSONAR
protected final Class<T> annotationType; // NOSONAR
private ConfigurableListableBeanFactory beanFactory;
private BeanDefinitionRegistry definitionRegistry;
private ConversionService conversionService;
private DestinationResolver<MessageChannel> channelResolver;
@SuppressWarnings(UNCHECKED)
public AbstractMethodAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "'beanFactory' must not be null");
public AbstractMethodAnnotationPostProcessor() {
this.messageHandlerAttributes.add(SEND_TIMEOUT_ATTRIBUTE);
this.beanFactory = beanFactory;
this.annotationType =
(Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), MethodAnnotationPostProcessor.class);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
this.definitionRegistry = (BeanDefinitionRegistry) beanFactory;
this.conversionService = this.beanFactory.getConversionService() != null
? this.beanFactory.getConversionService()
: DefaultConversionService.getSharedInstance();
this.channelResolver = ChannelResolverUtils.getChannelResolver(beanFactory);
this.annotationType =
(Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(),
MethodAnnotationPostProcessor.class);
}
protected ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
protected BeanDefinitionRegistry getDefinitionRegistry() {
return this.definitionRegistry;
}
protected ConversionService getConversionService() {
return this.conversionService;
}
protected DestinationResolver<MessageChannel> getChannelResolver() {
return this.channelResolver;
}
@Override
public void processBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
List<Annotation> annotations) {
ResolvableType handlerBeanType = getHandlerBeanClass(beanDefinition);
String handlerBeanName = beanName;
BeanDefinition handlerBeanDefinition =
resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations);
MergedAnnotations mergedAnnotations = beanDefinition.getFactoryMethodMetadata().getAnnotations();
if (handlerBeanDefinition != null) {
if (handlerBeanDefinition != beanDefinition) {
Class<?> handlerBeanClass =
org.springframework.util.ClassUtils.resolveClassName(handlerBeanDefinition.getBeanClassName(),
this.beanFactory.getBeanClassLoader());
if (isClassIn(handlerBeanClass, Orderable.class, AbstractSimpleMessageHandlerFactoryBean.class)) {
mergedAnnotations.get(Order.class)
.getValue(AnnotationUtils.VALUE, String.class).
ifPresent((order) -> handlerBeanDefinition.getPropertyValues().add("order", order));
}
if (isClassIn(handlerBeanClass, AbstractMessageProducingHandler.class, AbstractMessageRouter.class,
AbstractSimpleMessageHandlerFactoryBean.class)) {
new BeanDefinitionPropertiesMapper(handlerBeanDefinition, annotations)
.setPropertyValue(SEND_TIMEOUT_ATTRIBUTE)
.setPropertyReference("outputChannel")
.setPropertyReference("defaultOutputChannel");
}
if (isClassIn(handlerBeanClass, AbstractMessageProducingHandler.class,
AbstractSimpleMessageHandlerFactoryBean.class)
&& !RouterFactoryBean.class.isAssignableFrom(handlerBeanClass)) {
applyAdviceChainIfAny(handlerBeanDefinition, annotations);
}
handlerBeanName = generateHandlerBeanName(beanName, mergedAnnotations);
this.definitionRegistry.registerBeanDefinition(handlerBeanName, handlerBeanDefinition);
}
}
else {
throw new BeanDefinitionValidationException(
"The messaging annotation processor for '" + this.annotationType +
"' cannot create a target handler bean. " +
"The bean definition with the problem is: " + beanName);
}
BeanDefinition endpointBeanDefinition =
createEndpointBeanDefinition(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName),
new BeanComponentDefinition(beanDefinition, beanName), annotations);
new BeanDefinitionPropertiesMapper(endpointBeanDefinition, annotations)
.setPropertyValue("autoStartup")
.setPropertyValue("phase");
Poller poller = MessagingAnnotationUtils.resolveAttribute(annotations, "poller", Poller.class);
Reactive reactive = MessagingAnnotationUtils.resolveAttribute(annotations, "reactive", Reactive.class);
Assert.state(reactive == null || poller == null,
() -> "The 'poller' and 'reactive' attributes are mutually exclusive." +
"The bean definition with the problem is: " + beanName);
if (poller != null) {
applyPollerForEndpoint(endpointBeanDefinition, poller);
}
else if (reactive != null) {
BeanMetadataElement reactiveCustomizer;
String reactiveCustomizerBean = reactive.value();
if (StringUtils.hasText(reactiveCustomizerBean)) {
reactiveCustomizer = new RuntimeBeanReference(reactiveCustomizerBean);
}
else {
reactiveCustomizer =
BeanDefinitionBuilder.genericBeanDefinition(Function.class)
.setFactoryMethod("identity")
.getBeanDefinition();
}
endpointBeanDefinition.getPropertyValues().add("reactiveCustomizer", reactiveCustomizer);
}
mergedAnnotations.get(Role.class)
.getValue(AnnotationUtils.VALUE, String.class).
ifPresent((role) -> endpointBeanDefinition.getPropertyValues().add("role", role));
String endpointBeanName =
generateHandlerBeanName(beanName, mergedAnnotations)
.replaceFirst("\\.(handler|source)$", "");
this.definitionRegistry.registerBeanDefinition(endpointBeanName, endpointBeanDefinition);
}
private static boolean isClassIn(Class<?> classToVerify, Class<?>... classesToMatch) {
for (Class<?> toMatch : classesToMatch) {
if (toMatch.isAssignableFrom(classToVerify)) {
return true;
}
}
return false;
}
protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition,
ComponentDefinition beanDefinition, List<Annotation> annotations) {
BeanDefinitionBuilder endpointDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class)
.addPropertyReference("handler", handlerBeanDefinition.getName());
String inputChannelAttribute = getInputChannelAttribute();
String inputChannelName =
MessagingAnnotationUtils.resolveAttribute(annotations, inputChannelAttribute, String.class);
Assert.hasText(inputChannelName,
() -> "The '" + inputChannelAttribute + "' attribute is required on '" + this.annotationType +
"' on @Bean method");
if (!this.definitionRegistry.containsBeanDefinition(inputChannelName)) {
this.definitionRegistry.registerBeanDefinition(inputChannelName,
new RootBeanDefinition(DirectChannel.class));
}
BeanDefinition endpointBeanDefinition =
endpointDefinitionBuilder.addPropertyReference("inputChannel", inputChannelName)
.getBeanDefinition();
applyAdviceChainIfAny(endpointBeanDefinition, annotations);
return endpointBeanDefinition;
}
private static void applyAdviceChainIfAny(BeanDefinition beanDefinition, List<Annotation> annotations) {
String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE,
String[].class);
if (!ObjectUtils.isEmpty(adviceChainNames)) {
ManagedList<RuntimeBeanReference> adviceChain =
Arrays.stream(adviceChainNames)
.map(RuntimeBeanReference::new)
.collect(Collectors.toCollection(ManagedList::new));
beanDefinition.getPropertyValues().addPropertyValue("adviceChain", adviceChain);
}
}
private static void applyPollerForEndpoint(BeanDefinition endpointBeanDefinition, Poller poller) {
BeanMetadataElement pollerMetadataBeanDefinition;
String ref = poller.value();
if (StringUtils.hasText(ref)) {
pollerMetadataBeanDefinition = new RuntimeBeanReference(ref);
}
else {
BeanDefinitionBuilder pollerBeanDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(PollerMetadata.class);
new BeanDefinitionPropertiesMapper(pollerBeanDefinitionBuilder.getRawBeanDefinition(), List.of(poller))
.setPropertyReference("trigger")
.setPropertyReference("taskExecutor")
.setPropertyValue("receiveTimeout")
.setPropertyValue("maxMessagesPerPoll");
String errorChannel = poller.errorChannel();
if (StringUtils.hasText(errorChannel)) {
BeanDefinitionBuilder errorHandler =
BeanDefinitionBuilder.genericBeanDefinition(MessagePublishingErrorHandler.class)
.addPropertyReference("defaultErrorChannel", errorChannel);
pollerBeanDefinitionBuilder.addPropertyValue("errorHandler", errorHandler.getBeanDefinition());
}
String fixedDelay = poller.fixedDelay();
String fixedRate = poller.fixedRate();
String cron = poller.cron();
BeanDefinition triggerBeanDefinition = null;
if (StringUtils.hasText(cron)) {
triggerBeanDefinition = triggerBeanDefinition(CronTrigger.class, cron);
}
else if (StringUtils.hasText(fixedDelay)) {
triggerBeanDefinition = triggerBeanDefinition(PeriodicTrigger.class, fixedDelay);
}
else if (StringUtils.hasText(fixedRate)) {
triggerBeanDefinition = triggerBeanDefinition(PeriodicTrigger.class, fixedRate);
triggerBeanDefinition.getPropertyValues().addPropertyValue("fixedRate", true);
}
if (triggerBeanDefinition != null) {
pollerBeanDefinitionBuilder.addPropertyValue("trigger", triggerBeanDefinition);
}
pollerMetadataBeanDefinition = pollerBeanDefinitionBuilder.getBeanDefinition();
}
endpointBeanDefinition.getPropertyValues()
.addPropertyValue("pollerMetadata", pollerMetadataBeanDefinition);
}
private static BeanDefinition triggerBeanDefinition(Class<? extends Trigger> triggerClass, String triggerValue) {
return BeanDefinitionBuilder.genericBeanDefinition(triggerClass)
.addConstructorArgValue(triggerValue)
.getBeanDefinition();
}
private ResolvableType getHandlerBeanClass(AnnotatedBeanDefinition beanDefinition) {
MethodMetadata factoryMethodMetadata = beanDefinition.getFactoryMethodMetadata();
if (factoryMethodMetadata instanceof StandardMethodMetadata standardMethodMetadata) {
return ResolvableType.forMethodReturnType(standardMethodMetadata.getIntrospectedMethod());
}
else {
String typeName = factoryMethodMetadata.getReturnTypeName();
Class<?> beanClass =
org.springframework.util.ClassUtils.resolveClassName(typeName,
this.beanFactory.getBeanClassLoader());
return ResolvableType.forClass(beanClass);
}
}
@Nullable
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotations) {
Class<?> classToCheck = handlerBeanType.toClass();
if (FactoryBean.class.isAssignableFrom(classToCheck)) {
classToCheck = this.beanFactory.getType(beanName);
}
if (isClassIn(classToCheck, AbstractReplyProducingMessageHandler.class, AbstractMessageRouter.class)) {
checkMessageHandlerAttributes(beanName, annotations);
return beanDefinition;
}
return null;
}
@Override
public Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations) {
Object sourceHandler = null;
if (beanAnnotationAware() && AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
if (!this.beanFactory.containsBeanDefinition(resolveTargetBeanName(method))) {
this.logger.debug("Skipping endpoint creation; perhaps due to some '@Conditional' annotation.");
return null;
}
else {
sourceHandler = resolveTargetBeanFromMethodWithBeanAnnotation(method);
}
}
MessageHandler handler = createHandler(bean, method, annotations);
if (!(handler instanceof ReactiveMessageHandlerAdapter)) {
orderable(method, handler);
producerOrRouter(annotations, handler);
if (!handler.equals(sourceHandler)) {
handler = registerHandlerBean(beanName, method, handler);
}
handler = registerHandlerBean(beanName, method, handler);
handler = annotated(method, handler);
handler = adviceChain(beanName, annotations, handler);
@@ -270,27 +522,6 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return handler;
}
@Override
public boolean shouldCreateEndpoint(Method method, List<Annotation> annotations) {
String inputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(),
String.class);
boolean createEndpoint = StringUtils.hasText(inputChannel);
if (!createEndpoint && beanAnnotationAware()) {
boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName());
Assert.isTrue(!isBean, () -> "A channel name in '" + getInputChannelAttribute() + "' is required when " +
this.annotationType + " is used on '@Bean' methods.");
}
return createEndpoint;
}
protected String getInputChannelAttribute() {
return INPUT_CHANNEL_ATTRIBUTE;
}
protected boolean beanAnnotationAware() {
return true;
}
protected List<Advice> extractAdviceChain(String beanName, List<Annotation> annotations) {
List<Advice> adviceChain = null;
String[] adviceChainNames = MessagingAnnotationUtils.resolveAttribute(annotations, ADVICE_CHAIN_ATTRIBUTE,
@@ -519,9 +750,19 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
}
protected String generateHandlerBeanName(String originalBeanName, Method method) {
String name = MessagingAnnotationUtils.endpointIdValue(method);
return generateHandlerBeanName(originalBeanName, MergedAnnotations.from(method), method.getName());
}
protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations) {
return generateHandlerBeanName(originalBeanName, mergedAnnotations, null);
}
protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations,
@Nullable String methodName) {
String name = MessagingAnnotationUtils.endpointIdValue(mergedAnnotations);
if (!StringUtils.hasText(name)) {
String baseName = originalBeanName + "." + method.getName() + "."
String baseName = originalBeanName + (methodName != null ? "." + methodName : "") + "."
+ org.springframework.util.ClassUtils.getShortNameAsProperty(this.annotationType);
name = baseName;
int count = 1;
@@ -532,47 +773,13 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return name + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX;
}
protected void setOutputChannelIfPresent(List<Annotation> annotations,
AbstractReplyProducingMessageHandler handler) {
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel",
String.class);
if (StringUtils.hasText(outputChannelName)) {
handler.setOutputChannelName(outputChannelName);
}
}
protected static void setOutputChannelIfPresent(List<Annotation> annotations,
AbstractMessageProducingHandler handler) {
protected Object resolveTargetBeanFromMethodWithBeanAnnotation(Method method) {
String id = resolveTargetBeanName(method);
return this.beanFactory.getBean(id);
}
protected String resolveTargetBeanName(Method method) {
String id = method.getName();
String[] names = AnnotationUtils.getAnnotation(method, Bean.class).name(); // NOSONAR never null
if (!ObjectUtils.isEmpty(names)) {
id = names[0];
String outputChannel = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class);
if (StringUtils.hasText(outputChannel)) {
handler.setOutputChannelName(outputChannel);
}
return id;
}
@SuppressWarnings(UNCHECKED)
protected <H> H extractTypeIfPossible(@Nullable Object targetObject, Class<H> expectedType) {
if (targetObject == null) {
return null;
}
if (expectedType.isAssignableFrom(targetObject.getClass())) {
return (H) targetObject;
}
if (targetObject instanceof Advised) {
TargetSource targetSource = ((Advised) targetObject).getTargetSource();
try {
return extractTypeIfPossible(targetSource.getTarget(), expectedType);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
return null;
}
protected void checkMessageHandlerAttributes(String handlerBeanName, List<Annotation> annotations) {
@@ -596,18 +803,23 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(attribute));
}
@Nullable
protected MessageProcessor<?> buildLambdaMessageProcessorForBeanMethod(Method method, Object target) {
if ((target instanceof Function || target instanceof Consumer) && ClassUtils.isLambda(target.getClass())
|| ClassUtils.isKotlinFunction1(target.getClass())) {
protected static BeanDefinition buildLambdaMessageProcessor(ResolvableType beanType,
AnnotatedBeanDefinition beanDefinition) {
ResolvableType methodReturnType = ResolvableType.forMethodReturnType(method);
Class<?> expectedPayloadType = methodReturnType.getGeneric(0).toClass();
return new LambdaMessageProcessor(target, expectedPayloadType);
}
else {
return null;
Class<?> beanClass = beanType.toClass();
if (Function.class.isAssignableFrom(beanClass)
|| Consumer.class.isAssignableFrom(beanClass)
|| ClassUtils.isKotlinFunction1(beanClass)) {
Class<?> expectedPayloadType = beanType.getGeneric(0).toClass();
return BeanDefinitionBuilder.genericBeanDefinition(LambdaMessageProcessor.class)
.addConstructorArgValue(beanDefinition)
.addConstructorArgValue(expectedPayloadType)
.getBeanDefinition();
}
return null;
}
private static void orderable(Method method, MessageHandler handler) {
@@ -628,4 +840,33 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
*/
protected abstract MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations);
protected record BeanDefinitionPropertiesMapper(BeanDefinition beanDefinition, List<Annotation> annotations) {
public BeanDefinitionPropertiesMapper setPropertyValue(String property) {
return setPropertyValue(property, null);
}
public BeanDefinitionPropertiesMapper setPropertyValue(String property, @Nullable String propertyName) {
String value = MessagingAnnotationUtils.resolveAttribute(this.annotations, property, String.class);
if (StringUtils.hasText(value)) {
this.beanDefinition.getPropertyValues().add(propertyName != null ? propertyName : property, value);
}
return this;
}
public BeanDefinitionPropertiesMapper setPropertyReference(String property) {
return setPropertyReference(property, null);
}
public BeanDefinitionPropertiesMapper setPropertyReference(String property, @Nullable String propertyName) {
String value = MessagingAnnotationUtils.resolveAttribute(this.annotations, property, String.class);
if (StringUtils.hasText(value)) {
this.beanDefinition.getPropertyValues()
.add(propertyName != null ? propertyName : property, new RuntimeBeanReference(value));
}
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,7 +20,6 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.integration.aggregator.AggregatingMessageHandler;
import org.springframework.integration.aggregator.MethodInvokingCorrelationStrategy;
import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor;
@@ -43,11 +42,6 @@ import org.springframework.util.StringUtils;
*/
public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Aggregator> {
public AggregatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method);
@@ -86,7 +80,8 @@ public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationP
return handler;
}
protected boolean beanAnnotationAware() {
@Override
public boolean beanAnnotationAware() {
return false;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2022 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.
@@ -20,18 +20,17 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Post-processor for the {@link BridgeFrom @BridgeFrom} annotation.
@@ -42,46 +41,37 @@ import org.springframework.util.StringUtils;
*/
public class BridgeFromAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<BridgeFrom> {
public BridgeFromAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
public boolean shouldCreateEndpoint(Method method, List<Annotation> annotations) {
boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName());
Assert.isTrue(isBean, "'@BridgeFrom' is eligible only for '@Bean' methods");
boolean isMessageChannelBean = MessageChannel.class.isAssignableFrom(method.getReturnType());
Assert.isTrue(isMessageChannelBean, "'@BridgeFrom' is eligible only for 'MessageChannel' '@Bean' methods");
String channel = MessagingAnnotationUtils.resolveAttribute(annotations, "value", String.class);
Assert.isTrue(StringUtils.hasText(channel), "'@BridgeFrom.value()' (inputChannelName) must not be empty");
boolean hasBridgeTo = AnnotatedElementUtils.isAnnotated(method, BridgeTo.class.getName());
Assert.isTrue(!hasBridgeTo, "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' " +
"'@Bean' method annotations");
return true;
}
@Override
protected String getInputChannelAttribute() {
public String getInputChannelAttribute() {
return AnnotationUtils.VALUE;
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
BridgeHandler handler = new BridgeHandler();
String outputChannelName = resolveTargetBeanName(method);
handler.setOutputChannelName(outputChannelName);
return handler;
public boolean supportsPojoMethod() {
return false;
}
@Override
protected Object resolveTargetBeanFromMethodWithBeanAnnotation(Method method) {
return null;
public boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List<Annotation> annotations) {
Assert.isTrue(super.shouldCreateEndpoint(mergedAnnotations, annotations),
"'@BridgeFrom.value()' (inputChannelName) must not be empty");
Assert.isTrue(!mergedAnnotations.isPresent(BridgeTo.class),
"'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' '@Bean' method annotations");
return true;
}
@Override
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotationChain) {
return BeanDefinitionBuilder.genericBeanDefinition(BridgeHandler.class)
.addPropertyReference("outputChannel", beanName)
.getBeanDefinition();
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2022 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.
@@ -20,15 +20,20 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.ComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -37,45 +42,55 @@ import org.springframework.util.StringUtils;
* Post-processor for the {@link BridgeTo @BridgeTo} annotation.
*
* @author Artem Bilan
*
* @since 4.0
*/
public class BridgeToAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<BridgeTo> {
public BridgeToAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
@Override
public boolean supportsPojoMethod() {
return false;
}
@Override
public boolean shouldCreateEndpoint(Method method, List<Annotation> annotations) {
boolean isBean = AnnotatedElementUtils.isAnnotated(method, Bean.class.getName());
Assert.isTrue(isBean, "'@BridgeTo' is eligible only for '@Bean' methods");
boolean isMessageChannelBean = MessageChannel.class.isAssignableFrom(method.getReturnType());
Assert.isTrue(isMessageChannelBean, "'@BridgeTo' is eligible only for 'MessageChannel' '@Bean' methods");
boolean hasBridgeFrom = AnnotatedElementUtils.isAnnotated(method, BridgeFrom.class.getName());
Assert.isTrue(!hasBridgeFrom, "'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' " +
"'@Bean' method annotations");
public boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List<Annotation> annotations) {
Assert.isTrue(!mergedAnnotations.isPresent(BridgeFrom.class),
"'@BridgeFrom' and '@BridgeTo' are mutually exclusive 'MessageChannel' '@Bean' method annotations");
return true;
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
BridgeHandler handler = new BridgeHandler();
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "value", String.class);
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotationChain) {
GenericBeanDefinition bridgeHandlerBeanDefinition = new GenericBeanDefinition();
bridgeHandlerBeanDefinition.setBeanClass(BridgeHandler.class);
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotationChain, "value", String.class);
if (StringUtils.hasText(outputChannelName)) {
handler.setOutputChannelName(outputChannelName);
bridgeHandlerBeanDefinition.getPropertyValues()
.addPropertyValue("outputChannel", new RuntimeBeanReference(outputChannelName));
}
return handler;
return bridgeHandlerBeanDefinition;
}
@Override
protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition,
ComponentDefinition beanDefinition, List<Annotation> annotations) {
return BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class)
.addPropertyReference("handler", handlerBeanDefinition.getName())
.addPropertyReference("inputChannel", beanDefinition.getName())
.getBeanDefinition();
}
@Override
protected AbstractEndpoint createEndpoint(MessageHandler handler, Method method, List<Annotation> annotations) {
Object inputChannel = this.resolveTargetBeanFromMethodWithBeanAnnotation(method);
Assert.isInstanceOf(MessageChannel.class, inputChannel);
return doCreateEndpoint(handler, (MessageChannel) inputChannel, annotations);
throw new UnsupportedOperationException();
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,10 +21,14 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.ResolvableType;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.config.FilterFactoryBean;
import org.springframework.integration.core.MessageSelector;
import org.springframework.integration.filter.MessageFilter;
import org.springframework.integration.filter.MethodInvokingSelector;
@@ -43,34 +47,45 @@ import org.springframework.util.StringUtils;
*/
public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Filter> {
public FilterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
public FilterAnnotationPostProcessor() {
this.messageHandlerAttributes.addAll(Arrays.asList("discardChannel", "throwExceptionOnRejection",
"adviceChain", "discardWithinAdvice"));
}
@Override
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotations) {
BeanDefinition handlerBeanDefinition =
super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations);
if (handlerBeanDefinition != null) {
return handlerBeanDefinition;
}
BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition);
if (targetObjectBeanDefinition == null) {
targetObjectBeanDefinition = new RuntimeBeanReference(beanName);
}
BeanDefinition filterBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(FilterFactoryBean.class)
.addPropertyValue("targetObject", targetObjectBeanDefinition)
.getBeanDefinition();
new BeanDefinitionPropertiesMapper(filterBeanDefinition, annotations)
.setPropertyValue("discardWithinAdvice")
.setPropertyValue("throwExceptionOnRejection")
.setPropertyReference("discardChannel");
return filterBeanDefinition;
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
MessageSelector selector;
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
if (target instanceof MessageSelector) {
selector = (MessageSelector) target;
}
else if (extractTypeIfPossible(target, MessageFilter.class) != null) {
checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations);
return (MessageHandler) target;
}
else {
selector = new MethodInvokingSelector(target);
}
}
else {
Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()),
"The Filter annotation may only be applied to methods with a boolean return type.");
selector = new MethodInvokingSelector(bean, method);
}
Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()),
"The Filter annotation may only be applied to methods with a boolean return type.");
MessageSelector selector = new MethodInvokingSelector(bean, method);
MessageFilter filter = new MessageFilter(selector);

View File

@@ -21,20 +21,24 @@ import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.ComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.util.ClassUtils;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
@@ -51,14 +55,55 @@ import org.springframework.util.Assert;
public class InboundChannelAdapterAnnotationPostProcessor extends
AbstractMethodAnnotationPostProcessor<InboundChannelAdapter> {
public InboundChannelAdapterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
@Override
public String getInputChannelAttribute() {
return "value";
}
@Override
protected String getInputChannelAttribute() {
return AnnotationUtils.VALUE;
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotationChain) {
Class<?> handlerBeanClass = handlerBeanType.toClass();
if (MessageSource.class.isAssignableFrom(handlerBeanClass)) {
return beanDefinition;
}
Method method = null;
if (Supplier.class.isAssignableFrom(handlerBeanClass)) {
method = ClassUtils.SUPPLIER_GET_METHOD;
}
else if (ClassUtils.isKotlinFunction0(handlerBeanClass)) {
method = ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD;
}
if (method != null) {
return BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageSource.class)
.addPropertyReference("object", beanName)
.addPropertyValue("method", method)
.getBeanDefinition();
}
else {
throw new IllegalArgumentException(
"The '" + this.annotationType + "' on @Bean method level is allowed only for: " +
MessageSource.class.getName() + ", or " + Supplier.class.getName() +
(ClassUtils.KOTLIN_FUNCTION_0_CLASS != null
? ", or " + ClassUtils.KOTLIN_FUNCTION_0_CLASS.getName()
: "") + " beans");
}
}
@Override
protected BeanDefinition createEndpointBeanDefinition(ComponentDefinition handlerBeanDefinition,
ComponentDefinition beanDefinition, List<Annotation> annotations) {
String channelName = MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty.");
return BeanDefinitionBuilder.rootBeanDefinition(SourcePollingChannelAdapterFactoryBean.class)
.addPropertyValue("outputChannelName", channelName)
.addPropertyReference("source", handlerBeanDefinition.getName())
.getBeanDefinition();
}
@Override
@@ -67,18 +112,7 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty.");
MessageSource<?> messageSource;
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;
}
MessageSource<?> messageSource = createMessageSource(bean, beanName, method);
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
adapter.setOutputChannelName(channelName);
@@ -89,51 +123,21 @@ public class InboundChannelAdapterAnnotationPostProcessor extends
return adapter;
}
private MessageSource<?> createMessageSource(Object beanArg, String beanNameArg, Method methodArg) {
MessageSource<?> messageSource = null;
Object bean = beanArg;
Method method = methodArg;
String beanName = beanNameArg;
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
Class<?> targetClass = target.getClass();
Assert.isTrue(MessageSource.class.isAssignableFrom(targetClass) ||
Supplier.class.isAssignableFrom(targetClass) ||
ClassUtils.isKotlinFunction0(targetClass),
() -> "The '" + this.annotationType + "' on @Bean method " + "level is allowed only for: " +
MessageSource.class.getName() + ", or " + Supplier.class.getName() +
(ClassUtils.KOTLIN_FUNCTION_0_CLASS != null
? ", or " + ClassUtils.KOTLIN_FUNCTION_0_CLASS.getName()
: "") + " beans");
if (target instanceof MessageSource<?>) {
messageSource = (MessageSource<?>) target;
}
else if (target instanceof Supplier<?>) {
method = ClassUtils.SUPPLIER_GET_METHOD;
bean = target;
beanName += '.' + methodArg.getName();
}
else if (ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD != null) {
method = ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD;
bean = target;
beanName += '.' + methodArg.getName();
}
}
if (messageSource == null) {
MethodInvokingMessageSource methodInvokingMessageSource = new MethodInvokingMessageSource();
methodInvokingMessageSource.setObject(bean);
methodInvokingMessageSource.setMethod(method);
String messageSourceBeanName = generateHandlerBeanName(beanName, method);
this.definitionRegistry.registerBeanDefinition(messageSourceBeanName,
new RootBeanDefinition(MethodInvokingMessageSource.class, () -> methodInvokingMessageSource));
messageSource = this.beanFactory.getBean(messageSourceBeanName, MessageSource.class);
}
return messageSource;
private MessageSource<?> createMessageSource(Object bean, String beanName, Method method) {
MethodInvokingMessageSource methodInvokingMessageSource = new MethodInvokingMessageSource();
methodInvokingMessageSource.setObject(bean);
methodInvokingMessageSource.setMethod(method);
String messageSourceBeanName = generateHandlerBeanName(beanName, method);
getDefinitionRegistry().registerBeanDefinition(messageSourceBeanName,
new RootBeanDefinition(MethodInvokingMessageSource.class, () -> methodInvokingMessageSource));
return getBeanFactory().getBean(messageSourceBeanName, MessageSource.class);
}
@Override
protected String generateHandlerBeanName(String originalBeanName, Method method) {
return super.generateHandlerBeanName(originalBeanName, method)
protected String generateHandlerBeanName(String originalBeanName, MergedAnnotations mergedAnnotations,
@Nullable String methodName) {
return super.generateHandlerBeanName(originalBeanName, mergedAnnotations, methodName)
.replaceFirst(IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX + '$', ".source");
}

View File

@@ -21,8 +21,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -32,18 +30,25 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aot.AotDetector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
@@ -73,50 +78,135 @@ import org.springframework.util.StringUtils;
* @author Gary Russell
* @author Rick Hogge
*/
public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean,
SmartInitializingSingleton {
public class MessagingAnnotationPostProcessor
implements BeanDefinitionRegistryPostProcessor, BeanPostProcessor, SmartInitializingSingleton {
protected final Log logger = LogFactory.getLog(this.getClass()); // NOSONAR
private final Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors = new HashMap<>();
private ConfigurableListableBeanFactory beanFactory;
private final Set<Class<?>> noAnnotationsCache = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
private final List<Runnable> methodsToPostProcessAfterContextInitialization = new ArrayList<>();
private BeanDefinitionRegistry registry;
private ConfigurableListableBeanFactory beanFactory;
private volatile boolean initialized;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(),
"a ConfigurableListableBeanFactory is required");
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.registry = registry;
this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor());
this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor());
this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor());
this.postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor());
this.postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor());
this.postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor());
this.postProcessors.put(InboundChannelAdapter.class, new InboundChannelAdapterAnnotationPostProcessor());
this.postProcessors.put(BridgeFrom.class, new BridgeFromAnnotationPostProcessor());
this.postProcessors.put(BridgeTo.class, new BridgeToAnnotationPostProcessor());
Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> customPostProcessors =
setupCustomPostProcessors();
if (!CollectionUtils.isEmpty(customPostProcessors)) {
this.postProcessors.putAll(customPostProcessors);
}
this.postProcessors.values().stream()
.filter(BeanFactoryAware.class::isInstance)
.map(BeanFactoryAware.class::cast)
.forEach((processor) -> processor.setBeanFactory((BeanFactory) this.registry));
if (!AotDetector.useGeneratedArtifacts()) {
String[] beanNames = registry.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef instanceof AnnotatedBeanDefinition annotatedBeanDefinition
&& annotatedBeanDefinition.getFactoryMethodMetadata() != null) {
processCandidate(beanName, annotatedBeanDefinition);
}
}
}
}
private void processCandidate(String beanName, AnnotatedBeanDefinition beanDefinition) {
MethodMetadata methodMetadata = beanDefinition.getFactoryMethodMetadata();
MergedAnnotations annotations = methodMetadata.getAnnotations();
if (methodMetadata instanceof StandardMethodMetadata standardMethodMetadata) {
annotations = MergedAnnotations.from(standardMethodMetadata.getIntrospectedMethod());
}
List<MessagingMetaAnnotation> messagingAnnotations = obtainMessagingAnnotations(annotations, beanName);
for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) {
Class<? extends Annotation> annotationType = messagingAnnotation.annotationType;
List<Annotation> annotationChain =
MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation, annotationType);
processMessagingAnnotationOnBean(beanName, beanDefinition, annotationType, annotationChain);
}
}
private List<MessagingMetaAnnotation> obtainMessagingAnnotations(MergedAnnotations annotations, String identified) {
List<MessagingMetaAnnotation> messagingAnnotations = new ArrayList<>();
for (Class<? extends Annotation> annotationType : this.postProcessors.keySet()) {
annotations.stream()
.filter((ann) -> ann.getType().equals(annotationType))
.map(MergedAnnotation::getRoot)
.map(MergedAnnotation::synthesize)
.map((ann) -> new MessagingMetaAnnotation(ann, annotationType))
.forEach(messagingAnnotations::add);
}
if (annotations.get(EndpointId.class, (ann) -> ann.hasNonDefaultValue("value")).isPresent()
&& messagingAnnotations.size() > 1) {
throw new IllegalStateException("@EndpointId on " + identified
+ " can only have one EIP annotation, found: " + messagingAnnotations.size());
}
return messagingAnnotations;
}
private void processMessagingAnnotationOnBean(String beanName, AnnotatedBeanDefinition beanDefinition,
Class<? extends Annotation> annotationType, List<Annotation> annotationChain) {
MethodAnnotationPostProcessor<?> messagingAnnotationProcessor = this.postProcessors.get(annotationType);
if (messagingAnnotationProcessor != null) {
if (messagingAnnotationProcessor.beanAnnotationAware()) {
if (messagingAnnotationProcessor.shouldCreateEndpoint(
beanDefinition.getFactoryMethodMetadata().getAnnotations(), annotationChain)) {
messagingAnnotationProcessor.processBeanDefinition(beanName, beanDefinition, annotationChain);
}
else {
throw new BeanDefinitionValidationException(
"The input channel for endpoint on '@Bean' method must be set for the "
+ annotationType + ". The bean definition with the problem is: " + beanName);
}
}
else {
throw new BeanDefinitionValidationException(
"The messaging annotation '" + annotationType + "' cannot be declared on '@Bean'. " +
"The bean definition with the problem is: " + beanName);
}
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
protected ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
@Override
public void afterPropertiesSet() {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
this.postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(InboundChannelAdapter.class,
new InboundChannelAdapterAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(BridgeFrom.class, new BridgeFromAnnotationPostProcessor(this.beanFactory));
this.postProcessors.put(BridgeTo.class, new BridgeToAnnotationPostProcessor(this.beanFactory));
Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> customPostProcessors =
setupCustomPostProcessors();
if (!CollectionUtils.isEmpty(customPostProcessors)) {
this.postProcessors.putAll(customPostProcessors);
}
protected BeanDefinitionRegistry getBeanDefinitionRegistry() {
return this.registry;
}
protected Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> setupCustomPostProcessors() {
@@ -160,34 +250,23 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
}
private void doWithMethod(Method method, Object bean, String beanName, Class<?> beanClass) {
MergedAnnotations mergedAnnotations =
MergedAnnotations.search(MergedAnnotations.SearchStrategy.DIRECT)
.from(method);
MergedAnnotations mergedAnnotations = MergedAnnotations.from(method);
List<MessagingMetaAnnotation> messagingAnnotations = new ArrayList<>();
boolean noMessagingAnnotations = true;
for (Class<? extends Annotation> annotationType : this.postProcessors.keySet()) {
mergedAnnotations.stream()
.filter((ann) -> ann.getType().equals(annotationType))
.map(MergedAnnotation::getRoot)
.map(MergedAnnotation::synthesize)
.map((ann) -> new MessagingMetaAnnotation(ann, annotationType))
.forEach(messagingAnnotations::add);
if (!mergedAnnotations.isPresent(Bean.class)) { // See postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
List<MessagingMetaAnnotation> messagingAnnotations =
obtainMessagingAnnotations(mergedAnnotations, method.toGenericString());
for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) {
noMessagingAnnotations = false;
Class<? extends Annotation> annotationType = messagingAnnotation.annotationType;
List<Annotation> annotationChain =
MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation, annotationType);
processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain);
}
}
if (mergedAnnotations.get(EndpointId.class, (ann) -> ann.hasNonDefaultValue("value")).isPresent()
&& messagingAnnotations.size() > 1) {
throw new IllegalStateException("@EndpointId on " + method.toGenericString()
+ " can only have one EIP annotation, found: " + messagingAnnotations.size());
}
for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) {
Class<? extends Annotation> annotationType = messagingAnnotation.messagingAnnotationType;
List<Annotation> annotationChain = getAnnotationChain(messagingAnnotation.annotation, annotationType);
processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain);
}
if (messagingAnnotations.size() == 0) {
if (noMessagingAnnotations) {
this.noAnnotationsCache.add(beanClass);
}
}
@@ -197,7 +276,9 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
MethodAnnotationPostProcessor<?> postProcessor =
MessagingAnnotationPostProcessor.this.postProcessors.get(annotationType);
if (postProcessor != null && postProcessor.shouldCreateEndpoint(method, annotations)) {
if (postProcessor != null && postProcessor.supportsPojoMethod()
&& postProcessor.shouldCreateEndpoint(method, annotations)) {
Method targetMethod = method;
if (AopUtils.isJdkDynamicProxy(bean)) {
try {
@@ -230,7 +311,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
BeanDefinitionRegistry definitionRegistry = (BeanDefinitionRegistry) beanFactory;
if (result instanceof AbstractEndpoint endpoint) {
String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup", String.class);
if (StringUtils.hasText(autoStartup)) {
@@ -255,51 +335,13 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
String endpointBeanName = generateBeanName(beanName, method, annotationType);
endpoint.setBeanName(endpointBeanName);
definitionRegistry.registerBeanDefinition(endpointBeanName,
new RootBeanDefinition((Class<AbstractEndpoint>) endpoint.getClass(), () -> endpoint));
getBeanDefinitionRegistry()
.registerBeanDefinition(endpointBeanName,
new RootBeanDefinition((Class<AbstractEndpoint>) endpoint.getClass(), () -> endpoint));
beanFactory.getBean(endpointBeanName);
}
}
/**
* @param messagingAnnotation the {@link Annotation} to take a chain for its meta-annotations.
* @param annotationType the annotation type.
* @return the hierarchical list of annotations in top-bottom order.
*/
protected List<Annotation> getAnnotationChain(Annotation messagingAnnotation,
Class<? extends Annotation> annotationType) {
List<Annotation> annotationChain = new LinkedList<>();
Set<Annotation> visited = new HashSet<>();
recursiveFindAnnotation(annotationType, messagingAnnotation, annotationChain, visited);
if (annotationChain.size() > 0) {
Collections.reverse(annotationChain);
}
return annotationChain;
}
protected boolean recursiveFindAnnotation(Class<? extends Annotation> annotationType, Annotation ann,
List<Annotation> annotationChain, Set<Annotation> visited) {
if (ann.annotationType().equals(annotationType)) {
annotationChain.add(ann);
return true;
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (!ann.equals(metaAnn) && !visited.contains(metaAnn)
&& !(metaAnn.annotationType().getPackage().getName().startsWith("java.lang"))) {
visited.add(metaAnn); // prevent infinite recursion if the same annotation is found again
if (recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) {
annotationChain.add(ann);
return true;
}
}
}
return false;
}
protected String generateBeanName(String originalBeanName, Method method,
Class<? extends Annotation> annotationType) {
@@ -309,7 +351,8 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
+ ClassUtils.getShortNameAsProperty(annotationType);
name = baseName;
int count = 1;
while (this.beanFactory.containsBean(name)) {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
while (beanFactory.containsBean(name)) {
name = baseName + "#" + (++count);
}
}
@@ -320,7 +363,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
return this.postProcessors;
}
private record MessagingMetaAnnotation(Annotation annotation, Class<? extends Annotation> messagingAnnotationType) {
protected record MessagingMetaAnnotation(Annotation annotation, Class<? extends Annotation> annotationType) {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,6 +20,11 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.util.StringUtils;
/**
* Strategy interface for post-processing annotated methods.
*
@@ -31,8 +36,12 @@ import java.util.List;
*/
public interface MethodAnnotationPostProcessor<T extends Annotation> {
String INPUT_CHANNEL_ATTRIBUTE = "inputChannel";
Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations);
void processBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition, List<Annotation> annotations);
/**
* Determine if the provided {@code method} and its {@code annotations} are eligible
* to create an {@link org.springframework.integration.endpoint.AbstractEndpoint}.
@@ -42,6 +51,26 @@ public interface MethodAnnotationPostProcessor<T extends Annotation> {
* {@link org.springframework.integration.endpoint.AbstractEndpoint}
* @since 4.0
*/
boolean shouldCreateEndpoint(Method method, List<Annotation> annotations);
default boolean shouldCreateEndpoint(Method method, List<Annotation> annotations) {
return shouldCreateEndpoint(MergedAnnotations.from(method), annotations);
}
default boolean shouldCreateEndpoint(MergedAnnotations mergedAnnotations, List<Annotation> annotations) {
String inputChannel =
MessagingAnnotationUtils.resolveAttribute(annotations, getInputChannelAttribute(), String.class);
return StringUtils.hasText(inputChannel);
}
default String getInputChannelAttribute() {
return INPUT_CHANNEL_ATTRIBUTE;
}
default boolean beanAnnotationAware() {
return true;
}
default boolean supportsPojoMethod() {
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,18 +20,24 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.config.RouterFactoryBean;
import org.springframework.integration.router.AbstractMessageRouter;
import org.springframework.integration.router.MethodInvokingRouter;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -44,41 +50,62 @@ import org.springframework.util.StringUtils;
*/
public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Router> {
public RouterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
public RouterAnnotationPostProcessor() {
this.messageHandlerAttributes.addAll(Arrays.asList("defaultOutputChannel", "applySequence",
"ignoreSendFailures", "resolutionRequired", "channelMappings", "prefix", "suffix"));
}
@Override
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotations) {
BeanDefinition handlerBeanDefinition =
super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations);
if (handlerBeanDefinition != null) {
return handlerBeanDefinition;
}
BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition);
if (targetObjectBeanDefinition == null) {
targetObjectBeanDefinition = new RuntimeBeanReference(beanName);
}
BeanDefinition routerBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(RouterFactoryBean.class)
.addPropertyValue("targetObject", targetObjectBeanDefinition)
.getBeanDefinition();
new BeanDefinitionPropertiesMapper(routerBeanDefinition, annotations)
.setPropertyValue("applySequence")
.setPropertyValue("ignoreSendFailures")
.setPropertyValue("resolutionRequired")
.setPropertyValue("prefix")
.setPropertyValue("suffix");
String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, "channelMappings",
String[].class);
if (!ObjectUtils.isEmpty(channelMappings)) {
Map<String, String> mappings =
Arrays.stream(channelMappings)
.map((mapping) -> {
String[] keyValue = mapping.split("=");
return Map.entry(keyValue[0], keyValue[1]);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
routerBeanDefinition.getPropertyValues()
.addPropertyValue("channelMappings", mappings);
}
return routerBeanDefinition;
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
AbstractMessageRouter router;
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
router = extractTypeIfPossible(target, AbstractMessageRouter.class);
if (router == null) {
if (target instanceof MessageHandler) {
Assert.isTrue(routerAttributesProvided(annotations),
"'defaultOutputChannel', 'applySequence', 'ignoreSendFailures', 'resolutionRequired', " +
"'channelMappings', 'prefix' and 'suffix' " +
"can be applied to 'AbstractMessageRouter' implementations, but target handler is: " +
target.getClass());
return (MessageHandler) target;
}
else {
router = new MethodInvokingRouter(target);
}
}
else {
checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations);
return router;
}
}
else {
router = new MethodInvokingRouter(bean, method);
}
String defaultOutputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations,
"defaultOutputChannel", String.class);
AbstractMessageRouter router = new MethodInvokingRouter(bean, method);
String defaultOutputChannelName =
MessagingAnnotationUtils.resolveAttribute(annotations, "defaultOutputChannel", String.class);
if (StringUtils.hasText(defaultOutputChannelName)) {
router.setDefaultOutputChannelName(defaultOutputChannelName);
}
@@ -110,14 +137,16 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
methodInvokingRouter.setResolutionRequired(resolveAttributeToBoolean(resolutionRequired));
}
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
String prefix = MessagingAnnotationUtils.resolveAttribute(annotations, "prefix", String.class);
if (StringUtils.hasText(prefix)) {
methodInvokingRouter.setPrefix(this.beanFactory.resolveEmbeddedValue(prefix));
methodInvokingRouter.setPrefix(beanFactory.resolveEmbeddedValue(prefix));
}
String suffix = MessagingAnnotationUtils.resolveAttribute(annotations, "suffix", String.class);
if (StringUtils.hasText(suffix)) {
methodInvokingRouter.setSuffix(this.beanFactory.resolveEmbeddedValue(suffix));
methodInvokingRouter.setSuffix(beanFactory.resolveEmbeddedValue(suffix));
}
String[] channelMappings = MessagingAnnotationUtils.resolveAttribute(annotations, "channelMappings",
@@ -127,7 +156,7 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
for (String channelMapping : channelMappings) {
mappings.append(channelMapping).append("\n");
}
Properties properties = (Properties) this.conversionService.convert(mappings.toString(),
Properties properties = (Properties) getConversionService().convert(mappings.toString(),
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Properties.class));
methodInvokingRouter.replaceChannelMappings(properties);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,18 +21,18 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.ResolvableType;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.config.ServiceActivatorFactoryBean;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.ReactiveMessageHandler;
import org.springframework.util.StringUtils;
/**
@@ -45,47 +45,41 @@ import org.springframework.util.StringUtils;
*/
public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<ServiceActivator> {
public ServiceActivatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
public ServiceActivatorAnnotationPostProcessor() {
this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "requiresReply", "adviceChain"));
}
@Override
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotations) {
BeanDefinition handlerBeanDefinition =
super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations);
if (handlerBeanDefinition != null) {
return handlerBeanDefinition;
}
BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition);
if (targetObjectBeanDefinition == null) {
targetObjectBeanDefinition = new RuntimeBeanReference(beanName);
}
BeanDefinition serviceActivatorBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(ServiceActivatorFactoryBean.class)
.addPropertyValue("targetObject", targetObjectBeanDefinition)
.getBeanDefinition();
new BeanDefinitionPropertiesMapper(serviceActivatorBeanDefinition, annotations)
.setPropertyValue("requiresReply")
.setPropertyValue("async");
return serviceActivatorBeanDefinition;
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
AbstractReplyProducingMessageHandler serviceActivator;
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
serviceActivator = extractTypeIfPossible(target, AbstractReplyProducingMessageHandler.class);
if (serviceActivator == null) {
if (target instanceof ReactiveMessageHandler) {
return new ReactiveMessageHandlerAdapter((ReactiveMessageHandler) target);
}
if (target instanceof MessageHandler) {
/*
* Return a reply-producing message handler so that we still get 'produced no reply' messages
* and the super class will inject the advice chain to advise the handler method if needed.
*/
return new ReplyProducingMessageHandlerWrapper((MessageHandler) target);
}
else {
MessageProcessor<?> messageProcessor = buildLambdaMessageProcessorForBeanMethod(method, target);
if (messageProcessor != null) {
serviceActivator = new ServiceActivatingHandler(messageProcessor);
}
else {
serviceActivator = new ServiceActivatingHandler(target);
}
}
}
else {
checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations);
return (MessageHandler) target;
}
}
else {
serviceActivator = new ServiceActivatingHandler(bean, method);
}
AbstractReplyProducingMessageHandler serviceActivator = new ServiceActivatingHandler(bean, method);
String requiresReply = MessagingAnnotationUtils.resolveAttribute(annotations, "requiresReply", String.class);
if (StringUtils.hasText(requiresReply)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,15 +21,18 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.ResolvableType;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.config.SplitterFactoryBean;
import org.springframework.integration.splitter.AbstractMessageSplitter;
import org.springframework.integration.splitter.MethodInvokingSplitter;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -41,43 +44,47 @@ import org.springframework.util.StringUtils;
*/
public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Splitter> {
public SplitterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
public SplitterAnnotationPostProcessor() {
this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "applySequence", "adviceChain"));
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotations) {
BeanDefinition handlerBeanDefinition =
super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations);
if (handlerBeanDefinition != null) {
return handlerBeanDefinition;
}
BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition);
if (targetObjectBeanDefinition == null) {
targetObjectBeanDefinition = new RuntimeBeanReference(beanName);
}
BeanDefinitionBuilder splitterBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(SplitterFactoryBean.class)
.addPropertyValue("targetObject", targetObjectBeanDefinition);
String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, "applySequence", String.class);
AbstractMessageSplitter splitter;
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
splitter = this.extractTypeIfPossible(target, AbstractMessageSplitter.class);
if (splitter == null) {
if (target instanceof MessageHandler) {
Assert.hasText(applySequence, "'applySequence' can be applied to 'AbstractMessageSplitter', but " +
"target handler is: " + target.getClass());
return (MessageHandler) target;
}
else {
splitter = new MethodInvokingSplitter(target);
}
}
else {
checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations);
return splitter;
}
}
else {
splitter = new MethodInvokingSplitter(bean, method);
if (StringUtils.hasText(applySequence)) {
splitterBeanDefinition.addPropertyValue("applySequence", applySequence);
}
return splitterBeanDefinition.getBeanDefinition();
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
AbstractMessageSplitter splitter = new MethodInvokingSplitter(bean, method);
String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, "applySequence", String.class);
if (StringUtils.hasText(applySequence)) {
splitter.setApplySequence(resolveAttributeToBoolean(applySequence));
}
this.setOutputChannelIfPresent(annotations, splitter);
setOutputChannelIfPresent(annotations, splitter);
return splitter;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,11 +21,13 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.ResolvableType;
import org.springframework.integration.config.TransformerFactoryBean;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.integration.transformer.MethodInvokingTransformer;
import org.springframework.integration.transformer.Transformer;
@@ -42,35 +44,34 @@ import org.springframework.messaging.MessageHandler;
public class TransformerAnnotationPostProcessor
extends AbstractMethodAnnotationPostProcessor<org.springframework.integration.annotation.Transformer> {
public TransformerAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
public TransformerAnnotationPostProcessor() {
this.messageHandlerAttributes.addAll(Arrays.asList("outputChannel", "adviceChain"));
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
Transformer transformer;
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
transformer = extractTypeIfPossible(target, Transformer.class);
if (transformer == null) {
if (extractTypeIfPossible(target, AbstractReplyProducingMessageHandler.class) != null) {
checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations);
return (MessageHandler) target;
}
MessageProcessor<?> messageProcessor = buildLambdaMessageProcessorForBeanMethod(method, target);
if (messageProcessor != null) {
transformer = new MethodInvokingTransformer(messageProcessor);
}
else {
transformer = new MethodInvokingTransformer(target);
}
}
}
else {
transformer = new MethodInvokingTransformer(bean, method);
protected BeanDefinition resolveHandlerBeanDefinition(String beanName, AnnotatedBeanDefinition beanDefinition,
ResolvableType handlerBeanType, List<Annotation> annotations) {
BeanDefinition handlerBeanDefinition =
super.resolveHandlerBeanDefinition(beanName, beanDefinition, handlerBeanType, annotations);
if (handlerBeanDefinition != null) {
return handlerBeanDefinition;
}
BeanMetadataElement targetObjectBeanDefinition = buildLambdaMessageProcessor(handlerBeanType, beanDefinition);
if (targetObjectBeanDefinition == null) {
targetObjectBeanDefinition = new RuntimeBeanReference(beanName);
}
return BeanDefinitionBuilder.genericBeanDefinition(TransformerFactoryBean.class)
.addPropertyValue("targetObject", targetObjectBeanDefinition)
.getBeanDefinition();
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
Transformer transformer = new MethodInvokingTransformer(bean, method);
MessageTransformingHandler handler = new MessageTransformingHandler(transformer);
setOutputChannelIfPresent(annotations, handler);
return handler;

View File

@@ -18,14 +18,21 @@ package org.springframework.integration.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.integration.annotation.EndpointId;
import org.springframework.integration.annotation.Payloads;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
@@ -51,7 +58,6 @@ public final class MessagingAnnotationUtils {
/**
* Get the attribute value from the annotation hierarchy, returning the first
* {@link MessagingAnnotationUtils#hasValue non-empty}) value closest to the annotated method.
*
* @param annotations The meta-annotations in order (closest first).
* @param name The attribute name.
* @param requiredType The expected type.
@@ -88,11 +94,8 @@ public final class MessagingAnnotationUtils {
return false;
}
// Annotation with 'value' set to special 'none' string
if ((annotationValue instanceof Annotation) &&
ValueConstants.DEFAULT_NONE.equals(AnnotationUtils.getValue((Annotation) annotationValue))) {
return false;
}
return true;
return (!(annotationValue instanceof Annotation)) ||
!ValueConstants.DEFAULT_NONE.equals(AnnotationUtils.getValue((Annotation) annotationValue));
}
public static Method findAnnotatedMethod(Object target, final Class<? extends Annotation> annotationType) {
@@ -143,9 +146,62 @@ public final class MessagingAnnotationUtils {
* @return the id, or null.
* @since 5.0.4
*/
@Nullable
public static String endpointIdValue(Method method) {
EndpointId endpointId = AnnotationUtils.findAnnotation(method, EndpointId.class);
return endpointId != null ? endpointId.value() : null;
return endpointIdValue(MergedAnnotations.from(method));
}
/**
* Return the {@link EndpointId#value()} property, if present.
* @param mergedAnnotations the {@link MergedAnnotations} to analyze.
* @return the id, or null.
* @since 6.0
*/
@Nullable
public static String endpointIdValue(MergedAnnotations mergedAnnotations) {
MergedAnnotation<EndpointId> endpointIdAnnotation = mergedAnnotations.get(EndpointId.class);
return endpointIdAnnotation.getValue(AnnotationUtils.VALUE, String.class).orElse(null);
}
/**
* Get a chain of its meta-annotations for the provided instance and expected type.
* @param messagingAnnotation the {@link Annotation} to take a chain for its meta-annotations.
* @param annotationType the annotation type.
* @return the hierarchical list of annotations in top-bottom order.
* @since 6.0
*/
public static List<Annotation> getAnnotationChain(Annotation messagingAnnotation,
Class<? extends Annotation> annotationType) {
List<Annotation> annotationChain = new LinkedList<>();
Set<Annotation> visited = new HashSet<>();
recursiveFindAnnotation(annotationType, messagingAnnotation, annotationChain, visited);
if (annotationChain.size() > 0) {
Collections.reverse(annotationChain);
}
return annotationChain;
}
private static boolean recursiveFindAnnotation(Class<? extends Annotation> annotationType, Annotation ann,
List<Annotation> annotationChain, Set<Annotation> visited) {
if (ann.annotationType().equals(annotationType)) {
annotationChain.add(ann);
return true;
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (!ann.equals(metaAnn) && !visited.contains(metaAnn)
&& !(metaAnn.annotationType().getPackage().getName().startsWith("java.lang"))) {
visited.add(metaAnn); // prevent infinite recursion if the same annotation is found again
if (recursiveFindAnnotation(annotationType, metaAnn, annotationChain, visited)) {
annotationChain.add(ann);
return true;
}
}
}
return false;
}
private MessagingAnnotationUtils() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -19,10 +19,11 @@ package org.springframework.integration.bus;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
@@ -45,20 +46,20 @@ import org.springframework.messaging.support.GenericMessage;
*/
public class DirectChannelSubscriptionTests {
private TestApplicationContext context = TestUtils.createTestApplicationContext();
private final TestApplicationContext context = TestUtils.createTestApplicationContext();
private DirectChannel sourceChannel = new DirectChannel();
private final DirectChannel sourceChannel = new DirectChannel();
private PollableChannel targetChannel = new QueueChannel();
private final PollableChannel targetChannel = new QueueChannel();
@Before
@BeforeEach
public void setupChannels() {
this.context.registerChannel("sourceChannel", this.sourceChannel);
this.context.registerChannel("targetChannel", this.targetChannel);
}
@After
@AfterEach
public void tearDown() {
this.context.close();
}
@@ -80,8 +81,8 @@ public class DirectChannelSubscriptionTests {
@Test
public void sendAndReceiveForAnnotatedEndpoint() {
MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
postProcessor.setBeanFactory(this.context.getBeanFactory());
postProcessor.afterPropertiesSet();
postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory());
postProcessor.postProcessBeanFactory(this.context.getBeanFactory());
postProcessor.afterSingletonsInstantiated();
TestEndpoint endpoint = new TestEndpoint();
postProcessor.postProcessAfterInitialization(endpoint, "testEndpoint");
@@ -91,7 +92,7 @@ public class DirectChannelSubscriptionTests {
assertThat(response.getPayload()).isEqualTo("foo-from-annotated-endpoint");
}
@Test(expected = MessagingException.class)
@Test
public void exceptionThrownFromRegisteredEndpoint() {
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
@@ -104,7 +105,8 @@ public class DirectChannelSubscriptionTests {
EventDrivenConsumer endpoint = new EventDrivenConsumer(sourceChannel, handler);
this.context.registerEndpoint("testEndpoint", endpoint);
this.context.refresh();
this.sourceChannel.send(new GenericMessage<>("foo"));
assertThatExceptionOfType(MessagingException.class)
.isThrownBy(() -> this.sourceChannel.send(new GenericMessage<>("foo")));
}
@Test
@@ -112,8 +114,7 @@ public class DirectChannelSubscriptionTests {
QueueChannel errorChannel = new QueueChannel();
this.context.registerChannel(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, errorChannel);
MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
postProcessor.setBeanFactory(this.context.getBeanFactory());
postProcessor.afterPropertiesSet();
postProcessor.postProcessBeanFactory(this.context.getBeanFactory());
postProcessor.afterSingletonsInstantiated();
FailingTestEndpoint endpoint = new FailingTestEndpoint();
postProcessor.postProcessAfterInitialization(endpoint, "testEndpoint");

View File

@@ -41,6 +41,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.log.LogAccessor;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.context.IntegrationContextUtils;
@@ -105,7 +106,7 @@ public class CustomMessagingAnnotationTests {
MessagingAnnotationPostProcessor messagingAnnotationPostProcessor = new MessagingAnnotationPostProcessor();
messagingAnnotationPostProcessor.
addMessagingAnnotationPostProcessor(Logging.class, new LogAnnotationPostProcessor(beanFactory));
addMessagingAnnotationPostProcessor(Logging.class, new LogAnnotationPostProcessor());
return messagingAnnotationPostProcessor;
}
@@ -124,29 +125,24 @@ public class CustomMessagingAnnotationTests {
String value();
LoggingHandler.Level level() default LoggingHandler.Level.INFO;
}
private static class LogAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Logging> {
LogAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected String getInputChannelAttribute() {
return "value";
public String getInputChannelAttribute() {
return AnnotationUtils.VALUE;
}
@Override
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
LoggingHandler.Level level = MessagingAnnotationUtils.resolveAttribute(annotations, "level",
LoggingHandler.Level.class);
LoggingHandler loggingHandler = new LoggingHandler(level.name());
LoggingHandler loggingHandler = new LoggingHandler(level);
MethodInvokingMessageProcessor<String> processor = new MethodInvokingMessageProcessor<>(bean, method);
processor.setBeanFactory(this.beanFactory);
processor.setBeanFactory(getBeanFactory());
loggingHandler.setLogExpression(new FunctionExpression<>(processor::processMessage));
return loggingHandler;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -22,10 +22,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import java.util.Collections;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.channel.DirectChannel;
@@ -54,16 +55,16 @@ public class FilterAnnotationPostProcessorTests {
private final QueueChannel outputChannel = new QueueChannel();
@Before
@BeforeEach
public void init() {
this.context.registerChannel("input", this.inputChannel);
this.context.registerChannel("output", this.outputChannel);
this.postProcessor.setBeanFactory(this.context.getBeanFactory());
this.postProcessor.afterPropertiesSet();
this.postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory());
this.postProcessor.postProcessBeanFactory(this.context.getBeanFactory());
this.postProcessor.afterSingletonsInstantiated();
}
@After
@AfterEach
public void tearDown() {
this.context.close();
}
@@ -111,7 +112,7 @@ public class FilterAnnotationPostProcessorTests {
@Test
public void filterAnnotationWithAdviceArray() {
TestAdvice advice = new TestAdvice();
context.registerBean("adviceChain", new TestAdvice[] { advice });
context.registerBean("adviceChain", new TestAdvice[]{ advice });
testValidFilter(new TestFilterWithAdviceDiscardWithin());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
assertThat(TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0)).isSameAs(advice);
@@ -122,10 +123,10 @@ public class FilterAnnotationPostProcessorTests {
public void filterAnnotationWithAdviceArrayTwice() {
TestAdvice advice1 = new TestAdvice();
TestAdvice advice2 = new TestAdvice();
context.registerBean("adviceChain1", new TestAdvice[] { advice1, advice2 });
context.registerBean("adviceChain1", new TestAdvice[]{ advice1, advice2 });
TestAdvice advice3 = new TestAdvice();
TestAdvice advice4 = new TestAdvice();
context.registerBean("adviceChain2", new TestAdvice[] { advice3, advice4 });
context.registerBean("adviceChain2", new TestAdvice[]{ advice3, advice4 });
testValidFilter(new TestFilterWithAdviceDiscardWithinTwice());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
List<?> adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class);
@@ -151,10 +152,10 @@ public class FilterAnnotationPostProcessorTests {
public void filterAnnotationWithAdviceCollectionTwice() {
TestAdvice advice1 = new TestAdvice();
TestAdvice advice2 = new TestAdvice();
context.registerBean("adviceChain1", new TestAdvice[] { advice1, advice2 });
context.registerBean("adviceChain1", new TestAdvice[]{ advice1, advice2 });
TestAdvice advice3 = new TestAdvice();
TestAdvice advice4 = new TestAdvice();
context.registerBean("adviceChain2", new TestAdvice[] { advice3, advice4 });
context.registerBean("adviceChain2", new TestAdvice[]{ advice3, advice4 });
testValidFilter(new TestFilterWithAdviceDiscardWithinTwice());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
List<?> adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class);

View File

@@ -17,7 +17,6 @@
package org.springframework.integration.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -27,9 +26,10 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -149,14 +149,6 @@ public class MessagingAnnotationPostProcessorTests {
context.close();
}
@Test
public void testPostProcessorWithoutBeanFactory() {
MessagingAnnotationPostProcessor postProcessor =
new MessagingAnnotationPostProcessor();
assertThatIllegalArgumentException()
.isThrownBy(postProcessor::afterPropertiesSet);
}
@Test
public void testChannelResolution() {
TestApplicationContext context = TestUtils.createTestApplicationContext();
@@ -307,12 +299,12 @@ public class MessagingAnnotationPostProcessorTests {
context.close();
}
private MessagingAnnotationPostProcessor prepareMessagingAnnotationPostProcessor(
private static MessagingAnnotationPostProcessor prepareMessagingAnnotationPostProcessor(
ConfigurableApplicationContext context) {
MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
postProcessor.setBeanFactory(context.getBeanFactory());
postProcessor.afterPropertiesSet();
postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) context.getBeanFactory());
postProcessor.postProcessBeanFactory(context.getBeanFactory());
postProcessor.afterSingletonsInstantiated();
return postProcessor;
}

View File

@@ -166,8 +166,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests {
assertThat(receive.getPayload()).isInstanceOf(MessageRejectedException.class);
MessageRejectedException exception = (MessageRejectedException) receive.getPayload();
assertThat(exception.getMessage())
.contains("message has been rejected in filter: bean " +
"'messagingAnnotationsWithBeanAnnotationTests.ContextConfiguration.filter.filter.handler'");
.contains("message has been rejected in filter: bean 'filter.filter.handler'");
}
@@ -330,7 +329,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests {
@Bean
@ServiceActivator(inputChannel = "aggregatorChannel")
public MessageHandler aggregator() {
public AggregatingMessageHandler aggregator() {
AggregatingMessageHandler handler = new AggregatingMessageHandler(MessageGroup::getMessages);
handler.setCorrelationStrategy(new ExpressionEvaluatingCorrelationStrategy("1"));
handler.setReleaseStrategy(new ExpressionEvaluatingReleaseStrategy("size() == 10"));
@@ -355,7 +354,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests {
@Bean
@Splitter(inputChannel = "splitterChannel", reactive = @Reactive("reactiveCustomizer"))
public MessageHandler splitter() {
public DefaultMessageSplitter splitter() {
DefaultMessageSplitter defaultMessageSplitter = new DefaultMessageSplitter();
defaultMessageSplitter.setOutputChannelName("serviceChannel");
return defaultMessageSplitter;
@@ -470,7 +469,7 @@ public class MessagingAnnotationsWithBeanAnnotationTests {
@Bean
@Splitter(inputChannel = "splitterChannel", applySequence = "false")
public MessageHandler splitter() {
public DefaultMessageSplitter splitter() {
return new DefaultMessageSplitter();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,10 +21,11 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.channel.DirectChannel;
@@ -42,6 +43,8 @@ public class RouterAnnotationPostProcessorTests {
private final TestApplicationContext context = TestUtils.createTestApplicationContext();
private final MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
private final DirectChannel inputChannel = new DirectChannel();
private final QueueChannel outputChannel = new QueueChannel();
@@ -53,26 +56,26 @@ public class RouterAnnotationPostProcessorTests {
private final QueueChannel stringChannel = new QueueChannel();
@Before
@BeforeEach
public void init() {
context.registerChannel("input", inputChannel);
context.registerChannel("output", outputChannel);
context.registerChannel("routingChannel", routingChannel);
context.registerChannel("integerChannel", integerChannel);
context.registerChannel("stringChannel", stringChannel);
this.postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory());
this.postProcessor.postProcessBeanFactory(this.context.getBeanFactory());
this.postProcessor.afterSingletonsInstantiated();
}
@After
@AfterEach
public void tearDown() {
this.context.close();
}
@Test
public void testRouter() {
MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
postProcessor.setBeanFactory(context.getBeanFactory());
postProcessor.afterPropertiesSet();
postProcessor.afterSingletonsInstantiated();
TestRouter testRouter = new TestRouter();
postProcessor.postProcessAfterInitialization(testRouter, "test");
context.refresh();
@@ -84,10 +87,6 @@ public class RouterAnnotationPostProcessorTests {
@Test
public void testRouterWithListParam() {
MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
postProcessor.setBeanFactory(context.getBeanFactory());
postProcessor.afterPropertiesSet();
postProcessor.afterSingletonsInstantiated();
TestRouter testRouter = new TestRouter();
postProcessor.postProcessAfterInitialization(testRouter, "test");
context.refresh();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -18,10 +18,11 @@ package org.springframework.integration.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.Lifecycle;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Splitter;
@@ -39,20 +40,20 @@ import org.springframework.messaging.support.GenericMessage;
*/
public class SplitterAnnotationPostProcessorTests {
private TestApplicationContext context = TestUtils.createTestApplicationContext();
private final TestApplicationContext context = TestUtils.createTestApplicationContext();
private DirectChannel inputChannel = new DirectChannel();
private final DirectChannel inputChannel = new DirectChannel();
private QueueChannel outputChannel = new QueueChannel();
private final QueueChannel outputChannel = new QueueChannel();
@Before
@BeforeEach
public void init() {
context.registerChannel("input", inputChannel);
context.registerChannel("output", outputChannel);
this.context.registerChannel("input", this.inputChannel);
this.context.registerChannel("output", this.outputChannel);
}
@After
@AfterEach
public void tearDown() {
this.context.close();
}
@@ -60,8 +61,8 @@ public class SplitterAnnotationPostProcessorTests {
@Test
public void testSplitterAnnotation() {
MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor();
postProcessor.setBeanFactory(context.getBeanFactory());
postProcessor.afterPropertiesSet();
postProcessor.postProcessBeanDefinitionRegistry((BeanDefinitionRegistry) this.context.getBeanFactory());
postProcessor.postProcessBeanFactory(this.context.getBeanFactory());
postProcessor.afterSingletonsInstantiated();
TestSplitter splitter = new TestSplitter();
postProcessor.postProcessAfterInitialization(splitter, "testSplitter");

View File

@@ -298,11 +298,11 @@ public class EnableIntegrationTests {
private CountDownLatch inputReceiveLatch;
@Autowired
@Qualifier("enableIntegrationTests.ContextConfiguration2.sendAsyncHandler.serviceActivator")
@Qualifier("sendAsyncHandler.serviceActivator")
private AbstractEndpoint sendAsyncHandler;
@Autowired
@Qualifier("enableIntegrationTests.ChildConfiguration.autoCreatedChannelMessageSource.inboundChannelAdapter")
@Qualifier("autoCreatedChannelMessageSource.inboundChannelAdapter")
private Lifecycle autoCreatedChannelMessageSourceAdapter;
@Autowired
@@ -669,8 +669,7 @@ public class EnableIntegrationTests {
.isThrownBy(() -> this.metaBridgeInput.send(testMessage))
.withMessageContaining("Dispatcher has no subscribers");
this.context.getBean("enableIntegrationTests.ContextConfiguration.metaBridgeOutput.bridgeFrom",
Lifecycle.class).start();
this.context.getBean("metaBridgeOutput.bridgeFrom", Lifecycle.class).start();
this.metaBridgeInput.send(testMessage);
receive = this.metaBridgeOutput.receive(10_000);
@@ -697,8 +696,7 @@ public class EnableIntegrationTests {
.isThrownBy(() -> this.myBridgeToInput.send(testMessage))
.withMessageContaining("Dispatcher has no subscribers");
this.context.getBean("enableIntegrationTests.ContextConfiguration.myBridgeToInput.bridgeTo",
Lifecycle.class).start();
this.context.getBean("myBridgeToInput.bridgeTo", Lifecycle.class).start();
this.myBridgeToInput.send(bridgeMessage);
receive = replyChannel.receive(10_000);
@@ -741,8 +739,7 @@ public class EnableIntegrationTests {
assertThat(this.roleController.noEndpointsRunning("bar")).isFalse();
Map<String, Boolean> state = this.roleController.getEndpointsRunningStatus("foo");
assertThat(state.get("annotationTestService.handle.serviceActivator")).isEqualTo(Boolean.FALSE);
assertThat(state.get("enableIntegrationTests.ContextConfiguration2.sendAsyncHandler.serviceActivator"))
.isEqualTo(Boolean.TRUE);
assertThat(state.get("sendAsyncHandler.serviceActivator")).isEqualTo(Boolean.TRUE);
this.roleController.startLifecyclesInRole("foo");
assertThat(this.roleController.allEndpointsRunning("foo")).isTrue();
this.roleController.stopLifecyclesInRole("foo");
@@ -1690,6 +1687,7 @@ public class EnableIntegrationTests {
@InboundChannelAdapter(value = "counterChannel", autoStartup = "false", phase = "23")
public @interface MyInboundChannelAdapter {
@AliasFor(annotation = InboundChannelAdapter.class, attribute = "value")
String value() default "";
String autoStartup() default "";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2020 the original author or authors.
* Copyright 2018-2022 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.
@@ -67,7 +67,7 @@ public class BeanNameTests {
@SuppressWarnings("unused")
@Autowired
@Qualifier("eipBean.handler")
@Qualifier("eipBeanHandler")
private MessageHandler eipBeanHandler;
@SuppressWarnings("unused")
@@ -76,11 +76,11 @@ public class BeanNameTests {
@SuppressWarnings("unused")
@Autowired
@Qualifier("eipBean2.handler")
@Qualifier("eipBean2Handler")
private MessageHandler eipBean2Handler;
@Autowired
@Qualifier("eipBean2.handler.wrapper")
@Qualifier("eipBean2.handler")
private ReplyProducingMessageHandlerWrapper eipBean2HandlerWrapper;
@SuppressWarnings("unused")
@@ -98,7 +98,7 @@ public class BeanNameTests {
@SuppressWarnings("unused")
@Autowired
@Qualifier("eipSource.source")
@Qualifier("eipSourceSource")
private MessageSource<?> eipSourceSource;
@Test
@@ -124,10 +124,10 @@ public class BeanNameTests {
}
}
@Bean("eipBean.handler")
@Bean("eipBeanHandler")
@EndpointId("eipBean")
@ServiceActivator(inputChannel = "channel2")
public MessageHandler replyingHandler() {
public AbstractReplyProducingMessageHandler replyingHandler() {
return new AbstractReplyProducingMessageHandler() {
@Override
@@ -138,7 +138,7 @@ public class BeanNameTests {
};
}
@Bean("eipBean2.handler")
@Bean("eipBean2Handler")
@EndpointId("eipBean2")
@ServiceActivator(inputChannel = "channel3")
public MessageHandler handler() {
@@ -151,7 +151,7 @@ public class BeanNameTests {
return null;
}
@Bean("eipSource.source")
@Bean("eipSourceSource")
@EndpointId("eipSource")
@InboundChannelAdapter(channel = "channel3", poller = @Poller(fixedDelay = "5000"))
public MessageSource<?> source() {

View File

@@ -139,7 +139,7 @@ public class IntegrationGraphServerTests {
assertThat(links.size()).isEqualTo(34);
jsonArray =
JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter')]");
JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter.router')]");
Map<String, Object> expressionRouter = (Map<String, Object>) jsonArray.get(0);
assertThat(((List<?>) expressionRouter.get("routes")).size()).isEqualTo(0);
@@ -151,7 +151,7 @@ public class IntegrationGraphServerTests {
this.testSource.receive();
this.expressionRouterInput.send(MessageBuilder.withPayload("foo").setHeader("foo", "fizChannel").build());
jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router')]");
jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router.router')]");
String routerJson = jsonArray.toJSONString();
this.server.rebuild();
@@ -172,7 +172,7 @@ public class IntegrationGraphServerTests {
assertThat(links).isNotNull();
assertThat(links.size()).isEqualTo(37);
jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router')]");
jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'router.router')]");
routerJson = jsonArray.toJSONString();
assertThat(routerJson).contains("\"sendTimers\":{\"successes\":{\"count\":4");
jsonArray = JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'toRouter')]");
@@ -200,7 +200,7 @@ public class IntegrationGraphServerTests {
assertThat(sourceJson).contains("\"receiveCounters\":{\"successes\":2,\"failures\":1");
jsonArray =
JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter')]");
JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.name == 'expressionRouter.router')]");
expressionRouter = (Map<String, Object>) jsonArray.get(0);
JSONArray routes = (JSONArray) expressionRouter.get("routes");

View File

@@ -48,7 +48,6 @@ import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
@@ -71,7 +70,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
*/
@SpringJUnitConfig
@DirtiesContext
@TestExecutionListeners(DependencyInjectionTestExecutionListener.class)
@TestExecutionListeners(DependencyInjectionTestExecutionListener.class) // To ignore other default listeners
public class MicrometerMetricsTests {
@Autowired
@@ -149,7 +148,7 @@ public class MicrometerMetricsTests {
.counter().count()).isEqualTo(1);
assertThat(registry.get("spring.integration.send")
.tag("name", "eipBean.handler")
.tag("name", "eipBean")
.tag("result", "success")
.timer().count()).isEqualTo(1);
@@ -277,7 +276,7 @@ public class MicrometerMetricsTests {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
registry.config().meterFilter(MeterFilter.deny(id ->
"channel".equals(id.getTag("type")) &&
"noMeters".equals(id.getTag("name"))));
"noMeters".equals(id.getTag("name"))));
return registry;
}
@@ -292,7 +291,7 @@ public class MicrometerMetricsTests {
@Bean("eipBean.handler")
@EndpointId("eipBean")
@ServiceActivator(inputChannel = "channel2")
public MessageHandler replyingHandler() {
public AbstractReplyProducingMessageHandler replyingHandler() {
return new AbstractReplyProducingMessageHandler() {
@Override

View File

@@ -20,9 +20,8 @@ import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -31,9 +30,7 @@ import org.springframework.integration.hazelcast.HazelcastTestRequestHandlerAdvi
import org.springframework.integration.hazelcast.outbound.util.HazelcastOutboundChannelAdapterTestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.hazelcast.instance.impl.HazelcastInstanceFactory;
import com.hazelcast.map.IMap;
@@ -45,11 +42,11 @@ import com.hazelcast.topic.ITopic;
* Hazelcast Outbound Channel Adapter JavaConfig driven Unit Test Class
*
* @author Eren Avsarogullari
* @author Atem Bilan
*
* @since 6.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HazelcastIntegrationOutboundTestConfiguration.class,
loader = AnnotationConfigContextLoader.class)
@SpringJUnitConfig(classes = HazelcastIntegrationOutboundTestConfiguration.class)
@DirtiesContext
public class HazelcastOutboundChannelAdapterConfigTests {
@@ -141,7 +138,7 @@ public class HazelcastOutboundChannelAdapterConfigTests {
@Qualifier("replicatedMapRequestHandlerAdvice")
private HazelcastTestRequestHandlerAdvice replicatedMapRequestHandlerAdvice;
@AfterClass
@AfterAll
public static void shutdown() {
HazelcastInstanceFactory.terminateAll();
}

View File

@@ -80,9 +80,9 @@ public class StoredProcJavaConfigTests {
// verify maxMessagesPerPoll == 1
assertThat(received).isNull();
MessagingTemplate template = new MessagingTemplate(this.control);
template.convertAndSend("@'storedProcJavaConfigTests.Config.storedProc.inboundChannelAdapter'.stop()");
template.convertAndSend("@'storedProc.inboundChannelAdapter'.stop()");
assertThat(template.convertSendAndReceive(
"@'storedProcJavaConfigTests.Config.storedProc.inboundChannelAdapter'.isRunning()", Boolean.class))
"@'storedProc.inboundChannelAdapter'.isRunning()", Boolean.class))
.isFalse();
}

View File

@@ -139,14 +139,14 @@ public class JmsTests extends ActiveMQMultiContextTests {
@Test
public void testPollingFlow() {
this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.start()");
this.controlBus.send("@'integerMessageSource.inboundChannelAdapter'.start()");
assertThat(this.beanFactory.getBean("integerChannel")).isInstanceOf(FixedSubscriberChannel.class);
for (int i = 0; i < 5; i++) {
Message<?> message = this.outputChannel.receive(20000);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isEqualTo("" + i);
}
this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.stop()");
this.controlBus.send("@'integerMessageSource.inboundChannelAdapter'.stop()");
assertThat(((InterceptableChannel) this.outputChannel).getInterceptors())
.contains(this.testChannelInterceptor);
@@ -353,7 +353,7 @@ public class JmsTests extends ActiveMQMultiContextTests {
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlow
.from(Jms.messageDrivenChannelAdapter(amqFactory,
DefaultMessageListenerContainer.class)
DefaultMessageListenerContainer.class)
.outputChannel(jmsMessageDrivenInputChannel())
.destination("jmsMessageDriven")
.configureListenerContainer(c -> c.clientId("foo")))
@@ -406,23 +406,23 @@ public class JmsTests extends ActiveMQMultiContextTests {
@Bean
public IntegrationFlow jmsInboundGatewayFlow() {
return IntegrationFlow.from(
Jms.inboundGateway(amqFactory)
.requestChannel(jmsInboundGatewayInputChannel())
.replyTimeout(1)
.errorOnTimeout(true)
.errorChannel(new FixedSubscriberChannel(new AbstractReplyProducingMessageHandler() {
Jms.inboundGateway(amqFactory)
.requestChannel(jmsInboundGatewayInputChannel())
.replyTimeout(1)
.errorOnTimeout(true)
.errorChannel(new FixedSubscriberChannel(new AbstractReplyProducingMessageHandler() {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return "error: " +
((MessageTimeoutException) requestMessage.getPayload())
.getFailedMessage().getPayload() + " is not convertible";
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return "error: " +
((MessageTimeoutException) requestMessage.getPayload())
.getFailedMessage().getPayload() + " is not convertible";
}
}))
.requestDestination("jmsPipelineTest")
.configureListenerContainer(c ->
c.transactionManager(mock(PlatformTransactionManager.class))))
}))
.requestDestination("jmsPipelineTest")
.configureListenerContainer(c ->
c.transactionManager(mock(PlatformTransactionManager.class))))
.filter(payload -> !"junk".equals(payload))
.<String, String>transform(String::toUpperCase)
.get();

View File

@@ -34,13 +34,14 @@ import org.springframework.integration.aggregator.ExpressionEvaluatingMessageGro
import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy;
import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy;
import org.springframework.integration.aggregator.MessageCountReleaseStrategy;
import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport;
import org.springframework.integration.router.RecipientListRouter;
import org.springframework.integration.scattergather.ScatterGatherHandler;
@@ -58,6 +59,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Artem Bilan
* @author Gary Russell
*
* @since 4.1
*/
@SpringJUnitConfig
@@ -79,7 +81,6 @@ public class ScatterGatherHandlerIntegrationTests {
@Test
public void testSimpleAuction() {
Message<String> quoteMessage = MessageBuilder.withPayload("testQuote").build();
this.inputAuctionWithoutGatherChannel.send(quoteMessage);
Message<?> bestQuoteMessage = this.output.receive(10000);
assertThat(bestQuoteMessage).isNotNull();
@@ -92,7 +93,6 @@ public class ScatterGatherHandlerIntegrationTests {
@Test
public void testAuctionWithGatherChannel() {
Message<String> quoteMessage = MessageBuilder.withPayload("testQuote").build();
this.inputAuctionWithGatherChannel.send(quoteMessage);
Message<?> bestQuoteMessage = this.output.receive(10000);
assertThat(bestQuoteMessage).isNotNull();
@@ -104,7 +104,6 @@ public class ScatterGatherHandlerIntegrationTests {
@Test
public void testDistribution() {
Message<String> quoteMessage = MessageBuilder.withPayload("testQuote").build();
this.distributionChannel.send(quoteMessage);
Message<?> bestQuoteMessage = this.output.receive(10000);
assertThat(bestQuoteMessage).isNotNull();
@@ -145,51 +144,25 @@ public class ScatterGatherHandlerIntegrationTests {
new ExpressionEvaluatingReleaseStrategy("size() == 2"));
}
@Bean
public MessageChannel inputAuctionWithoutGatherChannel() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "inputAuctionWithoutGatherChannel")
public MessageHandler scatterGatherAuctionWithoutGatherChannel() {
public ScatterGatherHandler scatterGatherAuctionWithoutGatherChannel() {
ScatterGatherHandler handler = new ScatterGatherHandler(scatterAuctionWithoutGatherChannel(), gatherer1());
handler.setOutputChannel(output());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel")
public MessageHandler auctionWithoutGatherChannelBridge1() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel1());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel")
public MessageHandler auctionWithoutGatherChannelBridge2() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel1());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel")
public MessageHandler auctionWithoutGatherChannelBridge3() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel1());
return handler;
}
@Bean
@BridgeFrom("scatterAuctionWithoutGatherChannel")
@BridgeFrom("scatterAuctionWithoutGatherChannel")
@BridgeFrom("scatterAuctionWithoutGatherChannel")
public MessageChannel serviceChannel1() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "serviceChannel1")
public MessageHandler service1() {
public AbstractReplyProducingMessageHandler service1() {
return new AbstractReplyProducingMessageHandler() {
@Override
@@ -201,7 +174,7 @@ public class ScatterGatherHandlerIntegrationTests {
}
@Bean
public MessageHandler distributor() {
public RecipientListRouter distributor() {
RecipientListRouter router = new RecipientListRouter();
router.setApplySequence(true);
router.setChannels(Arrays.asList(distributionChannel1(), distributionChannel2(), distributionChannel3()));
@@ -209,16 +182,19 @@ public class ScatterGatherHandlerIntegrationTests {
}
@Bean
@BridgeTo("serviceChannel1")
public MessageChannel distributionChannel1() {
return new DirectChannel();
}
@Bean
@BridgeTo("serviceChannel1")
public MessageChannel distributionChannel2() {
return new DirectChannel();
}
@Bean
@BridgeTo("serviceChannel1")
public MessageChannel distributionChannel3() {
return new DirectChannel();
}
@@ -230,36 +206,12 @@ public class ScatterGatherHandlerIntegrationTests {
@Bean
@ServiceActivator(inputChannel = "distributionChannel")
public MessageHandler scatterGatherDistribution() {
public ScatterGatherHandler scatterGatherDistribution() {
ScatterGatherHandler handler = new ScatterGatherHandler(distributor(), gatherer1());
handler.setOutputChannel(output());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "distributionChannel1")
public MessageHandler distributionBridge1() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel1());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "distributionChannel2")
public MessageHandler distributionBridge2() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel1());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "distributionChannel3")
public MessageHandler distributionBridge3() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel1());
return handler;
}
@Bean
public MessageChannel inputAuctionWithGatherChannel() {
return new DirectChannel();
@@ -288,7 +240,7 @@ public class ScatterGatherHandlerIntegrationTests {
@Bean
@ServiceActivator(inputChannel = "inputAuctionWithGatherChannel")
public MessageHandler scatterGatherAuctionWithGatherChannel() {
public ScatterGatherHandler scatterGatherAuctionWithGatherChannel() {
ScatterGatherHandler handler =
new ScatterGatherHandler(scatterAuctionWithGatherChannel(null), gatherer2());
handler.setGatherChannel(gatherChannel());
@@ -297,38 +249,10 @@ public class ScatterGatherHandlerIntegrationTests {
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel")
public MessageHandler auctionWithGatherChannelBridge1() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel2());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel")
public MessageHandler auctionWithGatherChannelBridge2() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel2());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel")
public MessageHandler auctionWithGatherChannelBridge3() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel2());
return handler;
}
@Bean
@ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel")
public MessageHandler auctionWithGatherChannelBridge4() {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(serviceChannel2());
return handler;
}
@Bean
@BridgeFrom("scatterAuctionWithGatherChannel")
@BridgeFrom("scatterAuctionWithGatherChannel")
@BridgeFrom("scatterAuctionWithGatherChannel")
@BridgeFrom("scatterAuctionWithGatherChannel")
public MessageChannel serviceChannel2() {
return new DirectChannel();
}

View File

@@ -100,9 +100,7 @@ public class MockMessageSourceTests {
@Test
public void testMockMessageSourceInConfig() {
Lifecycle channelAdapter =
this.applicationContext
.getBean("mockMessageSourceTests.Config.testingMessageSource.inboundChannelAdapter",
Lifecycle.class);
this.applicationContext.getBean("testingMessageSource.inboundChannelAdapter", Lifecycle.class);
channelAdapter.start();
Message<?> receive = this.results.receive(10_000);

View File

@@ -624,7 +624,7 @@ public class MyFlowConfiguration {
@Bean
@ServiceActivator(inputChannel = "httpChannel")
public MessageHandler httpHandler() {
public HttpRequestExecutingMessageHandler httpHandler() {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("https://foo/service");
handler.setExpectedResponseType(String.class);
handler.setOutputChannelName("outputChannel");
@@ -676,12 +676,15 @@ NOTE: The bean names are generated with the following algorithm:
* The `MessageHandler` (`MessageSource`) `@Bean` gets its own standard name from the method name or `name` attribute on the `@Bean`.
This works as though there were no messaging annotation on the `@Bean` method.
* The `AbstractEndpoint` bean name is generated with the following pattern: `[configurationComponentName].[methodName].[decapitalizedAnnotationClassShortName]`.
For example, the `SourcePollingChannelAdapter` endpoint for the `consoleSource()` definition <<annotations_on_beans,shown earlier>> gets a bean name of `myFlowConfiguration.consoleSource.inboundChannelAdapter`.
* The `AbstractEndpoint` bean name is generated with the following pattern: `[@Bean name].[decapitalizedAnnotationClassShortName]`.
For example, the `SourcePollingChannelAdapter` endpoint for the `consoleSource()` definition <<annotations_on_beans,shown earlier>> gets a bean name of `consoleSource.inboundChannelAdapter`.
Unlike with POJO methods, the bean method name is not included in the endpoint bean name.
See also <<./overview.adoc#endpoint-bean-names,Endpoint Bean Names>>.
* If `@Bean` cannot be used directly in the target endpoint (not an instance of a `MessageSource`, `AbstractReplyProducingMessageHandler` or `AbstractMessageRouter`), a respective `AbstractStandardMessageHandlerFactoryBean` is registered to delegate to this `@Bean`.
The bean name for this wrapper is generated with the following pattern: `[@Bean name].[decapitalizedAnnotationClassShortName].[handler (or source)]`.
IMPORTANT: When using these annotations on `@Bean` definitions, the `inputChannel` must reference a declared bean.
Channels are not automatically declared in this case.
Channels are automatically declared iif not present in the application context yet.
[NOTE]
=====