From a79f71cbe802a8d87bc22e6eba67553ccedd54a7 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 21 Dec 2016 09:28:08 -0500 Subject: [PATCH 1/2] Add @IntegrationComponentScan auto-configuration Update Spring Integration auto-configuration so that `@IntegrationComponentScan` from `AutoConfigurationPackages` is implicitly applied. Prior to this commit `@MessagingGateway` interfaces would only get picked up if `@IntegrationComponentScan` was added alongside with the `@SpringBootApplication`. Fixes gh-2037 Closes gh-7718 --- .../IntegrationAutoConfiguration.java | 50 +++++++++++++++++++ .../IntegrationAutoConfigurationTests.java | 38 +++++++++++--- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java index e5e61e58d71..5334bc33954 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java @@ -16,11 +16,15 @@ package org.springframework.boot.autoconfigure.integration; +import java.util.Map; + import javax.management.MBeanServer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -31,9 +35,15 @@ import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.config.EnableIntegrationManagement; +import org.springframework.integration.config.IntegrationComponentScanRegistrar; +import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport; import org.springframework.integration.monitor.IntegrationMBeanExporter; import org.springframework.integration.support.management.IntegrationManagementConfigurer; @@ -107,6 +117,46 @@ public class IntegrationAutoConfiguration { @Configuration @EnableIntegrationManagement(defaultCountsEnabled = "true", defaultStatsEnabled = "true") protected static class EnableIntegrationManagementConfiguration { + } + + } + + @ConditionalOnMissingBean(GatewayProxyFactoryBean.class) + @Import(AutoIntegrationComponentScanRegistrar.class) + protected static class IntegrationComponentScanAutoConfiguration { + + } + + private static class AutoIntegrationComponentScanRegistrar + extends IntegrationComponentScanRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + final BeanDefinitionRegistry registry) { + StandardAnnotationMetadata metadata = new StandardAnnotationMetadata( + IntegrationComponentScanConfiguration.class, true) { + + @Override + public Map getAnnotationAttributes( + String annotationName) { + Map annotationAttributes = super.getAnnotationAttributes( + annotationName); + if (IntegrationComponentScan.class.getName().equals(annotationName)) { + BeanFactory beanFactory = (BeanFactory) registry; + if (AutoConfigurationPackages.has(beanFactory)) { + annotationAttributes.put("value", + AutoConfigurationPackages.get(beanFactory)); + } + } + return annotationAttributes; + } + + }; + super.registerBeanDefinitions(metadata, registry); + } + + @IntegrationComponentScan + private class IntegrationComponentScanConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java index c42df154aed..a445add2e15 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java @@ -30,6 +30,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.gateway.RequestReplyExchanger; import org.springframework.integration.support.channel.HeaderChannelRegistry; import org.springframework.integration.support.management.IntegrationManagementConfigurer; import org.springframework.jmx.export.MBeanExporter; @@ -61,24 +64,34 @@ public class IntegrationAutoConfigurationTests { @Test public void integrationIsAvailable() { load(); - assertThat(this.context.getBean(HeaderChannelRegistry.class)).isNotNull(); + assertThat(this.context.getBean(TestGateway.class)).isNotNull(); + assertThat(this.context.getBean(IntegrationAutoConfiguration.IntegrationComponentScanAutoConfiguration.class)) + .isNotNull(); + } + + @Test + public void explicitIntegrationComponentScan() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(IntegrationComponentScanConfiguration.class, + JmxAutoConfiguration.class, + IntegrationAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(TestGateway.class)).isNotNull(); + assertThat(this.context.getBeansOfType(IntegrationAutoConfiguration.IntegrationComponentScanAutoConfiguration.class)) + .isEmpty(); } @Test public void parentContext() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(JmxAutoConfiguration.class, - IntegrationAutoConfiguration.class); - this.context.refresh(); + load(); AnnotationConfigApplicationContext parent = this.context; this.context = new AnnotationConfigApplicationContext(); this.context.setParent(parent); this.context.register(JmxAutoConfiguration.class, IntegrationAutoConfiguration.class); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "SPRING_JMX_DEFAULT_DOMAIN=org.foo"); this.context.refresh(); assertThat(this.context.getBean(HeaderChannelRegistry.class)).isNotNull(); - ((ConfigurableApplicationContext) this.context.getParent()).close(); - this.context.close(); } @Test @@ -151,4 +164,15 @@ public class IntegrationAutoConfigurationTests { } + @Configuration + @IntegrationComponentScan + static class IntegrationComponentScanConfiguration { + + } + + @MessagingGateway + public interface TestGateway extends RequestReplyExchanger { + + } + } From 982f41b70c88784ada042362f5463cd6abf289d1 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 21 Dec 2016 20:06:36 -0800 Subject: [PATCH 2/2] Polish @IntegrationComponentScan auto-configuration See gh-2037 See gh-7718 --- .../IntegrationAutoConfiguration.java | 57 +++--------- ...grationAutoConfigurationScanRegistrar.java | 87 +++++++++++++++++++ .../IntegrationAutoConfigurationTests.java | 14 +-- .../integration/SampleCommandLineRunner.java | 38 ++++++++ .../integration/SampleMessageGateway.java | 26 ++++++ .../SampleIntegrationApplicationTests.java | 34 ++++---- 6 files changed, 191 insertions(+), 65 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationScanRegistrar.java create mode 100644 spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleCommandLineRunner.java create mode 100644 spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleMessageGateway.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java index 5334bc33954..0241676b84d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java @@ -16,15 +16,11 @@ package org.springframework.boot.autoconfigure.integration; -import java.util.Map; - import javax.management.MBeanServer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -37,12 +33,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.StandardAnnotationMetadata; -import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.config.EnableIntegrationManagement; -import org.springframework.integration.config.IntegrationComponentScanRegistrar; import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport; import org.springframework.integration.monitor.IntegrationMBeanExporter; @@ -63,12 +55,18 @@ import org.springframework.util.StringUtils; @AutoConfigureAfter(JmxAutoConfiguration.class) public class IntegrationAutoConfiguration { + /** + * Basic Spring Integration configuration. + */ @Configuration @EnableIntegration protected static class IntegrationConfiguration { } + /** + * Spring Integration JMX configuration. + */ @Configuration @ConditionalOnClass(EnableIntegrationMBeanExport.class) @ConditionalOnMissingBean(value = IntegrationMBeanExporter.class, search = SearchStrategy.CURRENT) @@ -107,6 +105,9 @@ public class IntegrationAutoConfiguration { } + /** + * Integration management configuration. + */ @Configuration @ConditionalOnClass({ EnableIntegrationManagement.class, EnableIntegrationMBeanExport.class }) @@ -121,45 +122,13 @@ public class IntegrationAutoConfiguration { } + /** + * Integration component scan configuration. + */ @ConditionalOnMissingBean(GatewayProxyFactoryBean.class) - @Import(AutoIntegrationComponentScanRegistrar.class) + @Import(IntegrationAutoConfigurationScanRegistrar.class) protected static class IntegrationComponentScanAutoConfiguration { } - private static class AutoIntegrationComponentScanRegistrar - extends IntegrationComponentScanRegistrar { - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - final BeanDefinitionRegistry registry) { - StandardAnnotationMetadata metadata = new StandardAnnotationMetadata( - IntegrationComponentScanConfiguration.class, true) { - - @Override - public Map getAnnotationAttributes( - String annotationName) { - Map annotationAttributes = super.getAnnotationAttributes( - annotationName); - if (IntegrationComponentScan.class.getName().equals(annotationName)) { - BeanFactory beanFactory = (BeanFactory) registry; - if (AutoConfigurationPackages.has(beanFactory)) { - annotationAttributes.put("value", - AutoConfigurationPackages.get(beanFactory)); - } - } - return annotationAttributes; - } - - }; - super.registerBeanDefinitions(metadata, registry); - } - - @IntegrationComponentScan - private class IntegrationComponentScanConfiguration { - - } - - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationScanRegistrar.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationScanRegistrar.java new file mode 100644 index 00000000000..87f2653b603 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationScanRegistrar.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.boot.autoconfigure.integration; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.config.IntegrationComponentScanRegistrar; + +/** + * Variation of {@link IntegrationComponentScanRegistrar} the links + * {@link AutoConfigurationPackages}. + * + * @author Artem Bilan + * @author Phillip Webb + */ +class IntegrationAutoConfigurationScanRegistrar extends IntegrationComponentScanRegistrar + implements BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + final BeanDefinitionRegistry registry) { + super.registerBeanDefinitions( + new IntegrationComponentScanConfigurationMetaData(this.beanFactory), + registry); + } + + private static class IntegrationComponentScanConfigurationMetaData + extends StandardAnnotationMetadata { + + private final BeanFactory beanFactory; + + IntegrationComponentScanConfigurationMetaData(BeanFactory beanFactory) { + super(IntegrationComponentScanConfiguration.class, true); + this.beanFactory = beanFactory; + } + + @Override + public Map getAnnotationAttributes(String annotationName) { + Map attributes = super.getAnnotationAttributes( + annotationName); + if (IntegrationComponentScan.class.getName().equals(annotationName) + && AutoConfigurationPackages.has(this.beanFactory)) { + List packages = AutoConfigurationPackages.get(this.beanFactory); + attributes = new LinkedHashMap(attributes); + attributes.put("value", packages.toArray(new String[packages.size()])); + } + return attributes; + } + } + + @IntegrationComponentScan + private static class IntegrationComponentScanConfiguration { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java index a445add2e15..381b945f8eb 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfigurationTests.java @@ -24,6 +24,7 @@ import javax.management.MBeanServer; import org.junit.After; import org.junit.Test; +import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration.IntegrationComponentScanAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -65,7 +66,7 @@ public class IntegrationAutoConfigurationTests { public void integrationIsAvailable() { load(); assertThat(this.context.getBean(TestGateway.class)).isNotNull(); - assertThat(this.context.getBean(IntegrationAutoConfiguration.IntegrationComponentScanAutoConfiguration.class)) + assertThat(this.context.getBean(IntegrationComponentScanAutoConfiguration.class)) .isNotNull(); } @@ -73,12 +74,12 @@ public class IntegrationAutoConfigurationTests { public void explicitIntegrationComponentScan() { this.context = new AnnotationConfigApplicationContext(); this.context.register(IntegrationComponentScanConfiguration.class, - JmxAutoConfiguration.class, - IntegrationAutoConfiguration.class); + JmxAutoConfiguration.class, IntegrationAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(TestGateway.class)).isNotNull(); - assertThat(this.context.getBeansOfType(IntegrationAutoConfiguration.IntegrationComponentScanAutoConfiguration.class)) - .isEmpty(); + assertThat(this.context + .getBeansOfType(IntegrationComponentScanAutoConfiguration.class)) + .isEmpty(); } @Test @@ -89,7 +90,8 @@ public class IntegrationAutoConfigurationTests { this.context.setParent(parent); this.context.register(JmxAutoConfiguration.class, IntegrationAutoConfiguration.class); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "SPRING_JMX_DEFAULT_DOMAIN=org.foo"); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, + "SPRING_JMX_DEFAULT_DOMAIN=org.foo"); this.context.refresh(); assertThat(this.context.getBean(HeaderChannelRegistry.class)).isNotNull(); } diff --git a/spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleCommandLineRunner.java b/spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleCommandLineRunner.java new file mode 100644 index 00000000000..1ec5db2d135 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleCommandLineRunner.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 sample.integration; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class SampleCommandLineRunner implements CommandLineRunner { + + private final SampleMessageGateway gateway; + + public SampleCommandLineRunner(SampleMessageGateway gateway) { + this.gateway = gateway; + } + + @Override + public void run(String... args) throws Exception { + for (String arg : args) { + this.gateway.echo(arg); + } + } + +} diff --git a/spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleMessageGateway.java b/spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleMessageGateway.java new file mode 100644 index 00000000000..6f824c87f96 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-integration/src/main/java/sample/integration/SampleMessageGateway.java @@ -0,0 +1,26 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 sample.integration; + +import org.springframework.integration.annotation.MessagingGateway; + +@MessagingGateway(defaultRequestChannel = "outputChannel") +public interface SampleMessageGateway { + + void echo(String message); + +} diff --git a/spring-boot-samples/spring-boot-sample-integration/src/test/java/sample/integration/consumer/SampleIntegrationApplicationTests.java b/spring-boot-samples/spring-boot-sample-integration/src/test/java/sample/integration/consumer/SampleIntegrationApplicationTests.java index bd5bd9ba432..ac013b438ab 100644 --- a/spring-boot-samples/spring-boot-sample-integration/src/test/java/sample/integration/consumer/SampleIntegrationApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-integration/src/test/java/sample/integration/consumer/SampleIntegrationApplicationTests.java @@ -23,9 +23,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import sample.integration.SampleIntegrationApplication; import sample.integration.producer.ProducerApplication; @@ -48,32 +47,37 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class SampleIntegrationApplicationTests { - private static ConfigurableApplicationContext context; - - @BeforeClass - public static void start() throws Exception { - context = SpringApplication.run(SampleIntegrationApplication.class); - } - - @AfterClass - public static void stop() { - if (context != null) { - context.close(); - } - } + private ConfigurableApplicationContext context; @Before public void deleteOutput() { + FileSystemUtils.deleteRecursively(new File("target/input")); FileSystemUtils.deleteRecursively(new File("target/output")); } + @After + public void stop() { + if (this.context != null) { + this.context.close(); + } + } + @Test public void testVanillaExchange() throws Exception { + this.context = SpringApplication.run(SampleIntegrationApplication.class); SpringApplication.run(ProducerApplication.class, "World"); String output = getOutput(); assertThat(output).contains("Hello World"); } + @Test + public void testMessageGateway() throws Exception { + this.context = SpringApplication.run(SampleIntegrationApplication.class, + "testviamg"); + String output = getOutput(); + assertThat(output).contains("testviamg"); + } + private String getOutput() throws Exception { Future future = Executors.newSingleThreadExecutor() .submit(new Callable() {