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..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 @@ -31,9 +31,11 @@ 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.integration.config.EnableIntegration; import org.springframework.integration.config.EnableIntegrationManagement; +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; @@ -53,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) @@ -97,6 +105,9 @@ public class IntegrationAutoConfiguration { } + /** + * Integration management configuration. + */ @Configuration @ConditionalOnClass({ EnableIntegrationManagement.class, EnableIntegrationMBeanExport.class }) @@ -107,9 +118,17 @@ public class IntegrationAutoConfiguration { @Configuration @EnableIntegrationManagement(defaultCountsEnabled = "true", defaultStatsEnabled = "true") protected static class EnableIntegrationManagementConfiguration { - } } + /** + * Integration component scan configuration. + */ + @ConditionalOnMissingBean(GatewayProxyFactoryBean.class) + @Import(IntegrationAutoConfigurationScanRegistrar.class) + protected static class IntegrationComponentScanAutoConfiguration { + + } + } 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 c42df154aed..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,12 +24,16 @@ 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; 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 +65,35 @@ 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(IntegrationComponentScanAutoConfiguration.class)) + .isNotNull(); } @Test - public void parentContext() { + public void explicitIntegrationComponentScan() { this.context = new AnnotationConfigApplicationContext(); - this.context.register(JmxAutoConfiguration.class, - IntegrationAutoConfiguration.class); + this.context.register(IntegrationComponentScanConfiguration.class, + JmxAutoConfiguration.class, IntegrationAutoConfiguration.class); this.context.refresh(); + assertThat(this.context.getBean(TestGateway.class)).isNotNull(); + assertThat(this.context + .getBeansOfType(IntegrationComponentScanAutoConfiguration.class)) + .isEmpty(); + } + + @Test + public void parentContext() { + 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 +166,15 @@ public class IntegrationAutoConfigurationTests { } + @Configuration + @IntegrationComponentScan + static class IntegrationComponentScanConfiguration { + + } + + @MessagingGateway + public interface TestGateway extends RequestReplyExchanger { + + } + } 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() {