From 80402f89c390e1199b16ec4d5addd91096bfc570 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Fri, 28 Oct 2022 13:48:46 -0400 Subject: [PATCH 1/3] GH-3923: Add @MessagingGateway @Import support Fixes https://github.com/spring-projects/spring-integration/issues/3923 Currently, the `@MessagingGateway` interfaces can be scanned or created explicitly as `@Bean` definition for `AnnotationGatewayProxyFactoryBean` * Implement a `GatewayProxyBeanDefinitionPostProcessor` which is invoked before instantiation attempt on the `BeanDefinition` * Verify `@Import` for `@MessagingGateway` interface in the `GatewayInterfaceTests` --- ...atewayProxyInstantiationPostProcessor.java | 75 +++++++++++++++++++ .../config/IntegrationRegistrar.java | 13 ++++ .../gateway/GatewayInterfaceTests.java | 30 ++++++++ src/reference/asciidoc/gateway.adoc | 3 + src/reference/asciidoc/whats-new.adoc | 2 + 5 files changed, 123 insertions(+) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java new file mode 100644 index 00000000000..07d596b05f5 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GatewayProxyInstantiationPostProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean; + +/** + * The {@link InstantiationAwareBeanPostProcessor} to wrap beans for {@link MessagingGateway} + * into {@link AnnotationGatewayProxyFactoryBean}. + * + * @author Artem Bilan + * + * @since 6.0 + * + * @see AnnotationGatewayProxyFactoryBean + */ +class GatewayProxyInstantiationPostProcessor implements + InstantiationAwareBeanPostProcessor, ApplicationContextAware { + + private final BeanDefinitionRegistry registry; + + private ApplicationContext applicationContext; + + GatewayProxyInstantiationPostProcessor(BeanDefinitionRegistry registry) { + this.registry = registry; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + if (beanClass.isInterface() + && AnnotatedElementUtils.hasAnnotation(beanClass, MessagingGateway.class) + && this.registry.getBeanDefinition(beanName) instanceof AnnotatedGenericBeanDefinition) { + + AnnotationGatewayProxyFactoryBean gatewayProxyFactoryBean = + new AnnotationGatewayProxyFactoryBean<>(beanClass); + gatewayProxyFactoryBean.setApplicationContext(this.applicationContext); + gatewayProxyFactoryBean.setBeanFactory(this.applicationContext.getAutowireCapableBeanFactory()); + ClassLoader classLoader = this.applicationContext.getClassLoader(); + if (classLoader != null) { + gatewayProxyFactoryBean.setBeanClassLoader(classLoader); + } + gatewayProxyFactoryBean.setBeanName(beanName); + return gatewayProxyFactoryBean; + } + return null; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java index ee2b977659b..8300169a155 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java @@ -63,6 +63,7 @@ public void registerBeanDefinitions(@Nullable AnnotationMetadata importingClassM if (importingClassMetadata != null) { registerMessagingAnnotationPostProcessors(registry); } + registerGatewayProxyInstantiationPostProcessor(registry); } /** @@ -116,4 +117,16 @@ private void registerMessagingAnnotationPostProcessors(BeanDefinitionRegistry re } } + private void registerGatewayProxyInstantiationPostProcessor(BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition("gatewayProxyBeanDefinitionPostProcessor")) { + BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyInstantiationPostProcessor.class, + () -> new GatewayProxyInstantiationPostProcessor(registry)) + .addConstructorArgValue(registry) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + registry.registerBeanDefinition("gatewayProxyBeanDefinitionPostProcessor", builder.getBeanDefinition()); + } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index cf58f957be4..cfc0741acdf 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -41,6 +41,7 @@ import org.mockito.Mockito; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -53,6 +54,7 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -553,6 +555,26 @@ void primaryMarkerWins() { ((SubscribableChannel) this.errorChannel).unsubscribe(messageHandler); } + @Autowired + ImportedGateway importedGateway; + + @Test + void importedGatewayIsProxied() { + assertThat(AopUtils.isAopProxy(this.importedGateway)).isTrue(); + + AnnotationGatewayProxyFactoryBean gatewayProxyFactoryBean = + this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + ImportedGateway.class.getName(), + AnnotationGatewayProxyFactoryBean.class); + + assertThat(gatewayProxyFactoryBean.getObjectType()).isEqualTo(ImportedGateway.class); + assertThat(gatewayProxyFactoryBean.getGateways().keySet()) + .hasSize(1) + .extracting("name") + .contains("handle"); + + assertThat(gatewayProxyFactoryBean.getComponentName()).isEqualTo(ImportedGateway.class.getName()); + } + public interface Foo { @Gateway(requestChannel = "requestChannelFoo") @@ -623,6 +645,7 @@ public String service(String request) { @IntegrationComponentScan(useDefaultFilters = false, includeFilters = @ComponentScan.Filter(TestMessagingGateway.class)) @EnableIntegration + @Import(ImportedGateway.class) public static class TestConfig { @Bean(name = IntegrationContextUtils.INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME) @@ -756,6 +779,13 @@ public interface NotAGatewayByScanFilter { } + @MessagingGateway + public interface ImportedGateway { + + String handle(String payload); + + } + @MessagingGateway(defaultRequestChannel = "errorChannel") @TestMessagingGateway public interface IgnoredHeaderGateway { diff --git a/src/reference/asciidoc/gateway.adoc b/src/reference/asciidoc/gateway.adoc index 8b28162cff7..24e0ba515e6 100644 --- a/src/reference/asciidoc/gateway.adoc +++ b/src/reference/asciidoc/gateway.adoc @@ -319,6 +319,9 @@ Along with the `@MessagingGateway` annotation you can mark a service interface w 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. +Starting with version 6.0, `@MessagingGateway` interfaces can be used in the standard Spring `@Import` configuration. +This may be used as an alternative to the `@IntegrationComponentScan` or manual `AnnotationGatewayProxyFactoryBean` bean definitions. + 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. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index c2cdd26c939..e4beefc35d5 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -114,6 +114,8 @@ The Messaging Gateway interface method can now return `Future` and `Mono> 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`. From e456e96f294bf1ab4c70a4b5f5fc7b1bb0a54d62 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 31 Oct 2022 09:52:22 -0400 Subject: [PATCH 2/3] Remove supplier for `GatewayProxyInstantiationPostProcessor` bean definition Co-authored-by: Gary Russell --- .../springframework/integration/config/IntegrationRegistrar.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java index 8300169a155..7c1f8640888 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java @@ -121,7 +121,6 @@ private void registerGatewayProxyInstantiationPostProcessor(BeanDefinitionRegist if (!registry.containsBeanDefinition("gatewayProxyBeanDefinitionPostProcessor")) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyInstantiationPostProcessor.class, - () -> new GatewayProxyInstantiationPostProcessor(registry)) .addConstructorArgValue(registry) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); From 8ebc5ee2284aaa74c83dd4affc71e3c1384e95b7 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 31 Oct 2022 09:59:13 -0400 Subject: [PATCH 3/3] * Fix bean definition signature --- .../integration/config/IntegrationRegistrar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java index 7c1f8640888..3cd595328b8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java @@ -120,7 +120,7 @@ private void registerMessagingAnnotationPostProcessors(BeanDefinitionRegistry re private void registerGatewayProxyInstantiationPostProcessor(BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition("gatewayProxyBeanDefinitionPostProcessor")) { BeanDefinitionBuilder builder = - BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyInstantiationPostProcessor.class, + BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyInstantiationPostProcessor.class) .addConstructorArgValue(registry) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE);