GH-3920: Support @Primary on @MessagingGateway (#3924)
* GH-3920: Support @Primary on @MessagingGateway Fixes https://github.com/spring-projects/spring-integration/issues/3920 Any Spring `@Component` can also be marked with a `@Primary`. The `@MessagingGateway` for proxying interfaces has no difference with a standard `@Component` definition * Modify `MessagingGatewayRegistrar` to obtain a `@Primary` marked from the `importingClassMetadata` and pass it to the target `BeanDefinition` * Verify the `@Primary` effect in the `GatewayInterfaceTests` * Document this improvement * * Fix Checkstyle violations
This commit is contained in:
@@ -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.
|
||||
@@ -36,6 +36,7 @@ import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
@@ -64,6 +65,8 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
|
||||
|
||||
private static final String PROXY_DEFAULT_METHODS_ATTR = "proxyDefaultMethods";
|
||||
|
||||
private static final String PRIMARY_ATTR = "primary";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) {
|
||||
@@ -76,6 +79,9 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
|
||||
annotationAttributes.put("serviceInterface", importingClassMetadata.getClassName());
|
||||
annotationAttributes.put(PROXY_DEFAULT_METHODS_ATTR,
|
||||
"" + annotationAttributes.remove(PROXY_DEFAULT_METHODS_ATTR));
|
||||
if (importingClassMetadata.isAnnotated(Primary.class.getName())) {
|
||||
annotationAttributes.put(PRIMARY_ATTR, true);
|
||||
}
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(parse(annotationAttributes, registry), registry);
|
||||
}
|
||||
}
|
||||
@@ -182,6 +188,7 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
|
||||
}
|
||||
|
||||
gatewayProxyBuilder.addConstructorArgValue(serviceInterface);
|
||||
gatewayProxyBuilder.setPrimary(gatewayAttributes.containsKey(PRIMARY_ATTR));
|
||||
|
||||
AbstractBeanDefinition beanDefinition = gatewayProxyBuilder.getBeanDefinition();
|
||||
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, serviceInterface);
|
||||
|
||||
@@ -40,6 +40,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
@@ -48,6 +50,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
@@ -99,6 +102,9 @@ public class GatewayInterfaceTests {
|
||||
|
||||
private static final String IGNORE_HEADER = "ignoreHeader";
|
||||
|
||||
@Autowired
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Autowired
|
||||
private Int2634Gateway int2634Gateway;
|
||||
|
||||
@@ -305,7 +311,7 @@ public class GatewayInterfaceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithServiceEquals() throws Exception {
|
||||
public void testWithServiceEquals() {
|
||||
ConfigurableApplicationContext ac =
|
||||
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
|
||||
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
|
||||
@@ -515,6 +521,34 @@ public class GatewayInterfaceTests {
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void primaryMarkerWins() {
|
||||
PrimaryGateway primaryGateway = this.beanFactory.getBean(PrimaryGateway.class);
|
||||
assertThat(AopUtils.isAopProxy(primaryGateway)).isTrue();
|
||||
|
||||
MessageHandler messageHandler = mock(MessageHandler.class);
|
||||
|
||||
((SubscribableChannel) this.errorChannel).subscribe(messageHandler);
|
||||
|
||||
primaryGateway.testGateway("test data");
|
||||
|
||||
ArgumentCaptor<Message<?>> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
|
||||
verify(messageHandler).handleMessage(messageArgumentCaptor.capture());
|
||||
|
||||
Message<?> receive = messageArgumentCaptor.getValue();
|
||||
|
||||
assertThat(receive).isNotNull()
|
||||
.extracting(Message::getPayload)
|
||||
.isEqualTo("test data");
|
||||
|
||||
PrimaryGateway notPrimaryGateway = this.beanFactory.getBean("notPrimaryGatewayInstance", PrimaryGateway.class);
|
||||
assertThat(AopUtils.isAopProxy(notPrimaryGateway)).isFalse();
|
||||
|
||||
((SubscribableChannel) this.errorChannel).unsubscribe(messageHandler);
|
||||
}
|
||||
|
||||
public interface Foo {
|
||||
|
||||
@Gateway(requestChannel = "requestChannelFoo")
|
||||
@@ -644,6 +678,11 @@ public class GatewayInterfaceTests {
|
||||
return new AnnotationGatewayProxyFactoryBean(GatewayByAnnotationGPFB.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PrimaryGateway notPrimaryGatewayInstance() {
|
||||
return payload -> { };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MessagingGateway
|
||||
@@ -738,6 +777,15 @@ public class GatewayInterfaceTests {
|
||||
|
||||
}
|
||||
|
||||
@Primary
|
||||
@MessagingGateway(defaultRequestChannel = "errorChannel")
|
||||
@TestMessagingGateway
|
||||
public interface PrimaryGateway {
|
||||
|
||||
void testGateway(Object payload);
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TestMessagingGateway {
|
||||
|
||||
@@ -317,6 +317,8 @@ See also <<./configuration.adoc#annotations,Annotation Support>>.
|
||||
|
||||
Along with the `@MessagingGateway` annotation you can mark a service interface with the `@Profile` annotation to avoid the bean creation, if such a profile is not active.
|
||||
|
||||
Starting with version 6.0, an interface with the `@MessagingGateway` can also be marked with a `@Primary` annotation for respective configuration logic as its possible with any Spring `@Component` definition.
|
||||
|
||||
NOTE: If you have no XML configuration, the `@EnableIntegration` annotation is required on at least one `@Configuration` class.
|
||||
See <<./overview.adoc#configuration-enable-integration,Configuration and `@EnableIntegration`>> for more information.
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ See <<./cassandra.adoc#cassandra,Apache Cassandra Support>> for more information
|
||||
Kotlin Coroutines support has been introduced to the framework.
|
||||
|
||||
See <<./kotlin-functions.adoc#kotlin-coroutines,Kotlin Coroutines>> for more information.
|
||||
|
||||
[[x6.0-general]]
|
||||
=== General Changes
|
||||
|
||||
@@ -109,9 +110,11 @@ See <<./dsl.adoc#java-dsl,Java DSL>> for more information.
|
||||
The `org.springframework.util.concurrent.ListenableFuture` has been deprecated starting with Spring Framework `6.0`.
|
||||
All Spring Integration async API has been migrated to the `CompletableFuture`.
|
||||
|
||||
Also Messaging Gateway interface method can now return `Future<Void>` and `Mono<Void>` with a proper asynchronous execution of the downstream flow.
|
||||
The Messaging Gateway interface method can now return `Future<Void>` and `Mono<Void>` with a proper asynchronous execution of the downstream flow.
|
||||
|
||||
See <<./gateway.adoc#async-gateway, Asynchronous Gateway>> for more information.
|
||||
Alongside with a `@MessagingGateway` annotation the interface can also be marked with a `@Primary`.
|
||||
|
||||
See <<./gateway.adoc#gateway, Messaging Gateway>> for more information.
|
||||
|
||||
The `integrationGlobalProperties` bean is now declared by the framework as an instance of `org.springframework.integration.context.IntegrationProperties` instead of the previously deprecated `java.util.Properties`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user