From 8ac71c7b57bc00b291e84ec345235be4ac57ba32 Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Mon, 21 Nov 2016 23:58:18 -0500 Subject: [PATCH] Support flexible bound element types Fixes #519 Introduces some internal changes to the framework allowing the use of other types than MessageChannel/SubscribableChannel as bindable types (e.g. Flux, Observable, KStream, etc.) - Modify BinderFactory to allow the retrieval and lookup of a binder not only by name, but also by binding target type - Subsequent changes to ChannelBindingService and tests to account for the modified signature - Introduce BindingTargetFactory as the contract for creating bound elements - Remove any references to chanels and bound elements and use 'binding target' systematically across the board Reinstate our own Checkstyle checks with a reduced set of rules so that header validation can be performed automatically at compile time. Use ${project.version} for the checkstyle plugin configuration Renaming some occurences of 'boundElement' to 'bindingTarget' Renamed a stray occurence of 'channel' Fix some occurences of String concatenation in the same line after reformatting --- pom.xml | 491 +++++++++--------- .../stream/binder/AbstractBinderTests.java | 14 +- .../ContentTypeOutboundSourceTests.java | 3 +- .../config/CustomMessageConverterTests.java | 3 +- .../DeserializeJSONToJavaTypeTests.java | 3 +- .../stream/config/ErrorChannelTests.java | 3 +- ...ListenerAnnotatedMethodArgumentsTests.java | 1 + ...eamListenerContentTypeConversionTests.java | 1 + .../StreamListenerDuplicateMappingTests.java | 1 + .../StreamListenerHandlerBeanTests.java | 1 + .../StreamListenerHandlerMethodTests.java | 4 +- .../StreamListenerMessageArgumentTests.java | 1 + ...stenerMethodReturnWithConversionTests.java | 1 + ...mListenerMethodWithReturnMessageTests.java | 1 + ...eamListenerMethodWithReturnValueTests.java | 1 + .../config/StreamListenerTestUtils.java | 3 +- .../cloud/stream/reactive/FluxSender.java | 15 +- .../FluxToMessageChannelResultAdapter.java | 8 +- ...geChannelToFluxSenderParameterAdapter.java | 8 +- ...ageChannelToInputFluxParameterAdapter.java | 14 +- ...nnelToInputObservableParameterAdapter.java | 8 +- ...nelToObservableSenderParameterAdapter.java | 8 +- .../stream/reactive/ObservableSender.java | 13 +- ...servableToMessageChannelResultAdapter.java | 8 +- .../StreamListenerReactiveMethodTests.java | 1 + ...istenerReactiveReturnWithFailureTests.java | 4 +- ...istenerReactiveReturnWithMessageTests.java | 4 +- ...amListenerReactiveReturnWithPojoTests.java | 4 +- .../TestSupportBinderAutoConfiguration.java | 10 +- .../test/aggregate/AggregateTestWithBean.java | 16 + .../test/aggregate/AggregateTestWithMain.java | 16 + .../stream/test/example/ExampleTest.java | 3 +- .../resources/checkstyle-suppressions.xml | 1 + .../src/main/resources/checkstyle.xml | 285 +++++----- .../aggregate/AggregateApplication.java | 16 + .../AggregateApplicationBuilder.java | 50 +- .../aggregate/AggregateApplicationUtils.java | 47 +- .../SharedBindingTargetRegistry.java | 55 ++ .../aggregate/SharedChannelRegistry.java | 36 +- .../stream/annotation/EnableBinding.java | 14 +- .../cloud/stream/annotation/Input.java | 2 +- .../cloud/stream/annotation/Output.java | 2 +- .../stream/annotation/StreamListener.java | 47 +- .../cloud/stream/binder/AbstractBinder.java | 41 +- .../cloud/stream/binder/Binder.java | 19 +- .../stream/binder/BinderConfiguration.java | 11 +- .../cloud/stream/binder/BinderFactory.java | 10 +- .../stream/binder/DefaultBinderFactory.java | 81 +-- .../binding/AbstractBindingTargetFactory.java | 46 ++ .../cloud/stream/binding/Bindable.java | 8 +- .../cloud/stream/binding/BindableAdapter.java | 8 +- .../binding/BindableChannelFactory.java | 44 -- .../stream/binding/BindableProxyFactory.java | 193 +++---- .../binding/BinderAwareChannelResolver.java | 31 +- .../BindingBeanDefinitionRegistryUtils.java | 85 ++- .../cloud/stream/binding/BindingService.java | 180 +++++++ .../stream/binding/BindingTargetFactory.java | 51 ++ .../stream/binding/ChannelBindingService.java | 157 ------ .../binding/DynamicDestinationsBindable.java | 2 +- .../stream/binding/InputBindingLifecycle.java | 18 +- ...ageChannelStreamListenerResultAdapter.java | 8 +- .../binding/MessageConverterConfigurer.java | 10 +- .../binding/OutputBindingLifecycle.java | 21 +- ....java => SingleBindingTargetBindable.java} | 27 +- ...amListenerAnnotationBeanPostProcessor.java | 119 +++-- .../binding/StreamListenerErrorMessages.java | 5 +- .../binding/StreamListenerMethodUtils.java | 32 +- .../StreamListenerParameterAdapter.java | 28 +- .../binding/StreamListenerResultAdapter.java | 24 +- ...scribableChannelBindingTargetFactory.java} | 11 +- .../config/BinderFactoryConfiguration.java | 12 +- ...ion.java => BindingAutoConfiguration.java} | 8 +- .../stream/config/BindingBeansRegistrar.java | 4 +- ....java => BindingServiceConfiguration.java} | 86 +-- .../config/BindingServiceProperties.java | 209 ++++++++ .../ChannelBindingServiceProperties.java | 139 ++--- .../EnvironmentEntryInitializingTreeMap.java | 16 + .../stream/endpoint/ChannelsEndpoint.java | 9 +- .../internal/InternalPropertyNames.java | 32 ++ .../stream/reflection/GenericsUtils.java | 88 ++++ .../main/resources/META-INF/spring.factories | 2 +- .../stream/aggregation/AggregationTest.java | 27 +- ...traryInterfaceWithBindingTargetsTests.java | 3 +- .../ArbitraryInterfaceWithDefaultsTests.java | 3 +- .../BinderAwareChannelResolverTests.java | 44 +- .../BinderFactoryConfigurationTests.java | 33 +- .../stream/binder/ErrorBindingTests.java | 8 +- ...ertiesBinderAwareChannelResolverTests.java | 26 +- .../HealthIndicatorsConfigurationTests.java | 9 +- .../binder/InputOutputBindingOrderTest.java | 5 +- ...ocessorBindingWithBindingTargetsTests.java | 3 +- .../ProcessorBindingsWithDefaultsTests.java | 3 +- .../SinkBindingWithDefaultTargetsTests.java | 3 +- .../binder/SinkBindingWithDefaultsTests.java | 3 +- .../SourceBindingWithBindingTargetsTests.java | 3 +- .../SourceBindingWithDefaultsTests.java | 3 +- ...iceTests.java => BindingServiceTests.java} | 88 ++-- .../partitioning/PartitionedConsumerTest.java | 3 +- .../partitioning/PartitionedProducerTest.java | 3 +- 99 files changed, 1997 insertions(+), 1375 deletions(-) create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedBindingTargetRegistry.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/AbstractBindingTargetFactory.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableChannelFactory.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingService.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingTargetFactory.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java rename spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/{SingleChannelBindable.java => SingleBindingTargetBindable.java} (54%) rename spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/{DefaultBindableChannelFactory.java => SubscribableChannelBindingTargetFactory.java} (75%) rename spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/{ChannelBindingAutoConfiguration.java => BindingAutoConfiguration.java} (91%) rename spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/{ChannelBindingServiceConfiguration.java => BindingServiceConfiguration.java} (74%) create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/internal/InternalPropertyNames.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/reflection/GenericsUtils.java rename spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/{ChannelBindingServiceTests.java => BindingServiceTests.java} (79%) diff --git a/pom.xml b/pom.xml index c4f6c6950..598196a05 100644 --- a/pom.xml +++ b/pom.xml @@ -1,246 +1,249 @@ - - 4.0.0 - org.springframework.cloud - spring-cloud-stream-parent - 1.1.1.BUILD-SNAPSHOT - pom - - org.springframework.cloud - spring-cloud-build - 1.2.1.RELEASE - - - - https://github.com/spring-cloud/spring-cloud-stream - scm:git:git://github.com/spring-cloud/spring-cloud-stream.git - scm:git:ssh://git@github.com/spring-cloud/spring-cloud-stream.git - HEAD - - - 1.7 - 1.1.10 - 1.0.0.RELEASE - 1.0.0.RELEASE - 3.0.2.RELEASE - 3.0.3 - 2.1 - - - - - org.springframework.cloud - spring-cloud-stream - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-tools - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-codec - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-rxjava - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-schema-server - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-binder-test - ${project.version} - - - org.springframework - spring-tuple - ${spring.tuple.version} - - - org.springframework.integration - spring-integration-tuple - ${spring.integration.tuple.version} - - - org.springframework.cloud - spring-cloud-stream-test-support - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-test-support-internal - ${project.version} - - - com.esotericsoftware - kryo-shaded - ${kryo-shaded.version} - - - io.projectreactor - reactor-core - ${reactor.version} - - - io.reactivex - rxjava - ${rxjava.version} - - - org.objenesis - objenesis - ${objenesis.version} - - - - - spring-cloud-stream - spring-cloud-stream-binder-test - spring-cloud-stream-codec - spring-cloud-stream-rxjava - spring-cloud-stream-test-support - spring-cloud-stream-test-support-internal - spring-cloud-stream-integration-tests - spring-cloud-stream-core-docs - spring-cloud-stream-reactive - spring-cloud-stream-schema - spring-cloud-stream-schema-server - spring-cloud-stream-tools - - - - - - org.apache.maven.plugins - maven-antrun-plugin - 1.7 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - - com.puppycrawl.tools - checkstyle - 7.1 - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - true - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - org.springframework.cloud - spring-cloud-build-tools - 1.2.0.RELEASE - - - - - checkstyle-validation - validate - - checkstyle.xml - UTF-8 - true - true - true - - - check - - - - - - - - - spring - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/libs-release-local - - false - - - - - + + 4.0.0 + org.springframework.cloud + spring-cloud-stream-parent + 1.1.1.BUILD-SNAPSHOT + pom + + org.springframework.cloud + spring-cloud-build + 1.2.1.RELEASE + + + + https://github.com/spring-cloud/spring-cloud-stream + scm:git:git://github.com/spring-cloud/spring-cloud-stream.git + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-stream.git + HEAD + + + 1.7 + 1.1.10 + 1.0.0.RELEASE + 1.0.0.RELEASE + 3.0.2.RELEASE + 3.0.3 + 2.1 + + + + + org.springframework.cloud + spring-cloud-stream + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-tools + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-codec + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-rxjava + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-schema-server + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-binder-test + ${project.version} + + + org.springframework + spring-tuple + ${spring.tuple.version} + + + org.springframework.integration + spring-integration-tuple + ${spring.integration.tuple.version} + + + org.springframework.cloud + spring-cloud-stream-test-support + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-test-support-internal + ${project.version} + + + com.esotericsoftware + kryo-shaded + ${kryo-shaded.version} + + + io.projectreactor + reactor-core + ${reactor.version} + + + io.reactivex + rxjava + ${rxjava.version} + + + org.objenesis + objenesis + ${objenesis.version} + + + + + spring-cloud-stream + spring-cloud-stream-binder-test + spring-cloud-stream-codec + spring-cloud-stream-rxjava + spring-cloud-stream-test-support + spring-cloud-stream-test-support-internal + spring-cloud-stream-integration-tests + spring-cloud-stream-core-docs + spring-cloud-stream-reactive + spring-cloud-stream-schema + spring-cloud-stream-schema-server + spring-cloud-stream-tools + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + com.puppycrawl.tools + checkstyle + 7.1 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + org.springframework.cloud + spring-cloud-stream-tools + ${project.version} + + + + + checkstyle-validation + validate + + checkstyle.xml + checkstyle-header.txt + checkstyle-suppressions.xml + UTF-8 + true + true + true + + + check + + + + + + + + + spring + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/release + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/libs-release-local + + false + + + + + diff --git a/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java b/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java index e5a7354d4..8b7c5f8c2 100644 --- a/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java +++ b/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java @@ -25,7 +25,7 @@ import org.junit.Test; import org.springframework.cloud.stream.binding.MessageConverterConfigurer; import org.springframework.cloud.stream.config.BindingProperties; -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; +import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.Lifecycle; @@ -233,17 +233,17 @@ public abstract class AbstractBinderTests received = (Message) ((TestSupportBinder) binderFactory.getBinder(null)) + Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testSource.output()).poll(); assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()).isEqualTo("application/json"); assertThat(received).hasFieldOrPropertyWithValue("payload", "{\"message\":\"Hi\"}"); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java index b9410e4d2..ef617c8a6 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java @@ -37,6 +37,7 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.AbstractMessageConverter; import org.springframework.messaging.converter.MessageConverter; @@ -70,7 +71,7 @@ public class CustomMessageConverterTests { BarConverter.class, DefaultDatatypeChannelMessageConverter.class); testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build()); @SuppressWarnings("unchecked") - Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null)) + Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testSource.output()).poll(1, TimeUnit.SECONDS); Assert.assertThat(received, notNullValue()); assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeType.valueOf("test/foo")); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java index f01e224a0..5f0fc97f2 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java @@ -34,6 +34,7 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.converter.MessageConverter; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -59,7 +60,7 @@ public class DeserializeJSONToJavaTypeTests { public void testMessageDeserialized() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").setHeader("contentType", "application/json").build()); @SuppressWarnings("unchecked") - Message received = ((TestSupportBinder) binderFactory.getBinder(null)) + Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); assertThat(received.getPayload()).isInstanceOf(Foo.class); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ErrorChannelTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ErrorChannelTests.java index b6d8312cd..1bf7ac7c4 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ErrorChannelTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ErrorChannelTests.java @@ -35,6 +35,7 @@ import org.springframework.integration.annotation.Poller; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.core.MessageSource; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.ErrorMessage; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -55,7 +56,7 @@ public class ErrorChannelTests { @Test public void testErrorChannelBinding() throws Exception { - Message message = ((TestSupportBinder) binderFactory.getBinder(null)).messageCollector().forChannel(errorChannel).poll(10, TimeUnit.SECONDS); + Message message = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)).messageCollector().forChannel(errorChannel).poll(10, TimeUnit.SECONDS); Assert.isTrue(message instanceof ErrorMessage, "Message should be an instance of ErrorMessage"); Assert.isTrue(message.getPayload() instanceof MessagingException, "Message payload should be an instance" + "of MessagingException"); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java index 59470d7c7..dee6606c1 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java index 52e6cec80..baae43ce9 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java index 252892cf5..2063ab7c2 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import org.junit.Test; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java index b9af0bb08..95fea05e0 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java index f83a45c6b..c21b4e150 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java @@ -208,9 +208,9 @@ public class StreamListenerHandlerMethodTests { latch.countDown(); } }); - processor.input().send(MessageBuilder.withPayload("{\"foo\":\"fooTESTing" + "\"}") + processor.input().send(MessageBuilder.withPayload("{\"foo\":\"fooTESTing\"}") .setHeader("contentType", "application/json").build()); - inboundChannel2.input().send(MessageBuilder.withPayload("{\"bar\":\"bartestING" + "\"}") + inboundChannel2.input().send(MessageBuilder.withPayload("{\"bar\":\"bartestING\"}") .setHeader("contentType", "application/json").build()); assertThat(latch.await(1, TimeUnit.SECONDS)); context.close(); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java index a4e6c13eb..02644a768 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java index 80bc47ed9..f16090988 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java index 4631eb1a5..e865a0d23 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java index 59f66838d..38f688475 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import java.util.ArrayList; diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java index 8ef914dfd..45070b7ed 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.config; import org.springframework.cloud.stream.annotation.Input; @@ -53,7 +54,7 @@ public class StreamListenerTestUtils { public interface FooInboundChannel1 { - public String INPUT = "foo1-input"; + String INPUT = "foo1-input"; @Input(FooInboundChannel1.INPUT) SubscribableChannel input(); diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxSender.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxSender.java index f02cdfbe3..d171b15b7 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxSender.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxSender.java @@ -20,18 +20,19 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** - * Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments annotated with {@link - * org.springframework.cloud.stream.annotation.Output}. + * Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments + * annotated with {@link org.springframework.cloud.stream.annotation.Output}. * @author Marius Bogoevici */ public interface FluxSender { /** - * Streams the {@link reactor.core.publisher.Flux} through the bound - * element corresponding to the {@link org.springframework.cloud.stream.annotation.Output} annotation of the - * argument. - * @param flux a {@link Flux} that will be streamed through the bound element - * @return a {@link Mono} representing the result of sending the flux (completion or error) + * Streams the {@link reactor.core.publisher.Flux} through the binding target + * corresponding to the {@link org.springframework.cloud.stream.annotation.Output} + * annotation of the argument. + * @param flux a {@link Flux} that will be streamed through the binding target + * @return a {@link Mono} representing the result of sending the flux (completion or + * error) */ Mono send(Flux flux); } diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxToMessageChannelResultAdapter.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxToMessageChannelResultAdapter.java index 689d4a28c..2264bfbd9 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxToMessageChannelResultAdapter.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/FluxToMessageChannelResultAdapter.java @@ -36,16 +36,16 @@ public class FluxToMessageChannelResultAdapter private Log log = LogFactory.getLog(FluxToMessageChannelResultAdapter.class); @Override - public boolean supports(Class resultType, Class boundType) { - return Flux.class.isAssignableFrom(resultType) && MessageChannel.class.isAssignableFrom(boundType); + public boolean supports(Class resultType, Class bindingTarget) { + return Flux.class.isAssignableFrom(resultType) && MessageChannel.class.isAssignableFrom(bindingTarget); } - public void adapt(Flux streamListenerResult, MessageChannel boundElement) { + public void adapt(Flux streamListenerResult, MessageChannel bindingTarget) { streamListenerResult .doOnError(e -> this.log.error("Error while processing result", e)) .retry() .subscribe( - result -> boundElement.send(result instanceof Message ? (Message) result + result -> bindingTarget.send(result instanceof Message ? (Message) result : MessageBuilder.withPayload(result).build())); } } diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToFluxSenderParameterAdapter.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToFluxSenderParameterAdapter.java index 46bf6b9f1..76a919ad1 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToFluxSenderParameterAdapter.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToFluxSenderParameterAdapter.java @@ -38,14 +38,14 @@ public class MessageChannelToFluxSenderParameterAdapter private Log log = LogFactory.getLog(MessageChannelToFluxSenderParameterAdapter.class); @Override - public boolean supports(Class boundElementType, MethodParameter methodParameter) { + public boolean supports(Class bindingTargetType, MethodParameter methodParameter) { ResolvableType type = ResolvableType.forMethodParameter(methodParameter); - return MessageChannel.class.isAssignableFrom(boundElementType) + return MessageChannel.class.isAssignableFrom(bindingTargetType) && FluxSender.class.isAssignableFrom(type.getRawClass()); } @Override - public FluxSender adapt(MessageChannel boundElement, MethodParameter parameter) { + public FluxSender adapt(MessageChannel bindingTarget, MethodParameter parameter) { return resultPublisher -> { MonoProcessor sendResult = MonoProcessor.create(); // add error handling and reconnect in the event of an error @@ -53,7 +53,7 @@ public class MessageChannelToFluxSenderParameterAdapter .doOnError(e -> this.log.error("Error during processing: ", e)) .retry() .subscribe( - result -> boundElement.send(result instanceof Message ? (Message) result : + result -> bindingTarget.send(result instanceof Message ? (Message) result : MessageBuilder.withPayload(result).build()), e -> sendResult.onError(e), () -> sendResult.onComplete()); return sendResult; diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputFluxParameterAdapter.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputFluxParameterAdapter.java index 82a41912c..5f1514ecc 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputFluxParameterAdapter.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputFluxParameterAdapter.java @@ -45,13 +45,13 @@ public class MessageChannelToInputFluxParameterAdapter } @Override - public boolean supports(Class boundElementType, MethodParameter methodParameter) { - return SubscribableChannel.class.isAssignableFrom(boundElementType) + public boolean supports(Class bindingTargetType, MethodParameter methodParameter) { + return SubscribableChannel.class.isAssignableFrom(bindingTargetType) && Flux.class.isAssignableFrom(methodParameter.getParameterType()); } @Override - public Flux adapt(final SubscribableChannel boundElement, MethodParameter parameter) { + public Flux adapt(final SubscribableChannel bindingTarget, MethodParameter parameter) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); final Class argumentClass = (resolvableType.getGeneric(0).getRawClass() != null) ? (resolvableType .getGeneric(0).getRawClass()) : Object.class; @@ -63,8 +63,8 @@ public class MessageChannelToInputFluxParameterAdapter emitter.next(message); } }; - boundElement.subscribe(messageHandler); - emitter.setCancellation(() -> boundElement.unsubscribe(messageHandler)); + bindingTarget.subscribe(messageHandler); + emitter.setCancellation(() -> bindingTarget.unsubscribe(messageHandler)); }).publish().autoConnect(); } else { @@ -80,8 +80,8 @@ public class MessageChannelToInputFluxParameterAdapter } } }; - boundElement.subscribe(messageHandler); - emitter.setCancellation(() -> boundElement.unsubscribe(messageHandler)); + bindingTarget.subscribe(messageHandler); + emitter.setCancellation(() -> bindingTarget.unsubscribe(messageHandler)); }).publish().autoConnect(); } } diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputObservableParameterAdapter.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputObservableParameterAdapter.java index 0fd8e2926..4d60f5466 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputObservableParameterAdapter.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToInputObservableParameterAdapter.java @@ -41,14 +41,14 @@ public class MessageChannelToInputObservableParameterAdapter this.messageChannelToInputFluxArgumentAdapter = messageChannelToInputFluxArgumentAdapter; } - public boolean supports(Class boundElementType, MethodParameter methodParameter) { - return SubscribableChannel.class.isAssignableFrom(boundElementType) + public boolean supports(Class bindingTargetType, MethodParameter methodParameter) { + return SubscribableChannel.class.isAssignableFrom(bindingTargetType) && Observable.class.isAssignableFrom(methodParameter.getParameterType()); } @Override - public Observable adapt(final SubscribableChannel boundElement, MethodParameter parameter) { + public Observable adapt(final SubscribableChannel bindingTarget, MethodParameter parameter) { return RxJava1Adapter.publisherToObservable( - this.messageChannelToInputFluxArgumentAdapter.adapt(boundElement, parameter)); + this.messageChannelToInputFluxArgumentAdapter.adapt(bindingTarget, parameter)); } } diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToObservableSenderParameterAdapter.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToObservableSenderParameterAdapter.java index 4b1235760..48aa57bc1 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToObservableSenderParameterAdapter.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/MessageChannelToObservableSenderParameterAdapter.java @@ -43,19 +43,19 @@ public class MessageChannelToObservableSenderParameterAdapter implements } @Override - public boolean supports(Class boundElementType, MethodParameter methodParameter) { + public boolean supports(Class bindingTargetType, MethodParameter methodParameter) { ResolvableType type = ResolvableType.forMethodParameter(methodParameter); - return MessageChannel.class.isAssignableFrom(boundElementType) + return MessageChannel.class.isAssignableFrom(bindingTargetType) && ObservableSender.class.isAssignableFrom(type.getRawClass()); } @Override - public ObservableSender adapt(MessageChannel boundElement, MethodParameter parameter) { + public ObservableSender adapt(MessageChannel bindingTarget, MethodParameter parameter) { return new ObservableSender() { private FluxSender fluxSender = MessageChannelToObservableSenderParameterAdapter.this .messageChannelToFluxSenderArgumentAdapter - .adapt(boundElement, parameter); + .adapt(bindingTarget, parameter); @Override public Single send(Observable observable) { diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableSender.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableSender.java index dd0051d90..d9443d049 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableSender.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableSender.java @@ -20,17 +20,20 @@ import rx.Observable; import rx.Single; /** - * Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments annotated with {@link - * org.springframework.cloud.stream.annotation.Output}. + * Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments + * annotated with {@link org.springframework.cloud.stream.annotation.Output}. + * * @author Marius Bogoevici */ public interface ObservableSender { /** - * Streams the {@link Observable} through the bound - * element corresponding to the {@link org.springframework.cloud.stream.annotation.Output} annotation of the + * Streams the {@link Observable} through the binding target corresponding to the + * {@link org.springframework.cloud.stream.annotation.Output} annotation of the * argument. - * @param observable an {@link Observable} that will be streamed through the bound element + * + * @param observable an {@link Observable} that will be streamed through the bound + * element * @return a {@link Single} representing the result of an operation */ Single send(Observable observable); diff --git a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableToMessageChannelResultAdapter.java b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableToMessageChannelResultAdapter.java index b67ca19c9..d7139e650 100644 --- a/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableToMessageChannelResultAdapter.java +++ b/spring-cloud-stream-reactive/src/main/java/org/springframework/cloud/stream/reactive/ObservableToMessageChannelResultAdapter.java @@ -40,13 +40,13 @@ public class ObservableToMessageChannelResultAdapter } @Override - public boolean supports(Class resultType, Class boundType) { + public boolean supports(Class resultType, Class bindingTarget) { return Observable.class.isAssignableFrom(resultType) - && MessageChannel.class.isAssignableFrom(boundType); + && MessageChannel.class.isAssignableFrom(bindingTarget); } - public void adapt(Observable streamListenerResult, MessageChannel boundElement) { + public void adapt(Observable streamListenerResult, MessageChannel bindingTarget) { this.fluxToMessageChannelResultAdapter.adapt(RxJava1Adapter.observableToFlux(streamListenerResult), - boundElement); + bindingTarget); } } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java index 95d62c345..0ddbd4e6c 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.stream.reactive; import org.junit.Test; diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java index c9cca769f..e901065c4 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java @@ -16,8 +16,6 @@ package org.springframework.cloud.stream.reactive; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.Collection; import java.util.UUID; @@ -42,6 +40,8 @@ import org.springframework.messaging.Message; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.support.MessageBuilder; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java index 9474c7798..714da085a 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java @@ -16,8 +16,6 @@ package org.springframework.cloud.stream.reactive; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.Collection; import java.util.UUID; @@ -42,6 +40,8 @@ import org.springframework.messaging.Message; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.support.MessageBuilder; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java index bd6df5dc9..70a874172 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java @@ -16,8 +16,6 @@ package org.springframework.cloud.stream.reactive; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.Collection; import java.util.concurrent.TimeUnit; @@ -41,6 +39,8 @@ import org.springframework.messaging.Message; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.support.MessageBuilder; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan diff --git a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java index 897620912..d81063211 100644 --- a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java +++ b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java @@ -19,6 +19,8 @@ package org.springframework.cloud.stream.test.binder; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderFactory; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -45,15 +47,15 @@ public class TestSupportBinderAutoConfiguration { public BinderFactory binderFactory() { return new BinderFactory() { @Override - public Binder getBinder(String configurationName) { - return messageChannelBinder; + public Binder getBinder(String configurationName, Class bindableType) { + return (Binder) messageChannelBinder; } }; } @Bean - public MessageCollector messageCollector(BinderFactory binderFactory) { - return ((TestSupportBinder) binderFactory.getBinder(null)).messageCollector(); + public MessageCollector messageCollector(BinderFactory binderFactory) { + return ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)).messageCollector(); } } diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithBean.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithBean.java index aa7aee1b8..a9a4955de 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithBean.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithBean.java @@ -1,3 +1,19 @@ +/* + * Copyright 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.cloud.stream.test.aggregate; import java.util.concurrent.TimeUnit; diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithMain.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithMain.java index 83f245605..fa3a66fc0 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithMain.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateTestWithMain.java @@ -1,3 +1,19 @@ +/* + * Copyright 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.cloud.stream.test.aggregate; import java.util.concurrent.TimeUnit; diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java index 7de0b9462..e6fbfcea4 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java @@ -29,7 +29,6 @@ import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.integration.annotation.Transformer; import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -46,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class ExampleTest { @Autowired - private BinderFactory binderFactory; + private BinderFactory binderFactory; @Autowired private MessageCollector messageCollector; diff --git a/spring-cloud-stream-tools/src/main/resources/checkstyle-suppressions.xml b/spring-cloud-stream-tools/src/main/resources/checkstyle-suppressions.xml index 54b366fd6..bdcd8fc68 100644 --- a/spring-cloud-stream-tools/src/main/resources/checkstyle-suppressions.xml +++ b/spring-cloud-stream-tools/src/main/resources/checkstyle-suppressions.xml @@ -4,4 +4,5 @@ "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> + \ No newline at end of file diff --git a/spring-cloud-stream-tools/src/main/resources/checkstyle.xml b/spring-cloud-stream-tools/src/main/resources/checkstyle.xml index 2d7db87b2..283e4e2c9 100644 --- a/spring-cloud-stream-tools/src/main/resources/checkstyle.xml +++ b/spring-cloud-stream-tools/src/main/resources/checkstyle.xml @@ -1,151 +1,164 @@ + "-//Puppy Crawl//DTD Check Configuration 1.3//EN" + "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> - - - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + - - - - - - + + + + + + - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + --> + + + + diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplication.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplication.java index 7945d4de8..cf9633cc9 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplication.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplication.java @@ -1,3 +1,19 @@ +/* + * Copyright 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.cloud.stream.aggregate; /** diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationBuilder.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationBuilder.java index 600f02742..87318115f 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationBuilder.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationBuilder.java @@ -77,7 +77,7 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic private boolean webEnvironment = true; public AggregateApplicationBuilder(String... args) { - this(new Object[]{ ParentConfiguration.class }, args); + this(new Object[] { ParentConfiguration.class }, args); } public AggregateApplicationBuilder(Object source, String... args) { @@ -184,8 +184,7 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic Class appToEmbed = appConfigurer.getApp(); // Always update namespace before preparing SharedChannelRegistry if (appConfigurer.namespace == null) { - appConfigurer.namespace = AggregateApplicationUtils.getDefaultNamespace(appConfigurer.getApp().getName(), - i); + appConfigurer.namespace = AggregateApplicationUtils.getDefaultNamespace(appConfigurer.getApp().getName(), i); } appsToEmbed.put(appToEmbed, appConfigurer.namespace); appConfigurers.put(appConfigurer, appConfigurer.namespace); @@ -195,18 +194,21 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic this.parentArgs.toArray(new String[0]), selfContained(), this.webEnvironment, this.headless); } else { - if (BeanFactoryUtils.beansOfTypeIncludingAncestors(this.parentContext, SharedChannelRegistry.class) + if (BeanFactoryUtils.beansOfTypeIncludingAncestors(this.parentContext, SharedBindingTargetRegistry.class) .size() == 0) { + SharedBindingTargetRegistry sharedBindingTargetRegistry = new SharedBindingTargetRegistry(); + this.parentContext.getBeanFactory().registerSingleton("sharedBindingTargetRegistry", + sharedBindingTargetRegistry); this.parentContext.getBeanFactory().registerSingleton("sharedChannelRegistry", - new SharedChannelRegistry()); + new SharedChannelRegistry(sharedBindingTargetRegistry)); } } - SharedChannelRegistry sharedChannelRegistry = this.parentContext.getBean(SharedChannelRegistry.class); - AggregateApplicationUtils.prepareSharedChannelRegistry(sharedChannelRegistry, appsToEmbed); + SharedBindingTargetRegistry sharedBindingTargetRegistry = this.parentContext.getBean(SharedBindingTargetRegistry.class); + AggregateApplicationUtils.prepareSharedBindingTargetRegistry(sharedBindingTargetRegistry, appsToEmbed); PropertySources propertySources = this.parentContext.getEnvironment() .getPropertySources(); - for (Map.Entry appConfigurerEntry : appConfigurers - .entrySet()) { + for (Map.Entry appConfigurerEntry : appConfigurers.entrySet()) { + AppConfigurer appConfigurer = appConfigurerEntry.getKey(); String namespace = appConfigurerEntry.getValue().toLowerCase(); Set argsToUpdate = new LinkedHashSet<>(); @@ -219,7 +221,8 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic // only update the values with the highest precedence level. if (!relaxedNameKeyExists(entry.getKey(), argKeys)) { String key = entry.getKey(); - // in case of environment variables pass the lower-case property key + // in case of environment variables pass the lower-case property + // key // as we pass the properties as command line properties if (key.contains("_")) { key = key.replace("_", "-").toLowerCase(); @@ -232,8 +235,9 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic // Add the args that are set at the application level if they weren't // overridden above from other property sources. if (appConfigurer.getArgs() != null) { - for (String arg: appConfigurer.getArgs()) { - // use the key part left to the assignment and trimming the prefix `--` + for (String arg : appConfigurer.getArgs()) { + // use the key part left to the assignment and trimming the prefix + // `--` String key = arg.substring(0, arg.indexOf("=")).substring(2); if (!relaxedNameKeyExists(key, argKeys)) { argsToUpdate.add(arg); @@ -269,10 +273,9 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic return false; } - private ChildContextBuilder childContext(Class app, - ConfigurableApplicationContext parentContext, String namespace) { - return new ChildContextBuilder(AggregateApplicationUtils.embedApp(parentContext, - namespace, app)); + private ChildContextBuilder childContext(Class app, ConfigurableApplicationContext parentContext, + String namespace) { + return new ChildContextBuilder(AggregateApplicationUtils.embedApp(parentContext, namespace, app)); } public class SourceConfigurer extends AppConfigurer { @@ -364,9 +367,8 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic } void embed() { - final ConfigurableApplicationContext childContext = - childContext(this.app, AggregateApplicationBuilder.this.parentContext, - this.namespace).args(this.args).config(this.names) + final ConfigurableApplicationContext childContext = childContext(this.app, + AggregateApplicationBuilder.this.parentContext, this.namespace).args(this.args).config(this.names) .profiles(this.profiles).run(); // Register bindable proxies as beans so they can be queried for later Map bindableProxies = BeanFactoryUtils @@ -456,10 +458,16 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic @EnableAutoConfiguration @EnableBinding public static class ParentConfiguration { + @Bean + @ConditionalOnMissingBean(SharedBindingTargetRegistry.class) + public SharedBindingTargetRegistry sharedBindingTargetRegistry() { + return new SharedBindingTargetRegistry(); + } + @Bean @ConditionalOnMissingBean(SharedChannelRegistry.class) - public SharedChannelRegistry sharedChannelRegistry() { - return new SharedChannelRegistry(); + public SharedChannelRegistry sharedChannelRegistry(SharedBindingTargetRegistry sharedBindingTargetRegistry) { + return new SharedChannelRegistry(sharedBindingTargetRegistry); } } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationUtils.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationUtils.java index 58f1b0620..1c58a2666 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationUtils.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/AggregateApplicationUtils.java @@ -21,6 +21,7 @@ import java.util.Map.Entry; import org.springframework.boot.Banner.Mode; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.stream.internal.InternalPropertyNames; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.integration.channel.DirectChannel; import org.springframework.messaging.SubscribableChannel; @@ -34,29 +35,20 @@ import org.springframework.messaging.SubscribableChannel; */ abstract class AggregateApplicationUtils { - private static final String SPRING_CLOUD_STREAM_INTERNAL_PREFIX = "spring.cloud.stream.internal"; + public static final String INPUT_BINDING_NAME = "input"; - private static final String CHANNEL_NAMESPACE_PROPERTY_NAME = - SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".channelNamespace"; + public static final String OUTPUT_BINDING_NAME = "output"; - private static final String SELF_CONTAINED_APP_PROPERTY_NAME = - SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".selfContained"; - - public static final String INPUT_CHANNEL_NAME = "input"; - - public static final String OUTPUT_CHANNEL_NAME = "output"; - - static ConfigurableApplicationContext createParentContext(Object[] sources, String[] args, final - boolean selfContained, boolean webEnvironment, + static ConfigurableApplicationContext createParentContext(Object[] sources, + String[] args, final boolean selfContained, boolean webEnvironment, boolean headless) { SpringApplicationBuilder aggregatorParentConfiguration = new SpringApplicationBuilder(); - aggregatorParentConfiguration - .sources(sources) - .web(webEnvironment) + aggregatorParentConfiguration.sources(sources).web(webEnvironment) .headless(headless) .properties("spring.jmx.default-domain=" - + AggregateApplicationBuilder.ParentConfiguration.class.getName(), - SELF_CONTAINED_APP_PROPERTY_NAME + "=" + selfContained); + + AggregateApplicationBuilder.ParentConfiguration.class.getName(), + InternalPropertyNames.SELF_CONTAINED_APP_PROPERTY_NAME + "=" + + selfContained); return aggregatorParentConfiguration.run(args); } @@ -64,32 +56,31 @@ abstract class AggregateApplicationUtils { return appClassName + "_" + index; } - protected static SpringApplicationBuilder embedApp( ConfigurableApplicationContext parentContext, String namespace, Class app) { - return new SpringApplicationBuilder(app) - .web(false) - .main(app) - .bannerMode(Mode.OFF) + return new SpringApplicationBuilder(app).web(false).main(app).bannerMode(Mode.OFF) .properties("spring.jmx.default-domain=" + namespace) - .properties(CHANNEL_NAMESPACE_PROPERTY_NAME + "=" + namespace) - .registerShutdownHook(false) - .parent(parentContext); + .properties( + InternalPropertyNames.NAMESPACE_PROPERTY_NAME + "=" + namespace) + .registerShutdownHook(false).parent(parentContext); } - static void prepareSharedChannelRegistry(SharedChannelRegistry sharedChannelRegistry, + static void prepareSharedBindingTargetRegistry( + SharedBindingTargetRegistry sharedBindingTargetRegistry, LinkedHashMap, String> appsWithNamespace) { int i = 0; SubscribableChannel sharedChannel = null; for (Entry, String> appEntry : appsWithNamespace.entrySet()) { String namespace = appEntry.getValue(); if (i > 0) { - sharedChannelRegistry.register(namespace + "." + INPUT_CHANNEL_NAME, sharedChannel); + sharedBindingTargetRegistry.register(namespace + "." + INPUT_BINDING_NAME, + sharedChannel); } sharedChannel = new DirectChannel(); if (i < appsWithNamespace.size() - 1) { - sharedChannelRegistry.register(namespace + "." + OUTPUT_CHANNEL_NAME, sharedChannel); + sharedBindingTargetRegistry.register(namespace + "." + OUTPUT_BINDING_NAME, + sharedChannel); } i++; } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedBindingTargetRegistry.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedBindingTargetRegistry.java new file mode 100644 index 000000000..b17d9eefd --- /dev/null +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedBindingTargetRegistry.java @@ -0,0 +1,55 @@ +/* + * Copyright 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.cloud.stream.aggregate; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Stores binding targets shared by the components of an aggregate application. + * + * @author Marius Bogoevici + * @since 1.1.1 + */ +public class SharedBindingTargetRegistry { + + private Map sharedBindingTargets = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER); + + public T get(String id, Class bindingTargetType) { + Object sharedBindingTarget = this.sharedBindingTargets.get(id); + if (sharedBindingTarget == null) { + return null; + } + if (!bindingTargetType.isAssignableFrom(sharedBindingTarget.getClass())) { + throw new IllegalArgumentException("A shared " + bindingTargetType.getName() + " was requested, " + + "but the existing shared target with id '" + id + "' is a " + sharedBindingTarget.getClass()); + } + else { + return (T) sharedBindingTarget; + } + } + + public void register(String id, Object bindingTarget) { + this.sharedBindingTargets.put(id, bindingTarget); + } + + public Map getAll() { + return Collections.unmodifiableMap(this.sharedBindingTargets); + } + +} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedChannelRegistry.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedChannelRegistry.java index 2dc52ae00..5114a6668 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedChannelRegistry.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/aggregate/SharedChannelRegistry.java @@ -17,31 +17,45 @@ package org.springframework.cloud.stream.aggregate; import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentSkipListMap; import org.springframework.messaging.MessageChannel; /** - * A registry for channels that can be shared between modules, used for module aggregation. + * Wraps the {@link SharedBindingTargetRegistry} for access to {@link MessageChannel} + * instances. This class is provided as a convenience for users of + * {@link SharedChannelRegistry} in previous versions and will be removed in the future. + * * @author Marius Bogoevici + * @deprecated in favour of {@link SharedBindingTargetRegistry} */ +@Deprecated public class SharedChannelRegistry { - /** - * A {@link Map} of channels, indexed by name. A channel's name may be prefixed by a namespace. - */ - private Map sharedChannels = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER); + private final SharedBindingTargetRegistry sharedBindingTargetRegistry; - public MessageChannel get(String id) { - return sharedChannels.get(id); + public SharedChannelRegistry(SharedBindingTargetRegistry sharedBindingTargetRegistry) { + this.sharedBindingTargetRegistry = sharedBindingTargetRegistry; } - public void register(String id, MessageChannel messageChannel) { - this.sharedChannels.put(id, messageChannel); + public MessageChannel get(String id) { + return this.sharedBindingTargetRegistry.get(id, MessageChannel.class); + } + + public void register(String id, MessageChannel bindingTarget) { + this.sharedBindingTargetRegistry.register(id, bindingTarget); } public Map getAll() { - return Collections.unmodifiableMap(this.sharedChannels); + Map sharedBindingTargets = this.sharedBindingTargetRegistry.getAll(); + Map sharedMessageChannels = new HashMap<>(); + for (Map.Entry sharedBindingTargetEntry : sharedBindingTargets.entrySet()) { + if (MessageChannel.class.isAssignableFrom(sharedBindingTargetEntry.getValue().getClass())) { + sharedMessageChannels.put(sharedBindingTargetEntry.getKey(), + (MessageChannel) sharedBindingTargetEntry.getValue()); + } + } + return Collections.unmodifiableMap(sharedMessageChannels); } } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java index 5b95a311e..b81eb6758 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java @@ -25,33 +25,33 @@ import java.lang.annotation.Target; import org.springframework.cloud.stream.config.BinderFactoryConfiguration; import org.springframework.cloud.stream.config.BindingBeansRegistrar; -import org.springframework.cloud.stream.config.ChannelBindingServiceConfiguration; +import org.springframework.cloud.stream.config.BindingServiceConfiguration; import org.springframework.cloud.stream.config.SpelExpressionConverterConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.integration.config.EnableIntegration; /** - * Enables the binding of components annotated with {@link Input} and {@link Output} to a broker, according to the list - * of interfaces passed as value to the annotation. + * Enables the binding of targets annotated with {@link Input} and {@link Output} to a + * broker, according to the list of interfaces passed as value to the annotation. * * @author Dave Syer * @author Marius Bogoevici * @author David Turanski */ -@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Configuration -@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class, - SpelExpressionConverterConfiguration.class}) +@Import({ BindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class, + SpelExpressionConverterConfiguration.class }) @EnableIntegration public @interface EnableBinding { /** * A list of interfaces having methods annotated with {@link Input} and/or - * {@link Output} to indicate bindable components. + * {@link Output} to indicate binding targets. */ Class[] value() default {}; diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Input.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Input.java index 3931c040b..7dd88c120 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Input.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Input.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Qualifier; /** - * Indicates that an input channel will be created by the framework. + * Indicates that an input binding target will be created by the framework. * * @author Dave Syer * @author Marius Bogoevici diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Output.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Output.java index 3405a4306..ba30f2432 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Output.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/Output.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Qualifier; /** - * Indicates that an output channel will be created by the framework. + * Indicates that an output binding target will be created by the framework. * * @author Dave Syer * @author Marius Bogoevici diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java index ee5e8714c..0b4c9fc13 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java @@ -36,34 +36,35 @@ import org.springframework.messaging.handler.annotation.MessageMapping; *

Declarative mode

* * A method is considered declarative if all its method parameter types and return type - * (if not void) are bound elements or conversion targets from bound elements via a + * (if not void) are binding targets or conversion targets from binding targets via a * registered {@link StreamListenerParameterAdapter}. * - * Only declarative methods can have bound elements or conversion targets as arguments and - * return type. + * Only declarative methods can have binding targets or conversion targets as arguments + * and return type. * * Declarative methods must specify what inputs and outputs correspond to their arguments * and return type, and can do this in one of the following ways. * *
    *
  • By using either the {@link Input} or {@link Output} annotation for each of the - * parameters and the {@link Output} annotation on the method for the return type (if applicable). The use - * of annotations in this case is mandatory. In this case the {@link StreamListener} - * annotation must not specify a value.
  • - *
  • By setting an {@link Input} bound target as the annotation value of {@link StreamListener} - * and using {@link org.springframework.messaging.handler.annotation.SendTo}
  • on the method - * for the return type (if applicable). In this case the method must have exactly one parameter, corresponding - * to an input. + * parameters and the {@link Output} annotation on the method for the return type (if + * applicable). The use of annotations in this case is mandatory. In this case the + * {@link StreamListener} annotation must not specify a value. + *
  • By setting an {@link Input} bound target as the annotation value of + * {@link StreamListener} and using + * {@link org.springframework.messaging.handler.annotation.SendTo}
  • on the method for + * the return type (if applicable). In this case the method must have exactly one + * parameter, corresponding to an input. *
* * An example of declarative method signature using the former idiom is as follows: * *
  * {@code
- * @StreamListener
+ * @StreamListener
  * public @Output("joined") Flux join(
- *       @Input("input1") Flux input1,
- *       @Input("input2") Flux input2) {
+ *       @Input("input1") Flux input1,
+ *       @Input("input2") Flux input2) {
  *   // ... join the two input streams via functional operators
  * }
  * }
@@ -73,8 +74,8 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
  *
  * 
  * {@code
- * @StreamListener(Processor.INPUT)
- * @SendTo(Processor.OUTPUT)
+ * @StreamListener(Processor.INPUT)
+ * @SendTo(Processor.OUTPUT)
  * public Flux convert(Flux input) {
  *     return input.map(String::toUppercase);
  * }
@@ -90,7 +91,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
  * flexible signature, as described by {@link MessageMapping}.
  *
  * If the method returns a {@link org.springframework.messaging.Message}, the result will
- * be automatically sent to a channel, as follows:
+ * be automatically sent to a binding target, as follows:
  * 
    *
  • A result of the type {@link org.springframework.messaging.Message} will be sent * as-is
  • @@ -98,8 +99,8 @@ import org.springframework.messaging.handler.annotation.MessageMapping; * {@link org.springframework.messaging.Message} *
* - * The target channel of the return message is determined by consulting in the following - * order: + * The output binding target where the return message is sent is determined by consulting + * in the following order: *
    *
  • The {@link org.springframework.messaging.MessageHeaders} of the resulting * message.
  • @@ -112,12 +113,8 @@ import org.springframework.messaging.handler.annotation.MessageMapping; * *
      * {@code
    - * @StreamListener(Processor.INPUT)
    - * @SendTo(Processor.OUTPUT)
    - * public String convert(String input) {
    - *     return input.toUppercase();
    - * }
    - * }
    + * @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public String convert(String
    + * input) { return input.toUppercase(); } }
      *
      * @author Marius Bogoevici
      * @author Ilayaperumal Gopinathan
    @@ -132,7 +129,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
     public @interface StreamListener {
     
     	/**
    -	 * The name of the bound component (e.g. channel) that the method subscribes to.
    +	 * The name of the binding target (e.g. channel) that the method subscribes to.
     	 */
     	String value() default "";
     
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java
    index b6e8cabdb..ad13fe994 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java
    @@ -49,6 +49,8 @@ import org.springframework.util.ObjectUtils;
     import org.springframework.util.StringUtils;
     
     /**
    + * Base class for {@link Binder} implementations.
    + *
      * @author David Turanski
      * @author Gary Russell
      * @author Ilayaperumal Gopinathan
    @@ -59,7 +61,8 @@ public abstract class AbstractBinder {
     
     	/**
    -	 * The delimiter between a group and index when constructing a binder consumer/producer.
    +	 * The delimiter between a group and index when constructing a binder
    +	 * consumer/producer.
     	 */
     	private static final String GROUP_INDEX_DELIMITER = ".";
     
    @@ -78,15 +81,15 @@ public abstract class AbstractBinder bindConsumer(String name, String group, T target, C properties) {
     		if (StringUtils.isEmpty(group)) {
    -			Assert.isTrue(!properties.isPartitioned(),
    -					"A consumer group is required for a partitioned subscription");
    +			Assert.isTrue(!properties.isPartitioned(), "A consumer group is required for a partitioned subscription");
     		}
     		return doBindConsumer(name, group, target, properties);
     	}
    @@ -152,7 +154,7 @@ public abstract class AbstractBinder {
     
     	/**
    -	 * Bind the target component as a message consumer to the logical entity identified by the name.
    +	 * Bind the target component as a message consumer to the logical entity identified by
    +	 * the name.
     	 * @param name the logical identity of the message source
    -	 * @param group the consumer group to which this consumer belongs - subscriptions are shared among consumers
    -	 * in the same group (a null or empty String, must be treated as an anonymous group that doesn't share
    -	 * the subscription with any other consumer)
    +	 * @param group the consumer group to which this consumer belongs - subscriptions are
    +	 * shared among consumers in the same group (a null or empty String, must
    +	 * be treated as an anonymous group that doesn't share the subscription with any other
    +	 * consumer)
     	 * @param inboundBindTarget the app interface to be bound as a consumer
     	 * @param consumerProperties the consumer properties
     	 */
     	Binding bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
     
     	/**
    -	 * Bind the target component as a message producer to the logical entity identified by the name.
    +	 * Bind the target component as a message producer to the logical entity identified by
    +	 * the name.
     	 * @param name the logical identity of the message target
     	 * @param outboundBindTarget the app interface to be bound as a producer
     	 * @param producerProperties the producer properties
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderConfiguration.java
    index 3cb47fcbf..1355000c5 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderConfiguration.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderConfiguration.java
    @@ -19,8 +19,9 @@ package org.springframework.cloud.stream.binder;
     import java.util.Properties;
     
     /**
    - * Configuration for a binder instance, associating a {@link BinderType} with its configuration {@link Properties}.
    - * An application may contain multiple {@link BinderConfiguration}s per {@link BinderType}, when connecting to multiple
    + * Configuration for a binder instance, associating a {@link BinderType} with its
    + * configuration {@link Properties}. An application may contain multiple
    + * {@link BinderConfiguration}s per {@link BinderType}, when connecting to multiple
      * systems of the same type.
      *
      * @author Marius Bogoevici
    @@ -38,8 +39,10 @@ public class BinderConfiguration {
     	/**
     	 * @param binderType the binder type used by this configuration
     	 * @param properties the properties for setting up the binder
    -	 * @param inheritEnvironment whether the binder should inherit the environment of the application
    -	 * @param defaultCandidate whether the binder should be considered as a candidate when determining a default
    +	 * @param inheritEnvironment whether the binder should inherit the environment of the
    +	 * application
    +	 * @param defaultCandidate whether the binder should be considered as a candidate when
    +	 * determining a default
     	 */
     	public BinderConfiguration(BinderType binderType, Properties properties, boolean inheritEnvironment,
     			boolean defaultCandidate) {
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java
    index 7b7a24466..a6db121fd 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java
    @@ -19,14 +19,16 @@ package org.springframework.cloud.stream.binder;
     /**
      * @author Marius Bogoevici
      */
    -public interface BinderFactory {
    +public interface BinderFactory {
     
     	/**
    -	 * Returns the binder instance associated with the given configuration name. Instance caching is a requirement,
    -	 * and implementations must return the same instance on subsequent invocations with the same argument.
    +	 * Returns the binder instance associated with the given configuration name. Instance
    +	 * caching is a requirement, and implementations must return the same instance on
    +	 * subsequent invocations with the same arguments.
     	 *
     	 * @param configurationName the name of a binder configuration
     	 * @return the binder instance
     	 */
    -	Binder getBinder(String configurationName);
    +	 Binder getBinder(String configurationName,
    +			Class bindableType);
     }
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java
    index 6ecd9f019..aafcd37d1 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java
    @@ -35,6 +35,7 @@ import org.springframework.boot.actuate.health.Health;
     import org.springframework.boot.actuate.health.HealthIndicator;
     import org.springframework.boot.actuate.health.OrderedHealthAggregator;
     import org.springframework.boot.builder.SpringApplicationBuilder;
    +import org.springframework.cloud.stream.reflection.GenericsUtils;
     import org.springframework.context.ApplicationContext;
     import org.springframework.context.ApplicationContextAware;
     import org.springframework.context.ConfigurableApplicationContext;
    @@ -48,14 +49,16 @@ import org.springframework.util.StringUtils;
      * Default {@link BinderFactory} implementation.
      * @author Marius Bogoevici
      */
    -public class DefaultBinderFactory implements BinderFactory, DisposableBean, ApplicationContextAware {
    +public class DefaultBinderFactory implements BinderFactory, DisposableBean, ApplicationContextAware {
     
     	private final Map binderConfigurations;
     
    -	private final Map> binderInstanceCache = new HashMap<>();
    +	private final Map binderInstanceCache = new HashMap<>();
     
     	private volatile ConfigurableApplicationContext context;
     
    +	private Map defaultBinderForBindingTargetType = new HashMap<>();
    +
     	private volatile String defaultBinder;
     
     	private volatile CompositeHealthIndicator bindersHealthIndicator;
    @@ -82,14 +85,15 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean
     
     	@Override
     	public void destroy() throws Exception {
    -		for (Map.Entry> entry : this.binderInstanceCache.entrySet()) {
    -			BinderInstanceHolder binderInstanceHolder = entry.getValue();
    +		for (Map.Entry entry : this.binderInstanceCache.entrySet()) {
    +			BinderInstanceHolder binderInstanceHolder = entry.getValue();
     			binderInstanceHolder.getBinderContext().close();
     		}
    +		this.defaultBinderForBindingTargetType.clear();
     	}
     
     	@Override
    -	public synchronized Binder getBinder(String name) {
    +	public synchronized  Binder getBinder(String name, Class bindingTargetType) {
     		String configurationName;
     		// Fall back to a default if no argument is provided
     		if (StringUtils.isEmpty(name)) {
    @@ -106,37 +110,57 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean
     					}
     				}
     				if (defaultCandidateConfigurations.size() == 1) {
    -					this.defaultBinder = defaultCandidateConfigurations.iterator().next();
    -					configurationName = this.defaultBinder;
    +					configurationName = defaultCandidateConfigurations.iterator().next();
    +					this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
     				}
     				else {
     					if (defaultCandidateConfigurations.size() > 1) {
    -						throw new IllegalStateException(
    -								"A default binder has been requested, but there is more than one binder available: "
    -										+ StringUtils.collectionToCommaDelimitedString(defaultCandidateConfigurations)
    -										+ ", and no default binder has been set.");
    +						List candidatesForBindableType = new ArrayList<>();
    +						for (String defaultCandidateConfiguration : defaultCandidateConfigurations) {
    +							Binder binderInstance = getBinderInstance(defaultCandidateConfiguration);
    +							Class binderType = GenericsUtils.getParameterType(binderInstance.getClass(), Binder.class, 0);
    +							if (binderType.isAssignableFrom(bindingTargetType)) {
    +								candidatesForBindableType.add(defaultCandidateConfiguration);
    +							}
    +						}
    +						if (candidatesForBindableType.size() == 1) {
    +							configurationName = candidatesForBindableType.iterator().next();
    +							this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
    +						} else if (candidatesForBindableType.size() > 1) {
    +							throw new IllegalStateException(
    +									"A default binder has been requested, but there is more than one binder available for '"
    +											+ bindingTargetType.getName() + "' : "
    +											+ StringUtils.collectionToCommaDelimitedString(candidatesForBindableType)
    +											+ ", and no default binder has been set.");
    +						} else {
    +							throw new IllegalStateException("A default binder has been requested, but none of the " +
    +									"registered binders can bind a '" + bindingTargetType + "': "
    +									+ StringUtils.collectionToCommaDelimitedString(defaultCandidateConfigurations));
    +						}
     					}
     					else {
    -						throw new IllegalStateException(
    -								"A default binder has been requested, but there there is no binder available");
    +						throw new IllegalArgumentException(
    +								"A default binder has been requested, but there is no default available");
     					}
     				}
     			}
     			else {
    -				if (StringUtils.hasText(this.defaultBinder)) {
    -					configurationName = this.defaultBinder;
    -				}
    -				else {
    -					throw new IllegalStateException(
    -							"A default binder has been requested, but there is more than one binder available: "
    -									+ StringUtils.collectionToCommaDelimitedString(this.binderConfigurations.keySet())
    -									+ ", and no default binder has been set.");
    -				}
    +				configurationName = this.defaultBinder;
     			}
     		}
     		else {
     			configurationName = name;
     		}
    +		Binder binderInstance = getBinderInstance(configurationName);
    +		if (!(GenericsUtils.getParameterType(binderInstance.getClass(), Binder.class, 0)
    +				.isAssignableFrom(bindingTargetType))) {
    +			throw new IllegalStateException(
    +					"The binder '" + configurationName + "' cannot bind a " + bindingTargetType.getName());
    +		}
    +		return binderInstance;
    +	}
    +
    +	private  Binder getBinderInstance(String configurationName) {
     		if (!this.binderInstanceCache.containsKey(configurationName)) {
     			BinderConfiguration binderConfiguration = this.binderConfigurations.get(configurationName);
     			if (binderConfiguration == null) {
    @@ -196,28 +220,27 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean
     								healthAggregator, indicators);
     				this.bindersHealthIndicator.addHealthIndicator(configurationName, binderHealthIndicator);
     			}
    -			this.binderInstanceCache.put(configurationName, new BinderInstanceHolder<>(binder,
    +			this.binderInstanceCache.put(configurationName, new BinderInstanceHolder(binder,
     					binderProducingContext));
     		}
    -		return this.binderInstanceCache.get(configurationName).getBinderInstance();
    +		return (Binder) this.binderInstanceCache.get(configurationName).getBinderInstance();
     	}
     
     	/**
     	 * Utility class for storing {@link Binder} instances, along with their associated contexts.
    -	 * @param 
     	 */
    -	private static final class BinderInstanceHolder {
    +	private static final class BinderInstanceHolder {
     
    -		private final Binder binderInstance;
    +		private final Binder binderInstance;
     
     		private final ConfigurableApplicationContext binderContext;
     
    -		private BinderInstanceHolder(Binder binderInstance, ConfigurableApplicationContext binderContext) {
    +		private BinderInstanceHolder(Binder binderInstance, ConfigurableApplicationContext binderContext) {
     			this.binderInstance = binderInstance;
     			this.binderContext = binderContext;
     		}
     
    -		public Binder getBinderInstance() {
    +		public Binder getBinderInstance() {
     			return this.binderInstance;
     		}
     
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/AbstractBindingTargetFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/AbstractBindingTargetFactory.java
    new file mode 100644
    index 000000000..8c6806a80
    --- /dev/null
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/AbstractBindingTargetFactory.java
    @@ -0,0 +1,46 @@
    +/*
    + * Copyright 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.cloud.stream.binding;
    +
    +import org.springframework.util.Assert;
    +
    +/**
    + * A {@link BindingTargetFactory} implementation that restricts the type of binding target
    + * to a specified class and its supertypes.
    + *
    + * @author Marius Bogoevici
    + */
    +public abstract class AbstractBindingTargetFactory implements BindingTargetFactory {
    +
    +	private final Class bindingTargetType;
    +
    +	protected AbstractBindingTargetFactory(Class bindingTargetType) {
    +		Assert.notNull(bindingTargetType, "The binding target type cannot be null");
    +		this.bindingTargetType = bindingTargetType;
    +	}
    +
    +	@Override
    +	public final boolean canCreate(Class clazz) {
    +		return clazz.isAssignableFrom(this.bindingTargetType);
    +	}
    +
    +	@Override
    +	public abstract T createInput(String name);
    +
    +	@Override
    +	public abstract T createOutput(String name);
    +}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/Bindable.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/Bindable.java
    index aa134faf6..7a4469586 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/Bindable.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/Bindable.java
    @@ -30,22 +30,22 @@ public interface Bindable {
     	/**
     	 * Binds all the inputs associated with this instance.
     	 */
    -	void bindInputs(ChannelBindingService adapter);
    +	void bindInputs(BindingService adapter);
     
     	/**
     	 * Binds all the outputs associated with this instance.
     	 */
    -	void bindOutputs(ChannelBindingService adapter);
    +	void bindOutputs(BindingService adapter);
     
     	/**
     	 * Unbinds all the inputs associated with this instance.
     	 */
    -	void unbindInputs(ChannelBindingService adapter);
    +	void unbindInputs(BindingService adapter);
     
     	/**
     	 * Unbinds all the outputs associated with this instance.
     	 */
    -	void unbindOutputs(ChannelBindingService adapter);
    +	void unbindOutputs(BindingService adapter);
     
     	/**
     	 * Enumerates all the input binding names.
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableAdapter.java
    index 95fdd41a3..35cbc6c0d 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableAdapter.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableAdapter.java
    @@ -28,19 +28,19 @@ public class BindableAdapter implements Bindable {
     
     
     	@Override
    -	public void bindInputs(ChannelBindingService adapter) {
    +	public void bindInputs(BindingService adapter) {
     	}
     
     	@Override
    -	public void bindOutputs(ChannelBindingService adapter) {
    +	public void bindOutputs(BindingService adapter) {
     	}
     
     	@Override
    -	public void unbindInputs(ChannelBindingService adapter) {
    +	public void unbindInputs(BindingService adapter) {
     	}
     
     	@Override
    -	public void unbindOutputs(ChannelBindingService adapter) {
    +	public void unbindOutputs(BindingService adapter) {
     	}
     
     	@Override
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableChannelFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableChannelFactory.java
    deleted file mode 100644
    index c77289edc..000000000
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableChannelFactory.java
    +++ /dev/null
    @@ -1,44 +0,0 @@
    -/*
    - * Copyright 2015 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.cloud.stream.binding;
    -
    -import org.springframework.messaging.SubscribableChannel;
    -
    -/**
    - * Defines methods to create/configure the {@link org.springframework.messaging.MessageChannel}s defined
    - * in {@link org.springframework.cloud.stream.annotation.EnableBinding}.
    - * @author Ilayaperumal Gopinathan
    - */
    -public interface BindableChannelFactory {
    -
    -	/**
    -	 * Create an input {@link SubscribableChannel} that will be bound via
    -	 * the message channel {@link org.springframework.cloud.stream.binder.Binder}.
    -	 * @param name name of the message channel
    -	 * @return subscribable message channel
    -	 */
    -	SubscribableChannel createInputChannel(String name);
    -
    -	/**
    -	 * Create an output {@link SubscribableChannel} that will be bound via
    -	 * the message channel {@link org.springframework.cloud.stream.binder.Binder}.
    -	 * @param name name of the message channel
    -	 * @return subscribable message channel
    -	 */
    -	SubscribableChannel createOutputChannel(String name);
    -
    -}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableProxyFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableProxyFactory.java
    index 5db6ff67a..0ba5a7315 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableProxyFactory.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindableProxyFactory.java
    @@ -17,7 +17,9 @@
     package org.springframework.cloud.stream.binding;
     
     import java.lang.reflect.Method;
    +import java.util.ArrayList;
     import java.util.HashMap;
    +import java.util.List;
     import java.util.Map;
     import java.util.Set;
     
    @@ -31,15 +33,15 @@ import org.springframework.beans.factory.FactoryBean;
     import org.springframework.beans.factory.InitializingBean;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.beans.factory.annotation.Value;
    -import org.springframework.cloud.stream.aggregate.SharedChannelRegistry;
    +import org.springframework.cloud.stream.aggregate.SharedBindingTargetRegistry;
     import org.springframework.cloud.stream.annotation.EnableBinding;
     import org.springframework.cloud.stream.annotation.Input;
     import org.springframework.cloud.stream.annotation.Output;
    +import org.springframework.cloud.stream.internal.InternalPropertyNames;
     import org.springframework.core.annotation.AnnotationUtils;
    -import org.springframework.messaging.MessageChannel;
    -import org.springframework.messaging.SubscribableChannel;
     import org.springframework.util.Assert;
     import org.springframework.util.ReflectionUtils;
    +import org.springframework.util.StringUtils;
     
     /**
      * {@link FactoryBean} for instantiating the interfaces specified via
    @@ -55,26 +57,22 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean bindingTargetFactories;
     
     	private Class type;
     
     	private Object proxy;
     
    -	private Map inputHolders = new HashMap<>();
    +	private Map inputHolders = new HashMap<>();
     
    -	private Map outputHolders = new HashMap<>();
    +	private Map outputHolders = new HashMap<>();
     
     	public BindableProxyFactory(Class type) {
     		this.type = type;
    @@ -82,41 +80,40 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean returnType = method.getReturnType();
    +					Object sharedBindingTarget = locateSharedBindingTarget(name, returnType);
    +					if (sharedBindingTarget != null) {
    +						BindableProxyFactory.this.inputHolders.put(name,
    +								new BoundTargetHolder(sharedBindingTarget, false));
     					}
     					else {
    -						BindableProxyFactory.this.inputHolders.put(name, new ChannelHolder(sharedChannel, false));
    +						BindableProxyFactory.this.inputHolders.put(name,
    +								new BoundTargetHolder(getBindingTargetFactory(returnType).createInput(name), true));
     					}
     				}
     			}
    @@ -126,35 +123,55 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean returnType = method.getReturnType();
    +					Object sharedBindingTarget = locateSharedBindingTarget(name, returnType);
    +					if (sharedBindingTarget != null) {
    +						BindableProxyFactory.this.outputHolders.put(name,
    +								new BoundTargetHolder(sharedBindingTarget, false));
     					}
     					else {
    -						BindableProxyFactory.this.outputHolders.put(name, new ChannelHolder(sharedChannel, false));
    +						BindableProxyFactory.this.outputHolders.put(name,
    +								new BoundTargetHolder(getBindingTargetFactory(returnType).createOutput(name), true));
     					}
     				}
     			}
    -
     		});
     	}
     
    -	private void validateChannelType(Class channelType) {
    -		Assert.isTrue(SubscribableChannel.class.equals(channelType) || MessageChannel.class.equals(channelType),
    -				"A bound channel should be either a '" + MessageChannel.class.getName() + "', " +
    -						" or a '" + SubscribableChannel.class.getName() + "'");
    +	private BindingTargetFactory getBindingTargetFactory(Class bindingTargetType) {
    +		List candidateBindingTargetFactories = new ArrayList<>();
    +		for (Map.Entry bindingTargetFactoryEntry : this.bindingTargetFactories
    +				.entrySet()) {
    +			if (bindingTargetFactoryEntry.getValue().canCreate(bindingTargetType)) {
    +				candidateBindingTargetFactories.add(bindingTargetFactoryEntry.getKey());
    +			}
    +		}
    +		if (candidateBindingTargetFactories.size() == 1) {
    +			return this.bindingTargetFactories.get(candidateBindingTargetFactories.get(0));
    +		}
    +		else {
    +			if (candidateBindingTargetFactories.size() == 0) {
    +				throw new IllegalStateException("No factory found for binding target type: "
    +						+ bindingTargetType.getName() + " among registered factories: "
    +						+ StringUtils.collectionToCommaDelimitedString(bindingTargetFactories.keySet()));
    +			}
    +			else {
    +				throw new IllegalStateException(
    +						"Multiple factories found for binding target type: " + bindingTargetType.getName() + ": "
    +								+ StringUtils.collectionToCommaDelimitedString(candidateBindingTargetFactories));
    +			}
    +		}
     	}
     
    -	private MessageChannel locateSharedChannel(String name) {
    -		return this.sharedChannelRegistry != null ?
    -				this.sharedChannelRegistry.get(getNamespacePrefixedChannelName(name)) : null;
    +	private  T locateSharedBindingTarget(String name, Class bindingTargetType) {
    +		return this.sharedBindingTargetRegistry != null
    +				? this.sharedBindingTargetRegistry.get(getNamespacePrefixedBindingTargetName(name), bindingTargetType)
    +				: null;
     	}
     
    -	private String getNamespacePrefixedChannelName(String name) {
    -		return this.channelNamespace + "." + name;
    +	private String getNamespacePrefixedBindingTargetName(String name) {
    +		return this.namespace + "." + name;
     	}
     
     	@Override
    @@ -177,65 +194,67 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean channelHolderEntry : this.inputHolders.entrySet()) {
    -			String inputChannelName = channelHolderEntry.getKey();
    -			ChannelHolder channelHolder = channelHolderEntry.getValue();
    -			if (channelHolder.isBindable()) {
    +		for (Map.Entry boundTargetHolderEntry : this.inputHolders.entrySet()) {
    +			String inputTargetName = boundTargetHolderEntry.getKey();
    +			BoundTargetHolder boundTargetHolder = boundTargetHolderEntry.getValue();
    +			if (boundTargetHolder.isBindable()) {
     				if (log.isDebugEnabled()) {
    -					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, inputChannelName));
    +					log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type, inputTargetName));
     				}
    -				channelBindingService.bindConsumer(channelHolder.getMessageChannel(), inputChannelName);
    +				bindingService.bindConsumer(boundTargetHolder.getBoundTarget(), inputTargetName);
     			}
     		}
     	}
     
     	@Override
    -	public void bindOutputs(ChannelBindingService channelBindingService) {
    +	public void bindOutputs(BindingService bindingService) {
     		if (log.isDebugEnabled()) {
    -			log.debug(String.format("Binding outputs for %s:%s", this.channelNamespace, this.type));
    +			log.debug(String.format("Binding outputs for %s:%s", this.namespace, this.type));
     		}
    -		for (Map.Entry channelHolderEntry : this.outputHolders.entrySet()) {
    -			ChannelHolder channelHolder = channelHolderEntry.getValue();
    -			String outputChannelName = channelHolderEntry.getKey();
    -			if (channelHolderEntry.getValue().isBindable()) {
    +		for (Map.Entry boundTargetHolderEntry : this.outputHolders.entrySet()) {
    +			BoundTargetHolder boundTargetHolder = boundTargetHolderEntry.getValue();
    +			String outputTargetName = boundTargetHolderEntry.getKey();
    +			if (boundTargetHolderEntry.getValue().isBindable()) {
     				if (log.isDebugEnabled()) {
    -					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, outputChannelName));
    +					log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type, outputTargetName));
     				}
    -				channelBindingService.bindProducer(channelHolder.getMessageChannel(), outputChannelName);
    +				bindingService.bindProducer(boundTargetHolder.getBoundTarget(), outputTargetName);
     			}
     		}
     	}
     
     	@Override
    -	public void unbindInputs(ChannelBindingService channelBindingService) {
    +	public void unbindInputs(BindingService bindingService) {
     		if (log.isDebugEnabled()) {
    -			log.debug(String.format("Unbinding inputs for %s:%s", this.channelNamespace, this.type));
    +			log.debug(String.format("Unbinding inputs for %s:%s", this.namespace, this.type));
     		}
    -		for (Map.Entry channelHolderEntry : this.inputHolders.entrySet()) {
    -			if (channelHolderEntry.getValue().isBindable()) {
    +		for (Map.Entry boundTargetHolderEntry : this.inputHolders.entrySet()) {
    +			if (boundTargetHolderEntry.getValue().isBindable()) {
     				if (log.isDebugEnabled()) {
    -					log.debug(String.format("Unbinding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
    +					log.debug(String.format("Unbinding %s:%s:%s", this.namespace, this.type,
    +							boundTargetHolderEntry.getKey()));
     				}
    -				channelBindingService.unbindConsumers(channelHolderEntry.getKey());
    +				bindingService.unbindConsumers(boundTargetHolderEntry.getKey());
     			}
     		}
     	}
     
     	@Override
    -	public void unbindOutputs(ChannelBindingService channelBindingService) {
    +	public void unbindOutputs(BindingService bindingService) {
     		if (log.isDebugEnabled()) {
    -			log.debug(String.format("Unbinding outputs for %s:%s", this.channelNamespace, this.type));
    +			log.debug(String.format("Unbinding outputs for %s:%s", this.namespace, this.type));
     		}
    -		for (Map.Entry channelHolderEntry : this.outputHolders.entrySet()) {
    -			if (channelHolderEntry.getValue().isBindable()) {
    +		for (Map.Entry boundTargetHolderEntry : this.outputHolders.entrySet()) {
    +			if (boundTargetHolderEntry.getValue().isBindable()) {
     				if (log.isDebugEnabled()) {
    -					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
    +					log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type,
    +							boundTargetHolderEntry.getKey()));
     				}
    -				channelBindingService.unbindProducers(channelHolderEntry.getKey());
    +				bindingService.unbindProducers(boundTargetHolderEntry.getKey());
     			}
     		}
     	}
    @@ -251,22 +270,22 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean bindingTargetFactory;
     
     	private final DynamicDestinationsBindable dynamicDestinationsBindable;
     
     	private ConfigurableListableBeanFactory beanFactory;
     
     	@SuppressWarnings("unchecked")
    -	public BinderAwareChannelResolver(ChannelBindingService channelBindingService,
    -			BindableChannelFactory bindableChannelFactory, DynamicDestinationsBindable dynamicDestinationsBindable) {
    +	public BinderAwareChannelResolver(BindingService bindingService,
    +			AbstractBindingTargetFactory bindingTargetFactory,
    +			DynamicDestinationsBindable dynamicDestinationsBindable) {
     		this.dynamicDestinationsBindable = dynamicDestinationsBindable;
    -		Assert.notNull(channelBindingService, "'channelBindingService' cannot be null");
    -		Assert.notNull(bindableChannelFactory, "'bindableChannelFactory' cannot be null");
    -		this.channelBindingService = channelBindingService;
    -		this.bindableChannelFactory = bindableChannelFactory;
    +		Assert.notNull(bindingService, "'bindingService' cannot be null");
    +		Assert.notNull(bindingTargetFactory, "'bindingTargetFactory' cannot be null");
    +		this.bindingService = bindingService;
    +		this.bindingTargetFactory = bindingTargetFactory;
     	}
     
     	@Override
    @@ -76,18 +77,18 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
     		synchronized (this) {
     			if (this.beanFactory != null) {
     				String[] dynamicDestinations = null;
    -				ChannelBindingServiceProperties channelBindingServiceProperties =
    -						this.channelBindingService.getChannelBindingServiceProperties();
    -				if (channelBindingServiceProperties != null) {
    -					dynamicDestinations = channelBindingServiceProperties.getDynamicDestinations();
    +				BindingServiceProperties bindingServiceProperties = this.bindingService
    +						.getBindingServiceProperties();
    +				if (bindingServiceProperties != null) {
    +					dynamicDestinations = bindingServiceProperties.getDynamicDestinations();
     				}
     				boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations)
     						|| ObjectUtils.containsElement(dynamicDestinations, channelName);
     				if (dynamicAllowed) {
    -					channel = this.bindableChannelFactory.createOutputChannel(channelName);
    +					channel = this.bindingTargetFactory.createOutput(channelName);
     					this.beanFactory.registerSingleton(channelName, channel);
     					channel = (MessageChannel) this.beanFactory.initializeBean(channel, channelName);
    -					Binding binding = this.channelBindingService.bindProducer(channel, channelName);
    +					Binding binding = this.bindingService.bindProducer(channel, channelName);
     					this.dynamicDestinationsBindable.addOutputBinding(channelName, binding);
     				}
     				else {
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingBeanDefinitionRegistryUtils.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingBeanDefinitionRegistryUtils.java
    index 9d197d7b6..626bc7454 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingBeanDefinitionRegistryUtils.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingBeanDefinitionRegistryUtils.java
    @@ -32,88 +32,79 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
     import org.springframework.util.StringUtils;
     
     /**
    - * Utility class for registering bean definitions for message channels.
    + * Utility class for registering bean definitions for binding targets.
      *
      * @author Marius Bogoevici
      * @author Dave Syer
      */
     public abstract class BindingBeanDefinitionRegistryUtils {
     
    -	public static void registerInputChannelBeanDefinition(String qualifierValue,
    -			String name, String channelInterfaceBeanName,
    -			String channelInterfaceMethodName, BeanDefinitionRegistry registry) {
    -		registerChannelBeanDefinition(Input.class, qualifierValue, name,
    -				channelInterfaceBeanName, channelInterfaceMethodName, registry);
    -	}
    -
    -	public static void registerOutputChannelBeanDefinition(String qualifierValue,
    -			String name, String channelInterfaceBeanName,
    -			String channelInterfaceMethodName, BeanDefinitionRegistry registry) {
    -		registerChannelBeanDefinition(Output.class, qualifierValue, name,
    -				channelInterfaceBeanName, channelInterfaceMethodName, registry);
    -	}
    -
    -	private static void registerChannelBeanDefinition(
    -			Class qualifier, String qualifierValue, String name,
    -			String channelInterfaceBeanName, String channelInterfaceMethodName,
    +	public static void registerInputBindingTargetBeanDefinition(String qualifierValue, String name,
    +			String bindingTargetInterfaceBeanName, String bindingTargetInterfaceMethodName,
     			BeanDefinitionRegistry registry) {
    +		registerBindingTargetBeanDefinition(Input.class, qualifierValue, name, bindingTargetInterfaceBeanName,
    +				bindingTargetInterfaceMethodName, registry);
    +	}
    +
    +	public static void registerOutputBindingTargetBeanDefinition(String qualifierValue, String name,
    +			String bindingTargetInterfaceBeanName, String bindingTargetInterfaceMethodName,
    +			BeanDefinitionRegistry registry) {
    +		registerBindingTargetBeanDefinition(Output.class, qualifierValue, name, bindingTargetInterfaceBeanName,
    +				bindingTargetInterfaceMethodName, registry);
    +	}
    +
    +	private static void registerBindingTargetBeanDefinition(Class qualifier,
    +			String qualifierValue, String name, String bindingTargetInterfaceBeanName,
    +			String bindingTargetInterfaceMethodName, BeanDefinitionRegistry registry) {
     
     		RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
    -		rootBeanDefinition.setFactoryBeanName(channelInterfaceBeanName);
    -		rootBeanDefinition.setUniqueFactoryMethodName(channelInterfaceMethodName);
    -		rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(qualifier,
    -				qualifierValue));
    +		rootBeanDefinition.setFactoryBeanName(bindingTargetInterfaceBeanName);
    +		rootBeanDefinition.setUniqueFactoryMethodName(bindingTargetInterfaceMethodName);
    +		rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(qualifier, qualifierValue));
     		registry.registerBeanDefinition(name, rootBeanDefinition);
     	}
     
    -	public static void registerChannelBeanDefinitions(Class type,
    -			final String channelInterfaceBeanName, final BeanDefinitionRegistry registry) {
    +	public static void registerBindingTargetBeanDefinitions(Class type, final String bindingTargetInterfaceBeanName,
    +			final BeanDefinitionRegistry registry) {
     		ReflectionUtils.doWithMethods(type, new MethodCallback() {
     			@Override
    -			public void doWith(Method method) throws IllegalArgumentException,
    -					IllegalAccessException {
    +			public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
     				Input input = AnnotationUtils.findAnnotation(method, Input.class);
     				if (input != null) {
    -					String name = getChannelName(input, method);
    -					registerInputChannelBeanDefinition(input.value(), name,
    -							channelInterfaceBeanName, method.getName(), registry);
    +					String name = getBindingTargetName(input, method);
    +					registerInputBindingTargetBeanDefinition(input.value(), name, bindingTargetInterfaceBeanName,
    +							method.getName(), registry);
     				}
     				Output output = AnnotationUtils.findAnnotation(method, Output.class);
     				if (output != null) {
    -					String name = getChannelName(output, method);
    -					registerOutputChannelBeanDefinition(output.value(), name,
    -							channelInterfaceBeanName, method.getName(), registry);
    +					String name = getBindingTargetName(output, method);
    +					registerOutputBindingTargetBeanDefinition(output.value(), name, bindingTargetInterfaceBeanName,
    +							method.getName(), registry);
     				}
     			}
     
     		});
     	}
     
    -	public static void registerChannelsQualifiedBeanDefinitions(Class parent,
    -			Class type, final BeanDefinitionRegistry registry) {
    +	public static void registerBindingTargetsQualifiedBeanDefinitions(Class parent, Class type,
    +			final BeanDefinitionRegistry registry) {
     
     		if (type.isInterface()) {
    -			RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(
    -					BindableProxyFactory.class);
    -			rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
    -					Bindings.class, parent));
    -			rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
    -					type);
    +			RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(BindableProxyFactory.class);
    +			rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(Bindings.class, parent));
    +			rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(type);
     			registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
     		}
     		else {
     			RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(type);
    -			rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
    -					Bindings.class, parent));
    +			rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(Bindings.class, parent));
     			registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
     		}
     	}
     
    -	public static String getChannelName(Annotation annotation, Method method) {
    -		Map attrs = AnnotationUtils.getAnnotationAttributes(annotation,
    -				false);
    -		if (attrs.containsKey("value")
    -				&& StringUtils.hasText((CharSequence) attrs.get("value"))) {
    +	public static String getBindingTargetName(Annotation annotation, Method method) {
    +		Map attrs = AnnotationUtils.getAnnotationAttributes(annotation, false);
    +		if (attrs.containsKey("value") && StringUtils.hasText((CharSequence) attrs.get("value"))) {
     			return (String) attrs.get("value");
     		}
     		return method.getName();
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingService.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingService.java
    new file mode 100644
    index 000000000..cecb2a96d
    --- /dev/null
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingService.java
    @@ -0,0 +1,180 @@
    +/*
    + * Copyright 2015-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.cloud.stream.binding;
    +
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.commons.logging.Log;
    +import org.apache.commons.logging.LogFactory;
    +
    +import org.springframework.beans.BeanUtils;
    +import org.springframework.boot.bind.RelaxedDataBinder;
    +import org.springframework.cloud.stream.binder.Binder;
    +import org.springframework.cloud.stream.binder.BinderFactory;
    +import org.springframework.cloud.stream.binder.Binding;
    +import org.springframework.cloud.stream.binder.ConsumerProperties;
    +import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
    +import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
    +import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
    +import org.springframework.cloud.stream.binder.ProducerProperties;
    +import org.springframework.cloud.stream.config.BindingServiceProperties;
    +import org.springframework.util.CollectionUtils;
    +import org.springframework.util.StringUtils;
    +import org.springframework.validation.beanvalidation.CustomValidatorBean;
    +
    +/**
    + * Handles binding of input/output targets by delegating to an underlying
    + * {@link Binder}.
    + *
    + * @author Mark Fisher
    + * @author Dave Syer
    + * @author Marius Bogoevici
    + * @author Ilayaperumal Gopinathan
    + * @author Gary Russell
    + */
    +public class BindingService {
    +
    +	private final CustomValidatorBean validator;
    +
    +	private final Log log = LogFactory.getLog(BindingService.class);
    +
    +	private BinderFactory binderFactory;
    +
    +	private final BindingServiceProperties bindingServiceProperties;
    +
    +	private final Map> producerBindings = new HashMap<>();
    +
    +	private final Map>> consumerBindings = new HashMap<>();
    +
    +	public BindingService(
    +			BindingServiceProperties bindingServiceProperties,
    +			BinderFactory binderFactory) {
    +		this.bindingServiceProperties = bindingServiceProperties;
    +		this.binderFactory = binderFactory;
    +		this.validator = new CustomValidatorBean();
    +		this.validator.afterPropertiesSet();
    +	}
    +
    +	@SuppressWarnings("unchecked")
    +	public  Collection> bindConsumer(T input, String inputName) {
    +		String bindingTarget = this.bindingServiceProperties
    +				.getBindingDestination(inputName);
    +		String[] bindingTargets = StringUtils
    +				.commaDelimitedListToStringArray(bindingTarget);
    +		Collection> bindings = new ArrayList<>();
    +		Binder binder = (Binder) getBinder(
    +				inputName, input.getClass());
    +		ConsumerProperties consumerProperties = this.bindingServiceProperties
    +				.getConsumerProperties(inputName);
    +		if (binder instanceof ExtendedPropertiesBinder) {
    +			Object extension = ((ExtendedPropertiesBinder) binder)
    +					.getExtendedConsumerProperties(inputName);
    +			ExtendedConsumerProperties extendedConsumerProperties = new ExtendedConsumerProperties(
    +					extension);
    +			BeanUtils.copyProperties(consumerProperties, extendedConsumerProperties);
    +			consumerProperties = extendedConsumerProperties;
    +		}
    +		validate(consumerProperties);
    +		for (String target : bindingTargets) {
    +			Binding binding = binder.bindConsumer(target,
    +					bindingServiceProperties.getGroup(inputName), input,
    +					consumerProperties);
    +			bindings.add(binding);
    +		}
    +		bindings = Collections.unmodifiableCollection(bindings);
    +		this.consumerBindings.put(inputName, new ArrayList>(bindings));
    +		return bindings;
    +	}
    +
    +	@SuppressWarnings("unchecked")
    +	public  Binding bindProducer(T output, String outputName) {
    +		String bindingTarget = this.bindingServiceProperties
    +				.getBindingDestination(outputName);
    +		Binder binder = (Binder) getBinder(
    +				outputName, output.getClass());
    +		ProducerProperties producerProperties = this.bindingServiceProperties
    +				.getProducerProperties(outputName);
    +		if (binder instanceof ExtendedPropertiesBinder) {
    +			Object extension = ((ExtendedPropertiesBinder) binder)
    +					.getExtendedProducerProperties(outputName);
    +			ExtendedProducerProperties extendedProducerProperties = new ExtendedProducerProperties<>(
    +					extension);
    +			BeanUtils.copyProperties(producerProperties, extendedProducerProperties);
    +			producerProperties = extendedProducerProperties;
    +		}
    +		validate(producerProperties);
    +		Binding binding = binder.bindProducer(bindingTarget, output,
    +				producerProperties);
    +		this.producerBindings.put(outputName, binding);
    +		return binding;
    +	}
    +
    +	public void unbindConsumers(String inputName) {
    +		List> bindings = this.consumerBindings.remove(inputName);
    +		if (bindings != null && !CollectionUtils.isEmpty(bindings)) {
    +			for (Binding binding : bindings) {
    +				binding.unbind();
    +			}
    +		}
    +		else if (log.isWarnEnabled()) {
    +			log.warn("Trying to unbind '" + inputName + "', but no binding found.");
    +		}
    +	}
    +
    +	public void unbindProducers(String outputName) {
    +		Binding binding = this.producerBindings.remove(outputName);
    +		if (binding != null) {
    +			binding.unbind();
    +		}
    +		else if (log.isWarnEnabled()) {
    +			log.warn("Trying to unbind '" + outputName + "', but no binding found.");
    +		}
    +	}
    +
    +	@SuppressWarnings("unchecked")
    +	private  Binder getBinder(String channelName, Class bindableType) {
    +		String transport = this.bindingServiceProperties.getBinder(channelName);
    +		return binderFactory.getBinder(transport, bindableType);
    +	}
    +
    +	/**
    +	 * Provided for backwards compatibility. Will be removed in a future version.
    +	 * @return
    +	 */
    +	@Deprecated
    +	public BindingServiceProperties getChannelBindingServiceProperties() {
    +		return this.bindingServiceProperties;
    +	}
    +
    +	public BindingServiceProperties getBindingServiceProperties() {
    +		return this.bindingServiceProperties;
    +	}
    +
    +	private void validate(Object properties) {
    +		RelaxedDataBinder dataBinder = new RelaxedDataBinder(properties);
    +		dataBinder.setValidator(validator);
    +		dataBinder.validate();
    +		if (dataBinder.getBindingResult().hasErrors()) {
    +			throw new IllegalStateException(dataBinder.getBindingResult().toString());
    +		}
    +	}
    +}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingTargetFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingTargetFactory.java
    new file mode 100644
    index 000000000..492c776fa
    --- /dev/null
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingTargetFactory.java
    @@ -0,0 +1,51 @@
    +/*
    + * Copyright 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.cloud.stream.binding;
    +
    +/**
    + * Defines methods to create/configure the binding targets defined by
    + * {@link org.springframework.cloud.stream.annotation.EnableBinding}.
    + *
    + * @author Marius Bogoevici
    + * @author Ilayaperumal Gopinathan
    + */
    +public interface BindingTargetFactory {
    +
    +	/**
    +	 * Checks whether a specific binding target type can be created by this factory.
    +	 * @param clazz the binding target type
    +	 * @return true if the binding target can be created
    +	 */
    +	boolean canCreate(Class clazz);
    +
    +	/**
    +	 * Create an input binding target that will be bound via a corresponding
    +	 * {@link org.springframework.cloud.stream.binder.Binder}.
    +	 * @param name name of the binding target
    +	 * @return binding target
    +	 */
    +	Object createInput(String name);
    +
    +	/**
    +	 * Create an output binding target that will be bound via a corresponding
    +	 * {@link org.springframework.cloud.stream.binder.Binder}.
    +	 * @param name name of the binding target
    +	 * @return binding target
    +	 */
    +	Object createOutput(String name);
    +
    +}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java
    deleted file mode 100644
    index a9f5fcd3f..000000000
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java
    +++ /dev/null
    @@ -1,157 +0,0 @@
    -/*
    - * Copyright 2015-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.cloud.stream.binding;
    -
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -
    -import org.apache.commons.logging.Log;
    -import org.apache.commons.logging.LogFactory;
    -
    -import org.springframework.beans.BeanUtils;
    -import org.springframework.boot.bind.RelaxedDataBinder;
    -import org.springframework.cloud.stream.binder.Binder;
    -import org.springframework.cloud.stream.binder.BinderFactory;
    -import org.springframework.cloud.stream.binder.Binding;
    -import org.springframework.cloud.stream.binder.ConsumerProperties;
    -import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
    -import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
    -import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
    -import org.springframework.cloud.stream.binder.ProducerProperties;
    -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
    -import org.springframework.messaging.MessageChannel;
    -import org.springframework.util.CollectionUtils;
    -import org.springframework.util.StringUtils;
    -import org.springframework.validation.beanvalidation.CustomValidatorBean;
    -
    -/**
    - * Handles the operations related to channel binding including binding of input/output channels by delegating
    - * to an underlying {@link Binder}, setting up data type conversion for binding channel.
    - * @author Mark Fisher
    - * @author Dave Syer
    - * @author Marius Bogoevici
    - * @author Ilayaperumal Gopinathan
    - * @author Gary Russell
    - */
    -public class ChannelBindingService {
    -
    -	private final CustomValidatorBean validator;
    -
    -	private final Log log = LogFactory.getLog(ChannelBindingService.class);
    -
    -	private BinderFactory binderFactory;
    -
    -	private final ChannelBindingServiceProperties channelBindingServiceProperties;
    -
    -	private final Map> producerBindings = new HashMap<>();
    -
    -	private final Map>> consumerBindings = new HashMap<>();
    -
    -	public ChannelBindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
    -			BinderFactory binderFactory) {
    -		this.channelBindingServiceProperties = channelBindingServiceProperties;
    -		this.binderFactory = binderFactory;
    -		this.validator = new CustomValidatorBean();
    -		this.validator.afterPropertiesSet();
    -	}
    -
    -
    -	@SuppressWarnings("unchecked")
    -	public Collection> bindConsumer(MessageChannel inputChannel, String inputChannelName) {
    -		String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(inputChannelName);
    -		String[] channelBindingTargets = StringUtils.commaDelimitedListToStringArray(channelBindingTarget);
    -		List> bindings = new ArrayList<>();
    -		Binder binder =
    -				(Binder) getBinderForChannel(inputChannelName);
    -		ConsumerProperties consumerProperties =
    -				this.channelBindingServiceProperties.getConsumerProperties(inputChannelName);
    -		if (binder instanceof ExtendedPropertiesBinder) {
    -			Object extension = ((ExtendedPropertiesBinder) binder).getExtendedConsumerProperties(inputChannelName);
    -			ExtendedConsumerProperties extendedConsumerProperties = new ExtendedConsumerProperties(extension);
    -			BeanUtils.copyProperties(consumerProperties, extendedConsumerProperties);
    -			consumerProperties = extendedConsumerProperties;
    -		}
    -		validate(consumerProperties);
    -		for (String target : channelBindingTargets) {
    -			Binding binding = binder.bindConsumer(target, channelBindingServiceProperties.getGroup(inputChannelName), inputChannel, consumerProperties);
    -			bindings.add(binding);
    -		}
    -		this.consumerBindings.put(inputChannelName, bindings);
    -		return bindings;
    -	}
    -
    -	@SuppressWarnings("unchecked")
    -	public Binding bindProducer(MessageChannel outputChannel, String outputChannelName) {
    -		String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(outputChannelName);
    -		Binder binder =
    -				(Binder) getBinderForChannel(outputChannelName);
    -		ProducerProperties producerProperties = this.channelBindingServiceProperties.getProducerProperties(outputChannelName);
    -		if (binder instanceof ExtendedPropertiesBinder) {
    -			Object extension = ((ExtendedPropertiesBinder) binder).getExtendedProducerProperties(outputChannelName);
    -			ExtendedProducerProperties extendedProducerProperties = new ExtendedProducerProperties<>(extension);
    -			BeanUtils.copyProperties(producerProperties, extendedProducerProperties);
    -			producerProperties = extendedProducerProperties;
    -		}
    -		validate(producerProperties);
    -		Binding binding = binder.bindProducer(channelBindingTarget, outputChannel, producerProperties);
    -		this.producerBindings.put(outputChannelName, binding);
    -		return binding;
    -	}
    -
    -	public void unbindConsumers(String inputChannelName) {
    -		List> bindings = this.consumerBindings.remove(inputChannelName);
    -		if (bindings != null && !CollectionUtils.isEmpty(bindings)) {
    -			for (Binding binding : bindings) {
    -				binding.unbind();
    -			}
    -		}
    -		else if (log.isWarnEnabled()) {
    -			log.warn("Trying to unbind channel '" + inputChannelName + "', but no binding found.");
    -		}
    -	}
    -
    -	public void unbindProducers(String outputChannelName) {
    -		Binding binding = this.producerBindings.remove(outputChannelName);
    -		if (binding != null) {
    -			binding.unbind();
    -		}
    -		else if (log.isWarnEnabled()) {
    -			log.warn("Trying to unbind channel '" + outputChannelName + "', but no binding found.");
    -		}
    -	}
    -
    -	private Binder getBinderForChannel(String channelName) {
    -		String transport = this.channelBindingServiceProperties.getBinder(channelName);
    -		return binderFactory.getBinder(transport);
    -	}
    -
    -	public ChannelBindingServiceProperties getChannelBindingServiceProperties() {
    -		return this.channelBindingServiceProperties;
    -	}
    -
    -	private void validate(Object properties) {
    -		RelaxedDataBinder dataBinder = new RelaxedDataBinder(properties);
    -		dataBinder.setValidator(validator);
    -		dataBinder.validate();
    -		if (dataBinder.getBindingResult().hasErrors()) {
    -			throw new IllegalStateException(dataBinder.getBindingResult().toString());
    -		}
    -	}
    -}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DynamicDestinationsBindable.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DynamicDestinationsBindable.java
    index 4498055c4..f6fc05ab9 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DynamicDestinationsBindable.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DynamicDestinationsBindable.java
    @@ -47,7 +47,7 @@ public final class DynamicDestinationsBindable extends BindableAdapter {
     	}
     
     	@Override
    -	public void unbindOutputs(ChannelBindingService adapter) {
    +	public void unbindOutputs(BindingService adapter) {
     		for (Map.Entry entry: outputBindings.entrySet()) {
     			entry.getValue().unbind();
     		}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/InputBindingLifecycle.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/InputBindingLifecycle.java
    index 83ac2e900..de3821eb3 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/InputBindingLifecycle.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/InputBindingLifecycle.java
    @@ -25,7 +25,7 @@ import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.context.SmartLifecycle;
     
     /**
    - * Coordinates binding/unbinding of input channels in accordance to the lifecycle
    + * Coordinates binding/unbinding of input binding targets in accordance to the lifecycle
      * of the host context.
      * @author Marius Bogoevici
      * @author Ilayaperumal Gopinathan
    @@ -45,14 +45,14 @@ public class InputBindingLifecycle implements SmartLifecycle, ApplicationContext
     	@Override
     	public void start() {
     		if (!running) {
    -			// retrieve the ChannelBindingService lazily, avoiding early initialization
    +			// retrieve the BindingService lazily, avoiding early initialization
     			try {
    -				ChannelBindingService channelBindingService = this.applicationContext
    -						.getBean(ChannelBindingService.class);
    +				BindingService bindingService = this.applicationContext
    +						.getBean(BindingService.class);
     				Map bindables = this.applicationContext
     						.getBeansOfType(Bindable.class);
     				for (Bindable bindable : bindables.values()) {
    -					bindable.bindInputs(channelBindingService);
    +					bindable.bindInputs(bindingService);
     				}
     			}
     			catch (BeansException e) {
    @@ -67,14 +67,14 @@ public class InputBindingLifecycle implements SmartLifecycle, ApplicationContext
     	public void stop() {
     		if (running) {
     			try {
    -				// retrieve the ChannelBindingService lazily, avoiding early
    +				// retrieve the BindingService lazily, avoiding early
     				// initialization
    -				ChannelBindingService channelBindingService = this.applicationContext
    -						.getBean(ChannelBindingService.class);
    +				BindingService bindingService = this.applicationContext
    +						.getBean(BindingService.class);
     				Map bindables = this.applicationContext
     						.getBeansOfType(Bindable.class);
     				for (Bindable bindable : bindables.values()) {
    -					bindable.unbindInputs(channelBindingService);
    +					bindable.unbindInputs(bindingService);
     				}
     			}
     			catch (BeansException e) {
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java
    index f085b4cc1..337c3d007 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java
    @@ -29,15 +29,15 @@ public class MessageChannelStreamListenerResultAdapter
     		implements StreamListenerResultAdapter {
     
     	@Override
    -	public boolean supports(Class resultType, Class boundElement) {
    +	public boolean supports(Class resultType, Class bindingTarget) {
     		return MessageChannel.class.isAssignableFrom(resultType)
    -				&& MessageChannel.class.isAssignableFrom(boundElement);
    +				&& MessageChannel.class.isAssignableFrom(bindingTarget);
     	}
     
     	@Override
    -	public void adapt(MessageChannel streamListenerResult, MessageChannel boundElement) {
    +	public void adapt(MessageChannel streamListenerResult, MessageChannel bindingTarget) {
     		BridgeHandler handler = new BridgeHandler();
    -		handler.setOutputChannel(boundElement);
    +		handler.setOutputChannel(bindingTarget);
     		handler.afterPropertiesSet();
     		((SubscribableChannel) streamListenerResult).subscribe(handler);
     	}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java
    index 1faeeae71..d3c3864d9 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java
    @@ -25,7 +25,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
     import org.springframework.cloud.stream.binder.BinderHeaders;
     import org.springframework.cloud.stream.binder.PartitionHandler;
     import org.springframework.cloud.stream.config.BindingProperties;
    -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
    +import org.springframework.cloud.stream.config.BindingServiceProperties;
     import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
     import org.springframework.cloud.stream.converter.MessageConverterUtils;
     import org.springframework.expression.EvaluationContext;
    @@ -63,12 +63,12 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
     
     	private final CompositeMessageConverterFactory compositeMessageConverterFactory;
     
    -	private final ChannelBindingServiceProperties channelBindingServiceProperties;
    +	private final BindingServiceProperties bindingServiceProperties;
     
    -	public MessageConverterConfigurer(ChannelBindingServiceProperties channelBindingServiceProperties,
    +	public MessageConverterConfigurer(BindingServiceProperties bindingServiceProperties,
     			CompositeMessageConverterFactory compositeMessageConverterFactory) {
     		Assert.notNull(compositeMessageConverterFactory, "The message converter factory cannot be null");
    -		this.channelBindingServiceProperties = channelBindingServiceProperties;
    +		this.bindingServiceProperties = bindingServiceProperties;
     		this.compositeMessageConverterFactory = compositeMessageConverterFactory;
     	}
     
    @@ -100,7 +100,7 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
     	private void configureMessageChannel(MessageChannel channel, String channelName, boolean input) {
     		Assert.isAssignable(AbstractMessageChannel.class, channel.getClass());
     		AbstractMessageChannel messageChannel = (AbstractMessageChannel) channel;
    -		final BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindingProperties(
    +		final BindingProperties bindingProperties = this.bindingServiceProperties.getBindingProperties(
     				channelName);
     		final String contentType = bindingProperties.getContentType();
     		if (!input && bindingProperties.getProducer() != null && bindingProperties.getProducer().isPartitioned()) {
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/OutputBindingLifecycle.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/OutputBindingLifecycle.java
    index 20955c596..4f897ba97 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/OutputBindingLifecycle.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/OutputBindingLifecycle.java
    @@ -25,7 +25,7 @@ import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.context.SmartLifecycle;
     
     /**
    - * Coordinates binding/unbinding of output channels in accordance to the lifecycle
    + * Coordinates binding/unbinding of output binding targets in accordance to the lifecycle
      * of the host context.
      *
      * @author Marius Bogoevici
    @@ -47,14 +47,14 @@ public class OutputBindingLifecycle implements SmartLifecycle, ApplicationContex
     	public void start() {
     		if (!running) {
     
    -			// retrieve the ChannelBindingService lazily, avoiding early initialization
    +			// retrieve the BindingService lazily, avoiding early initialization
     			try {
    -				ChannelBindingService channelBindingService = this.applicationContext
    -						.getBean(ChannelBindingService.class);
    +				BindingService bindingService = this.applicationContext
    +						.getBean(BindingService.class);
     				Map bindables = this.applicationContext
     						.getBeansOfType(Bindable.class);
     				for (Bindable bindable : bindables.values()) {
    -					bindable.bindOutputs(channelBindingService);
    +					bindable.bindOutputs(bindingService);
     				}
     			}
     			catch (BeansException e) {
    @@ -69,14 +69,14 @@ public class OutputBindingLifecycle implements SmartLifecycle, ApplicationContex
     	public void stop() {
     		if (running) {
     			try {
    -				// retrieve the ChannelBindingService lazily, avoiding early
    +				// retrieve the BindingService lazily, avoiding early
     				// initialization
    -				ChannelBindingService channelBindingService = this.applicationContext
    -						.getBean(ChannelBindingService.class);
    +				BindingService bindingService = this.applicationContext
    +						.getBean(BindingService.class);
     				Map bindables = this.applicationContext
     						.getBeansOfType(Bindable.class);
     				for (Bindable bindable : bindables.values()) {
    -					bindable.unbindOutputs(channelBindingService);
    +					bindable.unbindOutputs(bindingService);
     				}
     			}
     			catch (BeansException e) {
    @@ -106,7 +106,8 @@ public class OutputBindingLifecycle implements SmartLifecycle, ApplicationContex
     	}
     
     	/**
    -	 * Return a low value so that this bean is started after receiving Lifecycle beans are started. Beans that need to start before bindings will set a lower phase value.
    +	 * Return a low value so that this bean is started after receiving Lifecycle beans are
    +	 * started. Beans that need to start before bindings will set a lower phase value.
     	 */
     	@Override
     	public int getPhase() {
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SingleChannelBindable.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SingleBindingTargetBindable.java
    similarity index 54%
    rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SingleChannelBindable.java
    rename to spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SingleBindingTargetBindable.java
    index ae3d10c40..aa1fbc757 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SingleChannelBindable.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SingleBindingTargetBindable.java
    @@ -21,38 +21,37 @@ import java.util.Collections;
     import java.util.HashSet;
     import java.util.Set;
     
    -import org.springframework.messaging.MessageChannel;
    -
     /**
    - * A {@link Bindable} component that wraps a generic channel. Useful for binding channels outside the
    - * {@link org.springframework.cloud.stream.annotation.Input} and {@link org.springframework.cloud.stream.annotation.Output}
    - * annotated interfaces.
    + * A {@link Bindable} component that wraps a generic output binding target. Useful for
    + * binding targets outside the {@link org.springframework.cloud.stream.annotation.Input}
    + * and {@link org.springframework.cloud.stream.annotation.Output} annotated interfaces.
      *
      * @author Ilayaperumal Gopinathan
    + * @author Marius Bogoevici
      */
    -public class SingleChannelBindable extends BindableAdapter {
    +public class SingleBindingTargetBindable extends BindableAdapter {
     
     	private final String name;
     
    -	private final MessageChannel messageChannel;
    +	private final T bindingTarget;
     
    -	public SingleChannelBindable(String name, MessageChannel messageChannel) {
    +	public SingleBindingTargetBindable(String name, T bindingTarget) {
     		this.name = name;
    -		this.messageChannel = messageChannel;
    +		this.bindingTarget = bindingTarget;
     	}
     
     	@Override
    -	public void bindOutputs(ChannelBindingService adapter) {
    -		adapter.bindProducer(messageChannel, name);
    +	public void bindOutputs(BindingService bindingService) {
    +		bindingService.bindProducer(bindingTarget, name);
     	}
     
     	@Override
    -	public void unbindOutputs(ChannelBindingService adapter) {
    -		adapter.unbindProducers(name);
    +	public void unbindOutputs(BindingService bindingService) {
    +		bindingService.unbindProducers(name);
     	}
     
     	@Override
     	public Set getOutputs() {
    -		return Collections.unmodifiableSet(new HashSet(Arrays.asList(name)));
    +		return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(name)));
     	}
     }
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java
    index 94db41417..c405b5c49 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java
    @@ -79,16 +79,16 @@ public class StreamListenerAnnotationBeanPostProcessor
     	}
     
     	@Override
    -	@SuppressWarnings({"rawtypes", "unchecked"})
    +	@SuppressWarnings({ "rawtypes", "unchecked" })
     	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
     		this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    -		Map parameterAdapterMap =
    -				this.applicationContext.getBeansOfType(StreamListenerParameterAdapter.class);
    +		Map parameterAdapterMap = this.applicationContext
    +				.getBeansOfType(StreamListenerParameterAdapter.class);
     		for (StreamListenerParameterAdapter parameterAdapter : parameterAdapterMap.values()) {
     			this.streamListenerParameterAdapters.add(parameterAdapter);
     		}
    -		Map resultAdapterMap =
    -				this.applicationContext.getBeansOfType(StreamListenerResultAdapter.class);
    +		Map resultAdapterMap = this.applicationContext
    +				.getBeansOfType(StreamListenerResultAdapter.class);
     		this.streamListenerResultAdapters.add(new MessageChannelStreamListenerResultAdapter());
     		for (StreamListenerResultAdapter resultAdapter : resultAdapterMap.values()) {
     			this.streamListenerResultAdapters.add(resultAdapter);
    @@ -108,24 +108,30 @@ public class StreamListenerAnnotationBeanPostProcessor
     			public void doWith(final Method method) throws IllegalArgumentException, IllegalAccessException {
     				StreamListener streamListener = AnnotationUtils.findAnnotation(method, StreamListener.class);
     				if (streamListener != null) {
    -					Assert.isTrue(method.getAnnotation(Input.class) == null, StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER);
    +					Assert.isTrue(method.getAnnotation(Input.class) == null,
    +							StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER);
     					String methodAnnotatedInboundName = streamListener.value();
    -					String methodAnnotatedOutboundName = StreamListenerMethodUtils.getOutboundElementNameFromMethod(method);
    +					String methodAnnotatedOutboundName = StreamListenerMethodUtils.getOutboundBindingTargetName(method);
     					int inputAnnotationCount = StreamListenerMethodUtils.inputAnnotationCount(method);
     					int outputAnnotationCount = StreamListenerMethodUtils.outputAnnotationCount(method);
    -					boolean isDeclarative = checkDeclarativeMethod(method, methodAnnotatedInboundName, methodAnnotatedOutboundName);
    -					StreamListenerMethodUtils.validateStreamListenerMethod(method, inputAnnotationCount, outputAnnotationCount,
    -							methodAnnotatedInboundName, methodAnnotatedOutboundName, isDeclarative);
    +					boolean isDeclarative = checkDeclarativeMethod(method, methodAnnotatedInboundName,
    +							methodAnnotatedOutboundName);
    +					StreamListenerMethodUtils.validateStreamListenerMethod(method, inputAnnotationCount,
    +							outputAnnotationCount, methodAnnotatedInboundName, methodAnnotatedOutboundName,
    +							isDeclarative);
     					if (!method.getReturnType().equals(Void.TYPE)) {
     						if (!StringUtils.hasText(methodAnnotatedOutboundName)) {
     							if (outputAnnotationCount == 0) {
    -								throw new IllegalArgumentException(StreamListenerErrorMessages.RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
    +								throw new IllegalArgumentException(
    +										StreamListenerErrorMessages.RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
     							}
    -							Assert.isTrue((outputAnnotationCount == 1), StreamListenerErrorMessages.RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
    +							Assert.isTrue((outputAnnotationCount == 1),
    +									StreamListenerErrorMessages.RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
     						}
     					}
     					if (isDeclarative) {
    -						invokeSetupMethodOnListenedChannel(method, bean, methodAnnotatedInboundName, methodAnnotatedOutboundName);
    +						invokeSetupMethodOnListenedChannel(method, bean, methodAnnotatedInboundName,
    +								methodAnnotatedOutboundName);
     					}
     					else {
     						registerHandlerMethodOnListenedChannel(method, streamListener, bean);
    @@ -136,34 +142,36 @@ public class StreamListenerAnnotationBeanPostProcessor
     		return bean;
     	}
     
    -	private boolean checkDeclarativeMethod(Method method, String methodAnnotatedInboundName, String methodAnnotatedOutboundName) {
    +	private boolean checkDeclarativeMethod(Method method, String methodAnnotatedInboundName,
    +			String methodAnnotatedOutboundName) {
     		int methodArgumentsLength = method.getParameterTypes().length;
     		for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
    -			MethodParameter methodParameter = MethodParameter
    -					.forMethodOrConstructor(method, parameterIndex);
    +			MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
     			if (methodParameter.hasParameterAnnotation(Input.class)) {
     				String inboundName = (String) AnnotationUtils
     						.getValue(methodParameter.getParameterAnnotation(Input.class));
     				Assert.isTrue(StringUtils.hasText(inboundName), StreamListenerErrorMessages.INVALID_INBOUND_NAME);
    -				Assert.isTrue(isDeclarativeMethodParameter(this.applicationContext.getBean(inboundName),
    -						methodParameter), StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
    +				Assert.isTrue(
    +						isDeclarativeMethodParameter(this.applicationContext.getBean(inboundName), methodParameter),
    +						StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
     				return true;
     			}
     			if (methodParameter.hasParameterAnnotation(Output.class)) {
     				String outboundName = (String) AnnotationUtils
     						.getValue(methodParameter.getParameterAnnotation(Output.class));
     				Assert.isTrue(StringUtils.hasText(outboundName), StreamListenerErrorMessages.INVALID_OUTBOUND_NAME);
    -				Assert.isTrue(isDeclarativeMethodParameter(this.applicationContext.getBean(outboundName),
    -						methodParameter), StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
    +				Assert.isTrue(
    +						isDeclarativeMethodParameter(this.applicationContext.getBean(outboundName), methodParameter),
    +						StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
     				return true;
     			}
     			if (StringUtils.hasText(methodAnnotatedOutboundName)) {
    -				return isDeclarativeMethodParameter(
    -						this.applicationContext.getBean(methodAnnotatedOutboundName), methodParameter);
    +				return isDeclarativeMethodParameter(this.applicationContext.getBean(methodAnnotatedOutboundName),
    +						methodParameter);
     			}
     			if (StringUtils.hasText(methodAnnotatedInboundName)) {
    -				return isDeclarativeMethodParameter(
    -						this.applicationContext.getBean(methodAnnotatedInboundName), methodParameter);
    +				return isDeclarativeMethodParameter(this.applicationContext.getBean(methodAnnotatedInboundName),
    +						methodParameter);
     			}
     		}
     		return false;
    @@ -183,8 +191,9 @@ public class StreamListenerAnnotationBeanPostProcessor
     		return false;
     	}
     
    -	@SuppressWarnings({"rawtypes", "unchecked"})
    -	private void invokeSetupMethodOnListenedChannel(Method method, Object bean, String inboundName, String outboundName) {
    +	@SuppressWarnings({ "rawtypes", "unchecked" })
    +	private void invokeSetupMethodOnListenedChannel(Method method, Object bean, String inboundName,
    +			String outboundName) {
     		Object[] arguments = new Object[method.getParameterTypes().length];
     		for (int parameterIndex = 0; parameterIndex < arguments.length; parameterIndex++) {
     			MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
    @@ -206,17 +215,16 @@ public class StreamListenerAnnotationBeanPostProcessor
     					arguments[parameterIndex] = targetBean;
     				}
     				else {
    -					for (StreamListenerParameterAdapter streamListenerParameterAdapter :
    -							this.streamListenerParameterAdapters) {
    +					for (StreamListenerParameterAdapter streamListenerParameterAdapter : this.streamListenerParameterAdapters) {
     						if (streamListenerParameterAdapter.supports(targetBean.getClass(), methodParameter)) {
    -							arguments[parameterIndex] = streamListenerParameterAdapter.adapt(targetBean, methodParameter);
    +							arguments[parameterIndex] = streamListenerParameterAdapter.adapt(targetBean,
    +									methodParameter);
     							break;
     						}
     					}
     				}
    -				Assert.notNull(arguments[parameterIndex],
    -						"Cannot convert argument " + parameterIndex + " of " + method + "from " + targetBean.getClass()
    -								+ " to " + parameterType);
    +				Assert.notNull(arguments[parameterIndex], "Cannot convert argument " + parameterIndex + " of " + method
    +						+ "from " + targetBean.getClass() + " to " + parameterType);
     			}
     			else {
     				throw new IllegalStateException(StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
    @@ -230,15 +238,15 @@ public class StreamListenerAnnotationBeanPostProcessor
     				Object result = method.invoke(bean, arguments);
     				if (!StringUtils.hasText(outboundName)) {
     					for (int parameterIndex = 0; parameterIndex < method.getParameterTypes().length; parameterIndex++) {
    -						MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
    +						MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method,
    +								parameterIndex);
     						if (methodParameter.hasParameterAnnotation(Output.class)) {
     							outboundName = methodParameter.getParameterAnnotation(Output.class).value();
     						}
     					}
     				}
     				Object targetBean = this.applicationContext.getBean(outboundName);
    -				for (StreamListenerResultAdapter streamListenerResultAdapter : this
    -						.streamListenerResultAdapters) {
    +				for (StreamListenerResultAdapter streamListenerResultAdapter : this.streamListenerResultAdapters) {
     					if (streamListenerResultAdapter.supports(result.getClass(), targetBean.getClass())) {
     						streamListenerResultAdapter.adapt(result, targetBean);
     						break;
    @@ -254,31 +262,29 @@ public class StreamListenerAnnotationBeanPostProcessor
     	protected void registerHandlerMethodOnListenedChannel(Method method, StreamListener streamListener, Object bean) {
     		Method targetMethod = checkProxy(method, bean);
     		Assert.hasText(streamListener.value(), "The binding name cannot be null");
    -		final InvocableHandlerMethod invocableHandlerMethod =
    -				this.messageHandlerMethodFactory.createInvocableHandlerMethod(bean, targetMethod);
    +		final InvocableHandlerMethod invocableHandlerMethod = this.messageHandlerMethodFactory
    +				.createInvocableHandlerMethod(bean, targetMethod);
     		if (!StringUtils.hasText(streamListener.value())) {
     			throw new BeanInitializationException("A bound component name must be specified");
     		}
     		if (this.mappedBindings.containsKey(streamListener.value())) {
    -			throw new BeanInitializationException("Duplicate @" + StreamListener.class.getSimpleName() +
    -					" mapping for '" + streamListener.value() + "' on " + invocableHandlerMethod.getShortLogMessage() +
    -					" already existing for " + this.mappedBindings.get(streamListener.value()).getShortLogMessage());
    +			throw new BeanInitializationException("Duplicate @" + StreamListener.class.getSimpleName()
    +					+ " mapping for '" + streamListener.value() + "' on " + invocableHandlerMethod.getShortLogMessage()
    +					+ " already existing for " + this.mappedBindings.get(streamListener.value()).getShortLogMessage());
     		}
     		this.mappedBindings.put(streamListener.value(), invocableHandlerMethod);
     		SubscribableChannel channel = this.applicationContext.getBean(streamListener.value(),
     				SubscribableChannel.class);
    -		final String defaultOutputChannel = StreamListenerMethodUtils.getOutboundElementNameFromMethod(method);
    +		final String defaultOutputChannel = StreamListenerMethodUtils.getOutboundBindingTargetName(method);
     		if (invocableHandlerMethod.isVoid()) {
     			Assert.isTrue(StringUtils.isEmpty(defaultOutputChannel),
    -					"An output channel cannot be specified for a method that " +
    -							"does not return a value");
    +					"An output channel cannot be specified for a method that does not return a value");
     		}
     		else {
     			Assert.isTrue(!StringUtils.isEmpty(defaultOutputChannel),
    -					"An output channel must be specified for a method that " +
    -							"can return a value");
    +					"An output channel must be specified for a method that can return a value");
     		}
    -		StreamListenerMethodUtils.assertStreamListenerMessageHandlerMethod(method);
    +		StreamListenerMethodUtils.validateStreamListenerMessageHandler(method);
     		StreamListenerMessageHandler handler = new StreamListenerMessageHandler(invocableHandlerMethod);
     		handler.setApplicationContext(this.applicationContext);
     		handler.setChannelResolver(this.binderAwareChannelResolver);
    @@ -291,7 +297,8 @@ public class StreamListenerAnnotationBeanPostProcessor
     
     	@Override
     	public void afterSingletonsInstantiated() {
    -		// Dump the mappings after the context has been created, ensuring that beans can be processed correctly
    +		// Dump the mappings after the context has been created, ensuring that beans can
    +		// be processed correctly
     		// again.
     		this.mappedBindings.clear();
     	}
    @@ -300,7 +307,8 @@ public class StreamListenerAnnotationBeanPostProcessor
     		Method method = methodArg;
     		if (AopUtils.isJdkDynamicProxy(bean)) {
     			try {
    -				// Found a @StreamListener method on the target class for this JDK proxy ->
    +				// Found a @StreamListener method on the target class for this JDK proxy
    +				// ->
     				// is it also present on the proxy itself?
     				method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
     				Class[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces();
    @@ -318,11 +326,11 @@ public class StreamListenerAnnotationBeanPostProcessor
     			}
     			catch (NoSuchMethodException ex) {
     				throw new IllegalStateException(String.format(
    -						"@StreamListener method '%s' found on bean target class '%s', " +
    -								"but not found in any interface(s) for bean JDK proxy. Either " +
    -								"pull the method up to an interface or switch to subclass (CGLIB) " +
    -								"proxies by setting proxy-target-class/proxyTargetClass " +
    -								"attribute to 'true'", method.getName(), method.getDeclaringClass().getSimpleName()), ex);
    +						"@StreamListener method '%s' found on bean target class '%s', "
    +								+ "but not found in any interface(s) for bean JDK proxy. Either "
    +								+ "pull the method up to an interface or switch to subclass (CGLIB) "
    +								+ "proxies by setting proxy-target-class/proxyTargetClass attribute to 'true'",
    +						method.getName(), method.getDeclaringClass().getSimpleName()), ex);
     			}
     		}
     		return method;
    @@ -351,9 +359,8 @@ public class StreamListenerAnnotationBeanPostProcessor
     					throw (MessagingException) e;
     				}
     				else {
    -					throw new MessagingException(requestMessage, "Exception thrown while invoking " + this
    -							.invocableHandlerMethod
    -							.getShortLogMessage(), e);
    +					throw new MessagingException(requestMessage,
    +							"Exception thrown while invoking " + this.invocableHandlerMethod.getShortLogMessage(), e);
     				}
     			}
     		}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java
    index 4000d236a..5bc6d1b1b 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java
    @@ -13,6 +13,7 @@
      * See the License for the specific language governing permissions and
      * limitations under the License.
      */
    +
     package org.springframework.cloud.stream.binding;
     
     /**
    @@ -48,7 +49,7 @@ public abstract class StreamListenerErrorMessages {
     	public static final String NO_INPUT_DESTINATION = "No input destination is configured. Use either the @StreamListener value or @Input";
     
     	public static final String INVALID_DECLARATIVE_METHOD_PARAMETERS = PREFIX
    -			+ "may use @Input or @Output annotations only in declarative mode and for parameters that are bound elements or convertible from bound elements.";
    +			+ "may use @Input or @Output annotations only in declarative mode and for parameters that are binding targets or convertible from binding targets.";
     
     	public static final String AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS = "Ambiguous method arguments for the StreamListener method";
     
    @@ -59,6 +60,4 @@ public abstract class StreamListenerErrorMessages {
     
     	public static final String INVALID_OUTPUT_VALUES = "Cannot set both output (@Output/@SendTo) method annotation value"
     			+ " and @Output annotation as a method parameter";
    -
    -	public static final String TARGET_BEAN_NOT_EXISTS = "Target bean doesn't exist for the bound element name";
     }
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java
    index 0ca3f5aa0..364d88abe 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java
    @@ -13,6 +13,7 @@
      * See the License for the specific language governing permissions and
      * limitations under the License.
      */
    +
     package org.springframework.cloud.stream.binding;
     
     import java.lang.reflect.Method;
    @@ -29,7 +30,8 @@ import org.springframework.util.ObjectUtils;
     import org.springframework.util.StringUtils;
     
     /**
    - * This class contains utility methods for handling {@link StreamListener} annotated bean methods.
    + * This class contains utility methods for handling {@link StreamListener} annotated bean
    + * methods.
      *
      * @author Ilayaperumal Gopinathan
      */
    @@ -57,17 +59,22 @@ public class StreamListenerMethodUtils {
     		return outputAnnotationCount;
     	}
     
    -	protected static void validateStreamListenerMethod(Method method, int inputAnnotationCount, int outputAnnotationCount, String methodAnnotatedInboundName, String methodAnnotatedOutboundName, boolean isDeclarative) {
    +	protected static void validateStreamListenerMethod(Method method, int inputAnnotationCount,
    +			int outputAnnotationCount, String methodAnnotatedInboundName, String methodAnnotatedOutboundName,
    +			boolean isDeclarative) {
     		int methodArgumentsLength = method.getParameterTypes().length;
     		if (!isDeclarative) {
    -			Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
    +			Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0,
    +					StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
     		}
     		if (StringUtils.hasText(methodAnnotatedInboundName) && StringUtils.hasText(methodAnnotatedOutboundName)) {
    -			Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_INPUT_OUTPUT_METHOD_PARAMETERS);
    +			Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0,
    +					StreamListenerErrorMessages.INVALID_INPUT_OUTPUT_METHOD_PARAMETERS);
     		}
     		if (StringUtils.hasText(methodAnnotatedInboundName)) {
     			Assert.isTrue(inputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_INPUT_VALUES);
    -			Assert.isTrue(outputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM);
    +			Assert.isTrue(outputAnnotationCount == 0,
    +					StreamListenerErrorMessages.INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM);
     		}
     		else {
     			Assert.isTrue(inputAnnotationCount >= 1, StreamListenerErrorMessages.NO_INPUT_DESTINATION);
    @@ -79,21 +86,24 @@ public class StreamListenerMethodUtils {
     			for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
     				MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
     				if (methodParameter.hasParameterAnnotation(Input.class)) {
    -					String inboundName = (String) AnnotationUtils.getValue(methodParameter.getParameterAnnotation(Input.class));
    +					String inboundName = (String) AnnotationUtils
    +							.getValue(methodParameter.getParameterAnnotation(Input.class));
     					Assert.isTrue(StringUtils.hasText(inboundName), StreamListenerErrorMessages.INVALID_INBOUND_NAME);
     				}
     				if (methodParameter.hasParameterAnnotation(Output.class)) {
    -					String outboundName = (String) AnnotationUtils.getValue(methodParameter.getParameterAnnotation(Output.class));
    +					String outboundName = (String) AnnotationUtils
    +							.getValue(methodParameter.getParameterAnnotation(Output.class));
     					Assert.isTrue(StringUtils.hasText(outboundName), StreamListenerErrorMessages.INVALID_OUTBOUND_NAME);
     				}
     			}
    -			if (methodArgumentsLength > 1){
    -				Assert.isTrue(inputAnnotationCount + outputAnnotationCount == methodArgumentsLength, StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
    +			if (methodArgumentsLength > 1) {
    +				Assert.isTrue(inputAnnotationCount + outputAnnotationCount == methodArgumentsLength,
    +						StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
     			}
     		}
     	}
     
    -	protected static void assertStreamListenerMessageHandlerMethod(Method method) {
    +	protected static void validateStreamListenerMessageHandler(Method method) {
     		int methodArgumentsLength = method.getParameterTypes().length;
     		if (methodArgumentsLength > 1) {
     			int numAnnotatedMethodParameters = 0;
    @@ -114,7 +124,7 @@ public class StreamListenerMethodUtils {
     		}
     	}
     
    -	protected static String getOutboundElementNameFromMethod(Method method) {
    +	protected static String getOutboundBindingTargetName(Method method) {
     		SendTo sendTo = AnnotationUtils.findAnnotation(method, SendTo.class);
     		if (sendTo != null) {
     			Assert.isTrue(!ObjectUtils.isEmpty(sendTo.value()), StreamListenerErrorMessages.ATLEAST_ONE_OUTPUT);
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java
    index e147a5eab..ba80d4eb7 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java
    @@ -21,9 +21,8 @@ import org.springframework.core.MethodParameter;
     /**
      * Strategy for adapting a method argument type annotated with
      * {@link org.springframework.cloud.stream.annotation.Input} or
    - * {@link org.springframework.cloud.stream.annotation.Output} from a bound element
    - * (e.g. {@link org.springframework.messaging.MessageChannel}) supported by an
    - * existing binder.
    + * {@link org.springframework.cloud.stream.annotation.Output} from a binding type (e.g.
    + * {@link org.springframework.messaging.MessageChannel}) supported by an existing binder.
      *
      * This is a framework extension and is not primarily intended for use by end-users.
      * @author Marius Bogoevici
    @@ -31,21 +30,22 @@ import org.springframework.core.MethodParameter;
     public interface StreamListenerParameterAdapter {
     
     	/**
    -	 * Return true if the conversion from the bound element type to the argument type
    -	 * is supported.
    -	 * @param boundElementType the bound element type
    -	 * @param methodParameter  the method parameter for which the conversion is performed
    +	 * Return true if the conversion from the binding target type to the argument type is
    +	 * supported.
    +	 * @param bindingTargetType the binding target type
    +	 * @param methodParameter the method parameter for which the conversion is performed
     	 * @return true if the conversion is supported
     	 */
    -	boolean supports(Class boundElementType, MethodParameter methodParameter);
    +	boolean supports(Class bindingTargetType, MethodParameter methodParameter);
     
     	/**
    -	 * Adapts the bound element to the argument type. The result will be passed
    -	 * as argument to a method annotated with {@link org.springframework.cloud.stream.annotation.StreamListener}
    -	 * when used for setting up a pipeline.
    -	 * @param boundElement the bound element
    -	 * @param parameter    the method parameter for which the conversion is performed
    +	 * Adapts the binding target to the argument type. The result will be passed as
    +	 * argument to a method annotated with
    +	 * {@link org.springframework.cloud.stream.annotation.StreamListener} when used for
    +	 * setting up a pipeline.
    +	 * @param bindingTarget the binding target
    +	 * @param parameter the method parameter for which the conversion is performed
     	 * @return an instance of the parameter type, which will be passed to the method
     	 */
    -	A adapt(B boundElement, MethodParameter parameter);
    +	A adapt(B bindingTarget, MethodParameter parameter);
     }
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java
    index c8f370c93..3c1355ae9 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java
    @@ -17,28 +17,30 @@
     package org.springframework.cloud.stream.binding;
     
     /**
    - * A strategy for adapting the result of a {@link org.springframework.cloud.stream.annotation.StreamListener}
    - * annotated method to a bound element annotated with {@link org.springframework.cloud.stream.annotation.Output}.
    + * A strategy for adapting the result of a
    + * {@link org.springframework.cloud.stream.annotation.StreamListener} annotated method to
    + * a binding target annotated with
    + * {@link org.springframework.cloud.stream.annotation.Output}.
      *
    - * Used when the {@link org.springframework.cloud.stream.annotation.StreamListener} annotated method is operating in
    - * declarative mode.
    + * Used when the {@link org.springframework.cloud.stream.annotation.StreamListener}
    + * annotated method is operating in declarative mode.
      * @author Marius Bogoevici
      */
     public interface StreamListenerResultAdapter {
     
     	/**
    -	 * Return true if the result type can be converted to the bound element.
    -	 * @param resultType   the result type.
    -	 * @param boundElement the bound element.
    +	 * Return true if the result type can be converted to the binding target.
    +	 * @param resultType the result type.
    +	 * @param bindingTarget the binding target.
     	 * @return true if the conversion can take place.
     	 */
    -	boolean supports(Class resultType, Class boundElement);
    +	boolean supports(Class resultType, Class bindingTarget);
     
     	/**
    -	 * Adapts the result to the bound element.
    +	 * Adapts the result to the binding target.
     	 * @param streamListenerResult the result of invoking the method.
    -	 * @param boundElement         the bound element
    +	 * @param bindingTarget the binding target.
     	 */
    -	void adapt(R streamListenerResult, B boundElement);
    +	void adapt(R streamListenerResult, B bindingTarget);
     
     }
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultBindableChannelFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SubscribableChannelBindingTargetFactory.java
    similarity index 75%
    rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultBindableChannelFactory.java
    rename to spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SubscribableChannelBindingTargetFactory.java
    index 8af6fd79c..5adfadb24 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultBindableChannelFactory.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/SubscribableChannelBindingTargetFactory.java
    @@ -20,29 +20,30 @@ import org.springframework.integration.channel.DirectChannel;
     import org.springframework.messaging.SubscribableChannel;
     
     /**
    - * Class that {@link BindableProxyFactory} uses to create and configure message channels.
    + * An implementation of {@link BindingTargetFactory} for creating {@link SubscribableChannel}s.
      *
      * @author Marius Bogoevici
      * @author David Syer
      * @author Ilayaperumal Gopinathan
      */
    -public class DefaultBindableChannelFactory implements BindableChannelFactory {
    +public class SubscribableChannelBindingTargetFactory extends AbstractBindingTargetFactory {
     
     	private final MessageChannelConfigurer messageChannelConfigurer;
     
    -	public DefaultBindableChannelFactory(MessageChannelConfigurer messageChannelConfigurer) {
    +	public SubscribableChannelBindingTargetFactory(MessageChannelConfigurer messageChannelConfigurer) {
    +		super(SubscribableChannel.class);
     		this.messageChannelConfigurer = messageChannelConfigurer;
     	}
     
     	@Override
    -	public SubscribableChannel createInputChannel(String name) {
    +	public SubscribableChannel createInput(String name) {
     		SubscribableChannel subscribableChannel = new DirectChannel();
     		this.messageChannelConfigurer.configureInputChannel(subscribableChannel, name);
     		return subscribableChannel;
     	}
     
     	@Override
    -	public SubscribableChannel createOutputChannel(String name) {
    +	public SubscribableChannel createOutput(String name) {
     		SubscribableChannel subscribableChannel = new DirectChannel();
     		this.messageChannelConfigurer.configureOutputChannel(subscribableChannel, name);
     		return subscribableChannel;
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BinderFactoryConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BinderFactoryConfiguration.java
    index cf38d163e..c18680ef1 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BinderFactoryConfiguration.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BinderFactoryConfiguration.java
    @@ -61,10 +61,10 @@ public class BinderFactoryConfiguration {
     
     	@Bean
     	@ConditionalOnMissingBean(BinderFactory.class)
    -	public BinderFactory binderFactory(BinderTypeRegistry binderTypeRegistry,
    -			ChannelBindingServiceProperties channelBindingServiceProperties) {
    +	public BinderFactory binderFactory(BinderTypeRegistry binderTypeRegistry,
    +			BindingServiceProperties bindingServiceProperties) {
     		Map binderConfigurations = new HashMap<>();
    -		Map declaredBinders = channelBindingServiceProperties.getBinders();
    +		Map declaredBinders = bindingServiceProperties.getBinders();
     		boolean defaultCandidatesExist = false;
     		Iterator> binderPropertiesIterator = declaredBinders.entrySet().iterator();
     		while (!defaultCandidatesExist && binderPropertiesIterator.hasNext()) {
    @@ -94,8 +94,8 @@ public class BinderFactoryConfiguration {
     						new BinderConfiguration(entry.getValue(), new Properties(), true, true));
     			}
     		}
    -		DefaultBinderFactory binderFactory = new DefaultBinderFactory<>(binderConfigurations);
    -		binderFactory.setDefaultBinder(channelBindingServiceProperties.getDefaultBinder());
    +		DefaultBinderFactory binderFactory = new DefaultBinderFactory(binderConfigurations);
    +		binderFactory.setDefaultBinder(bindingServiceProperties.getDefaultBinder());
     		return binderFactory;
     	}
     
    @@ -105,7 +105,7 @@ public class BinderFactoryConfiguration {
     		Map binderTypes = new HashMap<>();
     		ClassLoader classLoader = configurableApplicationContext.getClassLoader();
     		if (classLoader == null) {
    -			classLoader = ChannelBindingAutoConfiguration.class.getClassLoader();
    +			classLoader = BindingAutoConfiguration.class.getClassLoader();
     		}
     		try {
     			Enumeration resources = classLoader.getResources("META-INF/spring.binders");
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingAutoConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingAutoConfiguration.java
    similarity index 91%
    rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingAutoConfiguration.java
    rename to spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingAutoConfiguration.java
    index ff710d84b..1e7d633b8 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingAutoConfiguration.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingAutoConfiguration.java
    @@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
     import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
     import org.springframework.boot.context.properties.EnableConfigurationProperties;
     import org.springframework.cloud.stream.binding.Bindable;
    -import org.springframework.cloud.stream.binding.ChannelBindingService;
    +import org.springframework.cloud.stream.binding.BindingService;
     import org.springframework.cloud.stream.endpoint.ChannelsEndpoint;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
    @@ -43,10 +43,10 @@ import org.springframework.messaging.MessageChannel;
      * @author Marius Bogoevici
      */
     @Configuration
    -@ConditionalOnBean(ChannelBindingService.class)
    +@ConditionalOnBean(BindingService.class)
     @EnableConfigurationProperties(DefaultPollerProperties.class)
     @AutoConfigureBefore(EndpointAutoConfiguration.class)
    -public class ChannelBindingAutoConfiguration {
    +public class BindingAutoConfiguration {
     
     	@Autowired
     	private DefaultPollerProperties poller;
    @@ -61,7 +61,7 @@ public class ChannelBindingAutoConfiguration {
     	}
     
     	@Bean
    -	public ChannelsEndpoint channelsEndpoint(ChannelBindingServiceProperties properties) {
    +	public ChannelsEndpoint channelsEndpoint(BindingServiceProperties properties) {
     		return new ChannelsEndpoint(this.adapters, properties);
     	}
     
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingBeansRegistrar.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingBeansRegistrar.java
    index 8103b2b00..222759ae1 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingBeansRegistrar.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingBeansRegistrar.java
    @@ -40,9 +40,9 @@ public class BindingBeansRegistrar implements ImportBeanDefinitionRegistrar {
     				ClassUtils.resolveClassName(metadata.getClassName(), null),
     				EnableBinding.class);
     		for (Class type : collectClasses(attrs, metadata.getClassName())) {
    -			BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions(type,
    +			BindingBeanDefinitionRegistryUtils.registerBindingTargetBeanDefinitions(type,
     					type.getName(), registry);
    -			BindingBeanDefinitionRegistryUtils.registerChannelsQualifiedBeanDefinitions(
    +			BindingBeanDefinitionRegistryUtils.registerBindingTargetsQualifiedBeanDefinitions(
     					ClassUtils.resolveClassName(metadata.getClassName(), null), type,
     					registry);
     		}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java
    similarity index 74%
    rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java
    rename to spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java
    index b3e3ecb63..133daa316 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java
    @@ -34,20 +34,20 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
     import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
     import org.springframework.boot.context.properties.EnableConfigurationProperties;
     import org.springframework.cloud.stream.binder.BinderFactory;
    -import org.springframework.cloud.stream.binding.BindableChannelFactory;
    +import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory;
     import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
     import org.springframework.cloud.stream.binding.BinderAwareRouterBeanPostProcessor;
    -import org.springframework.cloud.stream.binding.ChannelBindingService;
    +import org.springframework.cloud.stream.binding.BindingService;
     import org.springframework.cloud.stream.binding.CompositeMessageChannelConfigurer;
     import org.springframework.cloud.stream.binding.ContextStartAfterRefreshListener;
    -import org.springframework.cloud.stream.binding.DefaultBindableChannelFactory;
     import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
     import org.springframework.cloud.stream.binding.InputBindingLifecycle;
     import org.springframework.cloud.stream.binding.MessageChannelConfigurer;
     import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
     import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
    -import org.springframework.cloud.stream.binding.SingleChannelBindable;
    +import org.springframework.cloud.stream.binding.SingleBindingTargetBindable;
     import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
    +import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
     import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
    @@ -76,8 +76,8 @@ import org.springframework.util.CollectionUtils;
      * @author Gary Russell
      */
     @Configuration
    -@EnableConfigurationProperties(ChannelBindingServiceProperties.class)
    -public class ChannelBindingServiceConfiguration {
    +@EnableConfigurationProperties(BindingServiceProperties.class)
    +public class BindingServiceConfiguration {
     
     	private static final String ERROR_CHANNEL_NAME = "error";
     
    @@ -92,25 +92,24 @@ public class ChannelBindingServiceConfiguration {
     
     	@Bean
     	// This conditional is intentionally not in an autoconfig (usually a bad idea) because
    -	// it is used to detect a ChannelBindingService in the parent context (which we know
    +	// it is used to detect a BindingService in the parent context (which we know
     	// already exists).
    -	@ConditionalOnMissingBean(ChannelBindingService.class)
    -	public ChannelBindingService bindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
    -			BinderFactory binderFactory) {
    -		return new ChannelBindingService(channelBindingServiceProperties, binderFactory);
    +	@ConditionalOnMissingBean(BindingService.class)
    +	public BindingService bindingService(BindingServiceProperties bindingServiceProperties,
    +			BinderFactory binderFactory) {
    +		return new BindingService(bindingServiceProperties, binderFactory);
     	}
     
     	@Bean
    -	public MessageConverterConfigurer messageConverterConfigurer(
    -			ChannelBindingServiceProperties channelBindingServiceProperties,
    +	public MessageConverterConfigurer messageConverterConfigurer(BindingServiceProperties bindingServiceProperties,
     			CompositeMessageConverterFactory compositeMessageConverterFactory) {
    -		return new MessageConverterConfigurer(channelBindingServiceProperties,
    -				compositeMessageConverterFactory);
    +		return new MessageConverterConfigurer(bindingServiceProperties, compositeMessageConverterFactory);
     	}
     
     	@Bean
    -	public BindableChannelFactory channelFactory(CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) {
    -		return new DefaultBindableChannelFactory(compositeMessageChannelConfigurer);
    +	public SubscribableChannelBindingTargetFactory channelFactory(
    +			CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) {
    +		return new SubscribableChannelBindingTargetFactory(compositeMessageChannelConfigurer);
     	}
     
     	@Bean
    @@ -140,17 +139,17 @@ public class ChannelBindingServiceConfiguration {
     	}
     
     	@Bean
    -	public BinderAwareChannelResolver binderAwareChannelResolver(ChannelBindingService channelBindingService,
    -			BindableChannelFactory bindableChannelFactory, DynamicDestinationsBindable dynamicDestinationsBindable) {
    -		return new BinderAwareChannelResolver(channelBindingService, bindableChannelFactory,
    -				dynamicDestinationsBindable);
    +	public BinderAwareChannelResolver binderAwareChannelResolver(BindingService bindingService,
    +			AbstractBindingTargetFactory bindingTargetFactory,
    +			DynamicDestinationsBindable dynamicDestinationsBindable) {
    +		return new BinderAwareChannelResolver(bindingService, bindingTargetFactory, dynamicDestinationsBindable);
     	}
     
     	@Bean
     	@ConditionalOnProperty("spring.cloud.stream.bindings." + ERROR_CHANNEL_NAME + ".destination")
    -	public SingleChannelBindable errorChannelBindable(
    +	public SingleBindingTargetBindable errorChannelBindable(
     			@Qualifier(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME) PublishSubscribeChannel errorChannel) {
    -		return new SingleChannelBindable(ERROR_CHANNEL_NAME, errorChannel);
    +		return new SingleBindingTargetBindable(ERROR_CHANNEL_NAME, errorChannel);
     	}
     
     	@Bean
    @@ -171,8 +170,8 @@ public class ChannelBindingServiceConfiguration {
     	public static MessageHandlerMethodFactory messageHandlerMethodFactory(
     			CompositeMessageConverterFactory compositeMessageConverterFactory) {
     		DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
    -		messageHandlerMethodFactory.setMessageConverter(
    -				compositeMessageConverterFactory.getMessageConverterForAllRegistered());
    +		messageHandlerMethodFactory
    +				.setMessageConverter(compositeMessageConverterFactory.getMessageConverterForAllRegistered());
     		return messageHandlerMethodFactory;
     	}
     
    @@ -180,8 +179,14 @@ public class ChannelBindingServiceConfiguration {
     	public static StreamListenerAnnotationBeanPostProcessor bindToAnnotationBeanPostProcessor(
     			@Lazy BinderAwareChannelResolver binderAwareChannelResolver,
     			@Lazy MessageHandlerMethodFactory messageHandlerMethodFactory) {
    -		return new StreamListenerAnnotationBeanPostProcessor(binderAwareChannelResolver,
    -				messageHandlerMethodFactory);
    +		return new StreamListenerAnnotationBeanPostProcessor(binderAwareChannelResolver, messageHandlerMethodFactory);
    +	}
    +
    +	@Bean
    +	// provided for backwards compatibility scenarios
    +	public ChannelBindingServiceProperties channelBindingServiceProperties(
    +			BindingServiceProperties bindingServiceProperties) {
    +		return new ChannelBindingServiceProperties(bindingServiceProperties);
     	}
     
     	// IMPORTANT: Nested class to avoid instantiating all of the above early
    @@ -194,22 +199,18 @@ public class ChannelBindingServiceConfiguration {
     		public BinderAwareRouterBeanPostProcessor binderAwareRouterBeanPostProcessor(
     				final ConfigurableListableBeanFactory beanFactory) {
     			// IMPORTANT: Lazy delegate to avoid instantiating all of the above early
    -			return new BinderAwareRouterBeanPostProcessor(
    -					new DestinationResolver() {
    +			return new BinderAwareRouterBeanPostProcessor(new DestinationResolver() {
     
    -						@Override
    -						public MessageChannel resolveDestination(String name)
    -								throws DestinationResolutionException {
    -							if (PostProcessorConfiguration.this.binderAwareChannelResolver == null) {
    -								PostProcessorConfiguration.this.binderAwareChannelResolver = BeanFactoryUtils
    -										.beanOfType(beanFactory,
    -												BinderAwareChannelResolver.class);
    -							}
    -							return PostProcessorConfiguration.this.binderAwareChannelResolver
    -									.resolveDestination(name);
    -						}
    +				@Override
    +				public MessageChannel resolveDestination(String name) throws DestinationResolutionException {
    +					if (PostProcessorConfiguration.this.binderAwareChannelResolver == null) {
    +						PostProcessorConfiguration.this.binderAwareChannelResolver = BeanFactoryUtils
    +								.beanOfType(beanFactory, BinderAwareChannelResolver.class);
    +					}
    +					return PostProcessorConfiguration.this.binderAwareChannelResolver.resolveDestination(name);
    +				}
     
    -					});
    +			});
     		}
     
     		/**
    @@ -225,8 +226,7 @@ public class ChannelBindingServiceConfiguration {
     				@Override
     				public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
     					if (IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME.equals(beanName)) {
    -						IntegrationEvaluationContextFactoryBean factoryBean =
    -								(IntegrationEvaluationContextFactoryBean) bean;
    +						IntegrationEvaluationContextFactoryBean factoryBean = (IntegrationEvaluationContextFactoryBean) bean;
     						Map factoryBeanAccessors = factoryBean.getPropertyAccessors();
     						for (Map.Entry entry : accessors.entrySet()) {
     							if (!factoryBeanAccessors.containsKey(entry.getKey())) {
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java
    new file mode 100644
    index 000000000..6c64813ba
    --- /dev/null
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java
    @@ -0,0 +1,209 @@
    +/*
    + * Copyright 2015-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.cloud.stream.config;
    +
    +import java.util.HashMap;
    +import java.util.Map;
    +import java.util.TreeMap;
    +
    +import com.fasterxml.jackson.annotation.JsonInclude;
    +import com.fasterxml.jackson.annotation.JsonInclude.Include;
    +
    +import org.springframework.beans.BeanUtils;
    +import org.springframework.beans.BeansException;
    +import org.springframework.beans.factory.InitializingBean;
    +import org.springframework.beans.factory.annotation.Value;
    +import org.springframework.boot.context.properties.ConfigurationProperties;
    +import org.springframework.cloud.stream.binder.ConsumerProperties;
    +import org.springframework.cloud.stream.binder.ProducerProperties;
    +import org.springframework.context.ApplicationContext;
    +import org.springframework.context.ApplicationContextAware;
    +import org.springframework.context.ConfigurableApplicationContext;
    +import org.springframework.core.convert.ConversionService;
    +import org.springframework.integration.support.utils.IntegrationUtils;
    +import org.springframework.util.Assert;
    +
    +/**
    + * @author Dave Syer
    + * @author Marius Bogoevici
    + * @author Gary Russell
    + * @author Ilayaperumal Gopinathan
    + */
    +@ConfigurationProperties("spring.cloud.stream")
    +@JsonInclude(Include.NON_DEFAULT)
    +public class BindingServiceProperties
    +		implements ApplicationContextAware, InitializingBean {
    +
    +	private ConversionService conversionService;
    +
    +	@Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}")
    +	private int instanceIndex;
    +
    +	private int instanceCount = 1;
    +
    +	private Map bindings = new TreeMap<>(
    +			String.CASE_INSENSITIVE_ORDER);
    +
    +	private Map binders = new HashMap<>();
    +
    +	private String defaultBinder;
    +
    +	private String[] dynamicDestinations = new String[0];
    +
    +	private ConfigurableApplicationContext applicationContext;
    +
    +	public Map getBindings() {
    +		return this.bindings;
    +	}
    +
    +	public void setBindings(Map bindings) {
    +		this.bindings = bindings;
    +	}
    +
    +	public Map getBinders() {
    +		return this.binders;
    +	}
    +
    +	public void setBinders(Map binders) {
    +		this.binders = binders;
    +	}
    +
    +	public String getDefaultBinder() {
    +		return this.defaultBinder;
    +	}
    +
    +	public void setDefaultBinder(String defaultBinder) {
    +		this.defaultBinder = defaultBinder;
    +	}
    +
    +	public int getInstanceIndex() {
    +		return this.instanceIndex;
    +	}
    +
    +	public void setInstanceIndex(int instanceIndex) {
    +		this.instanceIndex = instanceIndex;
    +	}
    +
    +	public int getInstanceCount() {
    +		return this.instanceCount;
    +	}
    +
    +	public void setInstanceCount(int instanceCount) {
    +		this.instanceCount = instanceCount;
    +	}
    +
    +	public String[] getDynamicDestinations() {
    +		return this.dynamicDestinations;
    +	}
    +
    +	public void setDynamicDestinations(String[] dynamicDestinations) {
    +		this.dynamicDestinations = dynamicDestinations;
    +	}
    +
    +	@Override
    +	public void setApplicationContext(ApplicationContext applicationContext)
    +			throws BeansException {
    +		this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    +		// override the bindings store with the environment-initializing version if in a
    +		// Spring context
    +		this.bindings = new EnvironmentEntryInitializingTreeMap<>(this.applicationContext,
    +				BindingProperties.class, "spring.cloud.stream.default",
    +				new TreeMap(String.CASE_INSENSITIVE_ORDER));
    +	}
    +
    +	public void setConversionService(ConversionService conversionService) {
    +		this.conversionService = conversionService;
    +	}
    +
    +	@Override
    +	public void afterPropertiesSet() throws Exception {
    +		if (this.conversionService == null) {
    +			this.conversionService = this.applicationContext.getBean(
    +					IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME,
    +					ConversionService.class);
    +		}
    +	}
    +
    +	public String getBinder(String bindingName) {
    +		return getBindingProperties(bindingName).getBinder();
    +	}
    +
    +	/**
    +	 * Return configuration properties as Map.
    +	 * @return map of binding configuration properties.
    +	 */
    +	public Map asMapProperties() {
    +		Map properties = new HashMap<>();
    +		properties.put("instanceIndex", String.valueOf(getInstanceIndex()));
    +		properties.put("instanceCount", String.valueOf(getInstanceCount()));
    +		properties.put("defaultBinder", getDefaultBinder());
    +		properties.put("dynamicDestinations", getDynamicDestinations());
    +		for (Map.Entry entry : this.bindings.entrySet()) {
    +			properties.put(entry.getKey(), entry.getValue().toString());
    +		}
    +		for (Map.Entry entry : this.binders.entrySet()) {
    +			properties.put(entry.getKey(), entry.getValue());
    +		}
    +		return properties;
    +	}
    +
    +	public ConsumerProperties getConsumerProperties(String inputBindingName) {
    +		Assert.notNull(inputBindingName, "The input binding name cannot be null");
    +		ConsumerProperties consumerProperties = getBindingProperties(inputBindingName)
    +				.getConsumer();
    +		if (consumerProperties == null) {
    +			consumerProperties = new ConsumerProperties();
    +		}
    +		// propagate instance count and instance index if not already set
    +		if (consumerProperties.getInstanceCount() < 0) {
    +			consumerProperties.setInstanceCount(this.instanceCount);
    +		}
    +		if (consumerProperties.getInstanceIndex() < 0) {
    +			consumerProperties.setInstanceIndex(this.instanceIndex);
    +		}
    +		return consumerProperties;
    +	}
    +
    +	public ProducerProperties getProducerProperties(String outputBindingName) {
    +		Assert.notNull(outputBindingName, "The output binding name cannot be null");
    +		ProducerProperties producerProperties = getBindingProperties(outputBindingName)
    +				.getProducer();
    +		if (producerProperties == null) {
    +			producerProperties = new ProducerProperties();
    +		}
    +		return producerProperties;
    +	}
    +
    +	public BindingProperties getBindingProperties(String bindingName) {
    +		BindingProperties bindingProperties = new BindingProperties();
    +		if (this.bindings.containsKey(bindingName)) {
    +			BeanUtils.copyProperties(this.bindings.get(bindingName), bindingProperties);
    +		}
    +		if (bindingProperties.getDestination() == null) {
    +			bindingProperties.setDestination(bindingName);
    +		}
    +		return bindingProperties;
    +	}
    +
    +	public String getGroup(String bindingName) {
    +		return getBindingProperties(bindingName).getGroup();
    +	}
    +
    +	public String getBindingDestination(String bindingName) {
    +		return getBindingProperties(bindingName).getDestination();
    +	}
    +}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java
    index 6b5b7493d..47dc63d05 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2015-2016 the original author or authors.
    + * Copyright 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.
    @@ -16,187 +16,114 @@
     
     package org.springframework.cloud.stream.config;
     
    -import java.util.HashMap;
     import java.util.Map;
    -import java.util.TreeMap;
     
    -import com.fasterxml.jackson.annotation.JsonInclude;
    -import com.fasterxml.jackson.annotation.JsonInclude.Include;
    -
    -import org.springframework.beans.BeanUtils;
     import org.springframework.beans.BeansException;
    -import org.springframework.beans.factory.InitializingBean;
    -import org.springframework.beans.factory.annotation.Value;
    -import org.springframework.boot.context.properties.ConfigurationProperties;
     import org.springframework.cloud.stream.binder.ConsumerProperties;
     import org.springframework.cloud.stream.binder.ProducerProperties;
     import org.springframework.context.ApplicationContext;
    -import org.springframework.context.ApplicationContextAware;
    -import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.core.convert.ConversionService;
    -import org.springframework.integration.support.utils.IntegrationUtils;
    -import org.springframework.util.Assert;
     
     /**
    - * @author Dave Syer
    + * Provides a wrapper around {@link BindingServiceProperties} for backwards compatibility.
      * @author Marius Bogoevici
    - * @author Gary Russell
    - * @author Ilayaperumal Gopinathan
      */
    -@ConfigurationProperties("spring.cloud.stream")
    -@JsonInclude(Include.NON_DEFAULT)
    -public class ChannelBindingServiceProperties implements ApplicationContextAware, InitializingBean {
    +@Deprecated
    +public class ChannelBindingServiceProperties {
     
    -	private ConversionService conversionService;
    +	private final BindingServiceProperties bindingServiceProperties;
     
    -	@Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}")
    -	private int instanceIndex;
    -
    -	private int instanceCount = 1;
    -
    -	private Map bindings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    -
    -	private Map binders = new HashMap<>();
    -
    -	private String defaultBinder;
    -
    -	private String[] dynamicDestinations = new String[0];
    -
    -	private ConfigurableApplicationContext applicationContext;
    +	public ChannelBindingServiceProperties(
    +			BindingServiceProperties bindingServiceProperties) {
    +		this.bindingServiceProperties = bindingServiceProperties;
    +	}
     
     	public Map getBindings() {
    -		return this.bindings;
    +		return bindingServiceProperties.getBindings();
     	}
     
     	public void setBindings(Map bindings) {
    -		this.bindings = bindings;
    +		bindingServiceProperties.setBindings(bindings);
     	}
     
     	public Map getBinders() {
    -		return this.binders;
    +		return bindingServiceProperties.getBinders();
     	}
     
     	public void setBinders(Map binders) {
    -		this.binders = binders;
    +		bindingServiceProperties.setBinders(binders);
     	}
     
     	public String getDefaultBinder() {
    -		return this.defaultBinder;
    +		return bindingServiceProperties.getDefaultBinder();
     	}
     
     	public void setDefaultBinder(String defaultBinder) {
    -		this.defaultBinder = defaultBinder;
    +		bindingServiceProperties.setDefaultBinder(defaultBinder);
     	}
     
     	public int getInstanceIndex() {
    -		return this.instanceIndex;
    +		return bindingServiceProperties.getInstanceIndex();
     	}
     
     	public void setInstanceIndex(int instanceIndex) {
    -		this.instanceIndex = instanceIndex;
    +		bindingServiceProperties.setInstanceIndex(instanceIndex);
     	}
     
     	public int getInstanceCount() {
    -		return this.instanceCount;
    +		return bindingServiceProperties.getInstanceCount();
     	}
     
     	public void setInstanceCount(int instanceCount) {
    -		this.instanceCount = instanceCount;
    +		bindingServiceProperties.setInstanceCount(instanceCount);
     	}
     
     	public String[] getDynamicDestinations() {
    -		return this.dynamicDestinations;
    +		return bindingServiceProperties.getDynamicDestinations();
     	}
     
     	public void setDynamicDestinations(String[] dynamicDestinations) {
    -		this.dynamicDestinations = dynamicDestinations;
    +		bindingServiceProperties.setDynamicDestinations(dynamicDestinations);
     	}
     
    -	@Override
    -	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    -		this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    -		// override the bindings store with the environment-initializing version if in a Spring context
    -		this.bindings = new EnvironmentEntryInitializingTreeMap<>(this.applicationContext, BindingProperties.class,
    -				"spring.cloud.stream.default", new TreeMap(String.CASE_INSENSITIVE_ORDER));
    +	public void setApplicationContext(ApplicationContext applicationContext)
    +			throws BeansException {
    +		bindingServiceProperties.setApplicationContext(applicationContext);
     	}
     
     	public void setConversionService(ConversionService conversionService) {
    -		this.conversionService = conversionService;
    +		bindingServiceProperties.setConversionService(conversionService);
     	}
     
    -	@Override
     	public void afterPropertiesSet() throws Exception {
    -		if (this.conversionService == null) {
    -			this.conversionService = this.applicationContext.getBean(
    -					IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
    -		}
    +		bindingServiceProperties.afterPropertiesSet();
     	}
     
     	public String getBinder(String channelName) {
    -		return getBindingProperties(channelName).getBinder();
    +		return bindingServiceProperties.getBinder(channelName);
     	}
     
    -	/**
    -	 * Return configuration properties as Map.
    -	 * @return map of channel binding configuration properties.
    -	 */
     	public Map asMapProperties() {
    -		Map properties = new HashMap<>();
    -		properties.put("instanceIndex", String.valueOf(getInstanceIndex()));
    -		properties.put("instanceCount", String.valueOf(getInstanceCount()));
    -		properties.put("defaultBinder", getDefaultBinder());
    -		properties.put("dynamicDestinations", getDynamicDestinations());
    -		for (Map.Entry entry : this.bindings.entrySet()) {
    -			properties.put(entry.getKey(), entry.getValue().toString());
    -		}
    -		for (Map.Entry entry : this.binders.entrySet()) {
    -			properties.put(entry.getKey(), entry.getValue());
    -		}
    -		return properties;
    +		return bindingServiceProperties.asMapProperties();
     	}
     
     	public ConsumerProperties getConsumerProperties(String inputChannelName) {
    -		Assert.notNull(inputChannelName, "The input channel name cannot be null");
    -		ConsumerProperties consumerProperties = getBindingProperties(inputChannelName).getConsumer();
    -		if (consumerProperties == null) {
    -			consumerProperties = new ConsumerProperties();
    -		}
    -		// propagate instance count and instance index if not already set
    -		if (consumerProperties.getInstanceCount() < 0) {
    -			consumerProperties.setInstanceCount(this.instanceCount);
    -		}
    -		if (consumerProperties.getInstanceIndex() < 0) {
    -			consumerProperties.setInstanceIndex(this.instanceIndex);
    -		}
    -		return consumerProperties;
    +		return bindingServiceProperties.getConsumerProperties(inputChannelName);
     	}
     
     	public ProducerProperties getProducerProperties(String outputChannelName) {
    -		Assert.notNull(outputChannelName, "The output channel name cannot be null");
    -		ProducerProperties producerProperties = getBindingProperties(outputChannelName).getProducer();
    -		if (producerProperties == null) {
    -			producerProperties = new ProducerProperties();
    -		}
    -		return producerProperties;
    +		return bindingServiceProperties.getProducerProperties(outputChannelName);
     	}
     
     	public BindingProperties getBindingProperties(String channelName) {
    -		BindingProperties bindingProperties = new BindingProperties();
    -		if (this.bindings.containsKey(channelName)) {
    -			BeanUtils.copyProperties(this.bindings.get(channelName), bindingProperties);
    -		}
    -		if (bindingProperties.getDestination() == null) {
    -			bindingProperties.setDestination(channelName);
    -		}
    -		return bindingProperties;
    +		return bindingServiceProperties.getBindingProperties(channelName);
     	}
     
     	public String getGroup(String channelName) {
    -		return getBindingProperties(channelName).getGroup();
    +		return bindingServiceProperties.getGroup(channelName);
     	}
     
     	public String getBindingDestination(String channelName) {
    -		return getBindingProperties(channelName).getDestination();
    +		return bindingServiceProperties.getBindingDestination(channelName);
     	}
    -
     }
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/EnvironmentEntryInitializingTreeMap.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/EnvironmentEntryInitializingTreeMap.java
    index 37b7c6a87..d35ef9517 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/EnvironmentEntryInitializingTreeMap.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/EnvironmentEntryInitializingTreeMap.java
    @@ -1,3 +1,19 @@
    +/*
    + * Copyright 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.cloud.stream.config;
     
     import java.util.AbstractMap;
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java
    index 0029f33b3..eb2f69fc2 100644
    --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java
    @@ -29,10 +29,11 @@ import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
     import org.springframework.boot.actuate.endpoint.Endpoint;
     import org.springframework.cloud.stream.binding.Bindable;
     import org.springframework.cloud.stream.config.BindingProperties;
    -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
    +import org.springframework.cloud.stream.config.BindingServiceProperties;
     
     /**
    - * An {@link Endpoint} that has the binding information on all the {@link Bindable} message channels.
    + * An {@link Endpoint} that has the binding information on all the {@link Bindable}
    + * message channels.
      *
      * @author Dave Syer
      * @author Ilayaperumal Gopinathan
    @@ -41,9 +42,9 @@ public class ChannelsEndpoint extends AbstractEndpoint> {
     
     	private List adapters;
     
    -	private ChannelBindingServiceProperties properties;
    +	private BindingServiceProperties properties;
     
    -	public ChannelsEndpoint(List adapters, ChannelBindingServiceProperties properties) {
    +	public ChannelsEndpoint(List adapters, BindingServiceProperties properties) {
     		super("channels");
     		this.adapters = adapters;
     		this.properties = properties;
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/internal/InternalPropertyNames.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/internal/InternalPropertyNames.java
    new file mode 100644
    index 000000000..46ddd821b
    --- /dev/null
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/internal/InternalPropertyNames.java
    @@ -0,0 +1,32 @@
    +/*
    + * Copyright 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.cloud.stream.internal;
    +
    +/**
    + * Contains the names of properties for the internal use of Spring Cloud Stream.
    + * 
    + * @author Marius Bogoevici
    + */
    +public abstract class InternalPropertyNames {
    +
    +	public static final String SPRING_CLOUD_STREAM_INTERNAL_PREFIX = "spring.cloud.stream.internal";
    +
    +	public static final String NAMESPACE_PROPERTY_NAME = SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".namespace";
    +
    +	public static final String SELF_CONTAINED_APP_PROPERTY_NAME = SPRING_CLOUD_STREAM_INTERNAL_PREFIX
    +			+ ".selfContained";
    +}
    diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/reflection/GenericsUtils.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/reflection/GenericsUtils.java
    new file mode 100644
    index 000000000..c8f336b55
    --- /dev/null
    +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/reflection/GenericsUtils.java
    @@ -0,0 +1,88 @@
    +/*
    + * Copyright 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.cloud.stream.reflection;
    +
    +import org.springframework.core.ResolvableType;
    +import org.springframework.util.Assert;
    +
    +/**
    + * Internal utilities for handling generics.
    + *
    + * @author Marius Bogoevici
    + */
    +public abstract class GenericsUtils {
    +
    +	/**
    +	 * For a specific class that implements or extends a parametrized type
    +	 * returns the parameter of that interface at a given position. For example,
    +	 * for this class:
    +	 * 
    +	 * {@code
    +	 * class MessageChannelBinder implements Binder
    +	 * }
    +	 *
    +	 * 
    +	 * {@code
    +	 * getParameterType(MessageChannelBinder.class, Binder.class, 0);
    +	 * }
    +	 *
    +	 * will return {@code Binder}
    +	 *
    +	 * @param evaluatedClass the evaluated class
    +	 * @param interfaceClass the parametrized interface
    +	 * @param position the position
    +	 * @return the parameter type if any
    +	 * @throws IllegalStateException if the evaluated class does not implement the interface or
    +	 */
    +	public static Class getParameterType(Class evaluatedClass, Class interfaceClass, int position) {
    +		Class bindableType = null;
    +		Assert.isTrue(interfaceClass.isInterface(), "'interfaceClass' must be an interface");
    +		if (!interfaceClass.isAssignableFrom(evaluatedClass)) {
    +			throw new IllegalStateException(evaluatedClass + " does not implement " + interfaceClass);
    +		}
    +		ResolvableType currentType = ResolvableType.forType(evaluatedClass);
    +		while (!Object.class.equals(currentType.getRawClass()) && bindableType == null) {
    +			ResolvableType[] interfaces = currentType.getInterfaces();
    +			ResolvableType resolvableType = null;
    +			for (ResolvableType interfaceType : interfaces) {
    +				if (interfaceClass.equals(interfaceType.getRawClass())) {
    +					resolvableType = interfaceType;
    +					break;
    +				}
    +			}
    +			if (resolvableType == null) {
    +				currentType = currentType.getSuperType();
    +			}
    +			else {
    +				ResolvableType[] generics = resolvableType.getGenerics();
    +				ResolvableType generic = generics[position];
    +				Class resolvedParameter = generic.resolve();
    +				if (resolvedParameter != null) {
    +					bindableType = resolvedParameter;
    +				}
    +				else {
    +					bindableType = Object.class;
    +				}
    +			}
    +		}
    +		if (bindableType == null) {
    +			throw new IllegalStateException("Cannot find parameter of " + evaluatedClass.getName() + " for "
    +					+ interfaceClass + " at position " + position);
    +		}
    +		return bindableType;
    +	}
    +}
    diff --git a/spring-cloud-stream/src/main/resources/META-INF/spring.factories b/spring-cloud-stream/src/main/resources/META-INF/spring.factories
    index 3678aa5d1..257a86e9a 100644
    --- a/spring-cloud-stream/src/main/resources/META-INF/spring.factories
    +++ b/spring-cloud-stream/src/main/resources/META-INF/spring.factories
    @@ -1,2 +1,2 @@
     org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
    -org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration
    +org.springframework.cloud.stream.config.BindingAutoConfiguration
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/aggregation/AggregationTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/aggregation/AggregationTest.java
    index a47595035..92053d122 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/aggregation/AggregationTest.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/aggregation/AggregationTest.java
    @@ -27,9 +27,10 @@ import org.springframework.beans.DirectFieldAccessor;
     import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
     import org.springframework.cloud.stream.aggregate.AggregateApplicationBuilder;
     import org.springframework.cloud.stream.aggregate.AggregateApplicationBuilder.SourceConfigurer;
    +import org.springframework.cloud.stream.aggregate.SharedBindingTargetRegistry;
     import org.springframework.cloud.stream.aggregate.SharedChannelRegistry;
     import org.springframework.cloud.stream.annotation.EnableBinding;
    -import org.springframework.cloud.stream.binding.BindableChannelFactory;
    +import org.springframework.cloud.stream.binding.BindingTargetFactory;
     import org.springframework.cloud.stream.messaging.Processor;
     import org.springframework.cloud.stream.messaging.Source;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
    @@ -66,10 +67,26 @@ public class AggregationTest {
     				.from(TestSource.class)
     				.to(TestProcessor.class)
     				.run();
    +		SharedBindingTargetRegistry sharedBindingTargetRegistry = aggregatedApplicationContext
    +				.getBean(SharedBindingTargetRegistry.class);
    +		BindingTargetFactory channelFactory = aggregatedApplicationContext
    +				.getBean(BindingTargetFactory.class);
    +		assertThat(channelFactory).isNotNull();
    +		assertThat(sharedBindingTargetRegistry.getAll().keySet()).hasSize(2);
    +		aggregatedApplicationContext.close();
    +	}
    +
    +
    +	@Test
    +	public void testModuleAggregationUsingSharedChannelRegistry() {
    +		// test backward compatibility
    +		aggregatedApplicationContext = new AggregateApplicationBuilder(
    +				MockBinderRegistryConfiguration.class, "--server.port=0")
    +				.from(TestSource.class).to(TestProcessor.class).run();
     		SharedChannelRegistry sharedChannelRegistry = aggregatedApplicationContext
     				.getBean(SharedChannelRegistry.class);
    -		BindableChannelFactory channelFactory = aggregatedApplicationContext
    -				.getBean(BindableChannelFactory.class);
    +		BindingTargetFactory channelFactory = aggregatedApplicationContext
    +				.getBean(BindingTargetFactory.class);
     		assertThat(channelFactory).isNotNull();
     		assertThat(sharedChannelRegistry.getAll().keySet()).hasSize(2);
     		aggregatedApplicationContext.close();
    @@ -301,8 +318,8 @@ public class AggregationTest {
     				.namespace("bar").run();
     		SharedChannelRegistry sharedChannelRegistry = aggregatedApplicationContext
     				.getBean(SharedChannelRegistry.class);
    -		BindableChannelFactory channelFactory = aggregatedApplicationContext
    -				.getBean(BindableChannelFactory.class);
    +		BindingTargetFactory channelFactory = aggregatedApplicationContext
    +				.getBean(BindingTargetFactory.class);
     		Object fooOutput = sharedChannelRegistry.get("foo.output");
     		assertThat(fooOutput).isNotNull();
     		assertThat(fooOutput).isInstanceOf(MessageChannel.class);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithBindingTargetsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithBindingTargetsTests.java
    index cb7a4a1dc..63cf5e715 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithBindingTargetsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithBindingTargetsTests.java
    @@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
     import org.springframework.context.annotation.PropertySource;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.anyString;
    @@ -51,7 +52,7 @@ public class ArbitraryInterfaceWithBindingTargetsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testArbitraryInterfaceChannelsBound() {
    -		Binder binder = binderFactory.getBinder(null);
    +		Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindConsumer(eq("someQueue.0"), anyString(), eq(this.fooChannels.foo()),
     				Mockito.any());
     		verify(binder).bindConsumer(eq("someQueue.1"), anyString(), eq(this.fooChannels.bar()),
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithDefaultsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithDefaultsTests.java
    index 22bf635a4..7e5999311 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithDefaultsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceWithDefaultsTests.java
    @@ -26,6 +26,7 @@ import org.springframework.boot.test.context.SpringBootTest;
     import org.springframework.cloud.stream.annotation.EnableBinding;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.anyString;
    @@ -50,7 +51,7 @@ public class ArbitraryInterfaceWithDefaultsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testArbitraryInterfaceChannelsBound() {
    -		final Binder binder = this.binderFactory.getBinder(null);
    +		final Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindConsumer(eq("foo"), anyString(), eq(this.fooChannels.foo()),
     				Mockito.any());
     		verify(binder).bindConsumer(eq("bar"), anyString(), eq(this.fooChannels.bar()),
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java
    index 60a3db46c..40417f4c7 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java
    @@ -31,16 +31,16 @@ import org.mockito.Mockito;
     import org.springframework.beans.factory.BeanFactory;
     import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
     import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    -import org.springframework.cloud.stream.binding.BindableChannelFactory;
    +import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory;
     import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
    -import org.springframework.cloud.stream.binding.ChannelBindingService;
    -import org.springframework.cloud.stream.binding.DefaultBindableChannelFactory;
    +import org.springframework.cloud.stream.binding.BindingService;
     import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
     import org.springframework.cloud.stream.binding.InputBindingLifecycle;
     import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
     import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
    +import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
     import org.springframework.cloud.stream.config.BindingProperties;
    -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
    +import org.springframework.cloud.stream.config.BindingServiceProperties;
     import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
     import org.springframework.context.support.StaticApplicationContext;
     import org.springframework.integration.channel.DirectChannel;
    @@ -75,9 +75,9 @@ public class BinderAwareChannelResolverTests {
     
     	protected volatile Binder binder;
     
    -	protected volatile BindableChannelFactory bindableChannelFactory;
    +	protected volatile AbstractBindingTargetFactory bindingTargetFactory;
     
    -	protected volatile ChannelBindingServiceProperties channelBindingServiceProperties;
    +	protected volatile BindingServiceProperties bindingServiceProperties;
     
     	protected volatile DynamicDestinationsBindable dynamicDestinationsBindable;
     
    @@ -87,29 +87,29 @@ public class BinderAwareChannelResolverTests {
     	public void setupContext() throws Exception {
     		producerBindings = new ArrayList<>();
     		this.binder = new TestBinder();
    -		BinderFactory binderFactory = new BinderFactory() {
    +		BinderFactory binderFactory = new BinderFactory() {
     
     			@Override
    -			public Binder getBinder(String configurationName) {
    -				return binder;
    +			public  Binder getBinder(String configurationName, Class bindableType) {
    +				return (Binder) binder;
     			}
     		};
    -		this.channelBindingServiceProperties = new ChannelBindingServiceProperties();
    +		this.bindingServiceProperties = new BindingServiceProperties();
     		Map bindings = new HashMap<>();
     		BindingProperties bindingProperties = new BindingProperties();
     		bindingProperties.setContentType("text/plain");
     		bindings.put("foo", bindingProperties);
    -		this.channelBindingServiceProperties.setBindings(bindings);
    -		ChannelBindingService channelBindingService = new ChannelBindingService(channelBindingServiceProperties,
    +		this.bindingServiceProperties.setBindings(bindings);
    +		BindingService bindingService = new BindingService(bindingServiceProperties,
     				binderFactory);
     		MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer(
    -				this.channelBindingServiceProperties,
    +				this.bindingServiceProperties,
     				new CompositeMessageConverterFactory());
     		messageConverterConfigurer.setBeanFactory(Mockito.mock(ConfigurableListableBeanFactory.class));
     		messageConverterConfigurer.afterPropertiesSet();
    -		this.bindableChannelFactory = new DefaultBindableChannelFactory(messageConverterConfigurer);
    +		this.bindingTargetFactory = new SubscribableChannelBindingTargetFactory(messageConverterConfigurer);
     		dynamicDestinationsBindable = new DynamicDestinationsBindable();
    -		this.resolver = new BinderAwareChannelResolver(channelBindingService, this.bindableChannelFactory,
    +		this.resolver = new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory,
     				dynamicDestinationsBindable);
     		this.resolver.setBeanFactory(context.getBeanFactory());
     		context.getBeanFactory().registerSingleton("channelResolver", this.resolver);
    @@ -117,7 +117,7 @@ public class BinderAwareChannelResolverTests {
     		context.registerSingleton("other", DirectChannel.class);
     		context.registerSingleton(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME,
     				DefaultMessageBuilderFactory.class);
    -		context.getBeanFactory().registerSingleton("channelBindingService", channelBindingService);
    +		context.getBeanFactory().registerSingleton("bindingService", bindingService);
     		context.registerSingleton("inputBindingLifecycle", InputBindingLifecycle.class);
     		context.registerSingleton("outputBindingLifecycle", OutputBindingLifecycle.class);
     		context.refresh();
    @@ -171,24 +171,24 @@ public class BinderAwareChannelResolverTests {
     		BindingProperties genericProperties = new BindingProperties();
     		genericProperties.setContentType("text/plain");
     		bindings.put("foo", genericProperties);
    -		this.channelBindingServiceProperties.setBindings(bindings);
    +		this.bindingServiceProperties.setBindings(bindings);
     		@SuppressWarnings("unchecked")
     		Binder binder = mock(Binder.class);
     		Binder binder2 = mock(Binder.class);
    -		BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class);
    +		BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class);
     		Binding fooBinding = Mockito.mock(Binding.class);
     		Binding barBinding = Mockito.mock(Binding.class);
     		when(binder.bindProducer(
     				matches("foo"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(fooBinding);
     		when(binder2.bindProducer(
     				matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(barBinding);
    -		when(mockBinderFactory.getBinder(null)).thenReturn(binder);
    -		when(mockBinderFactory.getBinder("someTransport")).thenReturn(binder2);
    -		ChannelBindingService channelBindingService = new ChannelBindingService(channelBindingServiceProperties,
    +		when(mockBinderFactory.getBinder(null, DirectChannel.class)).thenReturn(binder);
    +		when(mockBinderFactory.getBinder("someTransport", DirectChannel.class)).thenReturn(binder2);
    +		BindingService bindingService = new BindingService(bindingServiceProperties,
     				mockBinderFactory);
     		@SuppressWarnings("unchecked")
     		BinderAwareChannelResolver resolver =
    -				new BinderAwareChannelResolver(channelBindingService, this.bindableChannelFactory,
    +				new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory,
     						new DynamicDestinationsBindable());
     		BeanFactory beanFactory = new DefaultListableBeanFactory();
     		resolver.setBeanFactory(beanFactory);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java
    index d35f5f08a..dfeca100c 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java
    @@ -36,6 +36,7 @@ import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.context.annotation.Import;
     import org.springframework.core.io.ClassPathResource;
     import org.springframework.core.io.DefaultResourceLoader;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.util.ObjectUtils;
     
     import static org.assertj.core.api.Assertions.assertThat;
    @@ -95,10 +96,10 @@ public class BinderFactoryConfigurationTests {
     
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
    -		Binder binder1 = binderFactory.getBinder("binder1");
    +		Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).isInstanceOf(StubBinder1.class);
     
    -		Binder defaultBinder = binderFactory.getBinder(null);
    +		Binder defaultBinder = binderFactory.getBinder(null, MessageChannel.class);
     		assertThat(defaultBinder).isSameAs(binder1);
     	}
     
    @@ -109,7 +110,7 @@ public class BinderFactoryConfigurationTests {
     
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
    -		Binder binder1 = binderFactory.getBinder("binder1");
    +		Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).hasFieldOrPropertyWithValue("name", "foo");
     	}
     
    @@ -122,10 +123,10 @@ public class BinderFactoryConfigurationTests {
     
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
    -		Binder binder1 = binderFactory.getBinder("custom");
    +		Binder binder1 = binderFactory.getBinder("custom", MessageChannel.class);
     		assertThat(binder1).hasFieldOrPropertyWithValue("name", "foo");
     
    -		assertThat(binderFactory.getBinder(null)).isSameAs(binder1);
    +		assertThat(binderFactory.getBinder(null, MessageChannel.class)).isSameAs(binder1);
     	}
     
     	@Test
    @@ -138,7 +139,7 @@ public class BinderFactoryConfigurationTests {
     
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
    -		Binder binder1 = binderFactory.getBinder("custom");
    +		Binder binder1 = binderFactory.getBinder("custom", MessageChannel.class);
     		assertThat(binder1).hasFieldOrPropertyWithValue("name", null);
     	}
     
    @@ -157,18 +158,18 @@ public class BinderFactoryConfigurationTests {
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
     		try {
    -			binderFactory.getBinder(null);
    +			binderFactory.getBinder(null, MessageChannel.class);
     			fail();
     		}
     		catch (Exception e) {
     			assertThat(e).isInstanceOf(IllegalStateException.class);
     			assertThat(e.getMessage()).contains(
    -					"A default binder has been requested, but there is more than one binder available:");
    +					"A default binder has been requested, but there is more than one binder available");
     		}
     
    -		Binder binder1 = binderFactory.getBinder("binder1");
    +		Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).isInstanceOf(StubBinder1.class);
    -		Binder binder2 = binderFactory.getBinder("binder2");
    +		Binder binder2 = binderFactory.getBinder("binder2", MessageChannel.class);
     		assertThat(binder2).isInstanceOf(StubBinder2.class);
     	}
     
    @@ -190,15 +191,15 @@ public class BinderFactoryConfigurationTests {
     
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
    -		Binder defaultBinder = binderFactory.getBinder(null);
    +		Binder defaultBinder = binderFactory.getBinder(null, MessageChannel.class);
     		assertThat(defaultBinder).isInstanceOf(StubBinder1.class);
     		assertThat(((StubBinder1) defaultBinder).getName()).isNullOrEmpty();
     
    -		Binder binder1 = binderFactory.getBinder("binder1");
    +		Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).isInstanceOf(StubBinder1.class);
     		assertThat(binder1).isSameAs(defaultBinder);
     
    -		Binder custom = binderFactory.getBinder("custom");
    +		Binder custom = binderFactory.getBinder("custom", MessageChannel.class);
     		assertThat(custom).isInstanceOf(StubBinder1.class);
     		assertThat(custom).isNotSameAs(defaultBinder);
     		assertThat(((StubBinder1) custom).getName()).isEqualTo("foo");
    @@ -222,12 +223,12 @@ public class BinderFactoryConfigurationTests {
     
     		BinderFactory binderFactory = context.getBean(BinderFactory.class);
     
    -		Binder binder1 = binderFactory.getBinder("binder1");
    +		Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).isInstanceOf(StubBinder1.class);
    -		Binder binder2 = binderFactory.getBinder("binder2");
    +		Binder binder2 = binderFactory.getBinder("binder2", MessageChannel.class);
     		assertThat(binder2).isInstanceOf(StubBinder2.class);
     
    -		Binder defaultBinder = binderFactory.getBinder(null);
    +		Binder defaultBinder = binderFactory.getBinder(null, MessageChannel.class);
     		assertThat(defaultBinder).isSameAs(binder2);
     	}
     
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java
    index ec695e94b..534a809ea 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java
    @@ -43,10 +43,10 @@ public class ErrorBindingTests {
     	public void testErrorChannelNotBoundByDefault() {
     
     		ConfigurableApplicationContext applicationContext = SpringApplication.run(TestProcessor.class, "--server.port=0");
    -		BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class);
    +		BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class);
     
     		@SuppressWarnings("unchecked")
    -		Binder binder = binderFactory.getBinder(null);
    +		Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     
     		Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(ConsumerProperties.class));
     		Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(ProducerProperties.class));
    @@ -59,10 +59,10 @@ public class ErrorBindingTests {
     
     		ConfigurableApplicationContext applicationContext =
     				SpringApplication.run(TestProcessor.class, "--spring.cloud.stream.bindings.error.destination=foo", "--server.port=0");
    -		BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class);
    +		BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class, MessageChannel.class);
     
     		@SuppressWarnings("unchecked")
    -		Binder binder =  binderFactory.getBinder(null);
    +		Binder binder =  binderFactory.getBinder(null, MessageChannel.class);
     
     		MessageChannel errorChannel = applicationContext.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME,
     				MessageChannel.class);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java
    index bbcc90b2d..829fbe1df 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java
    @@ -30,14 +30,14 @@ import org.mockito.Mockito;
     
     import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
     import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
    -import org.springframework.cloud.stream.binding.ChannelBindingService;
    -import org.springframework.cloud.stream.binding.DefaultBindableChannelFactory;
    +import org.springframework.cloud.stream.binding.BindingService;
     import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
     import org.springframework.cloud.stream.binding.InputBindingLifecycle;
     import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
     import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
    +import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
     import org.springframework.cloud.stream.config.BindingProperties;
    -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
    +import org.springframework.cloud.stream.config.BindingServiceProperties;
     import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
     import org.springframework.integration.channel.DirectChannel;
     import org.springframework.integration.support.DefaultMessageBuilderFactory;
    @@ -68,29 +68,29 @@ public class ExtendedPropertiesBinderAwareChannelResolverTests extends BinderAwa
     	public void setupContext() throws Exception {
     		producerBindings = new ArrayList<>();
     		this.binder = new TestBinder();
    -		BinderFactory binderFactory = new BinderFactory() {
    +		BinderFactory binderFactory = new BinderFactory() {
     
     			@Override
    -			public ExtendedPropertiesBinder getBinder(String configurationName) {
    -				return binder;
    +			public  Binder getBinder(String configurationName, Class bindableType) {
    +				return (Binder) binder;
     			}
     		};
    -		this.channelBindingServiceProperties = new ChannelBindingServiceProperties();
    +		this.bindingServiceProperties = new BindingServiceProperties();
     		Map bindings = new HashMap();
     		BindingProperties bindingProperties = new BindingProperties();
     		bindingProperties.setContentType("text/plain");
     		bindings.put("foo", bindingProperties);
    -		this.channelBindingServiceProperties.setBindings(bindings);
    +		this.bindingServiceProperties.setBindings(bindings);
     		MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer(
    -				this.channelBindingServiceProperties,
    +				this.bindingServiceProperties,
     				new CompositeMessageConverterFactory());
     		messageConverterConfigurer.setBeanFactory(Mockito.mock(ConfigurableListableBeanFactory.class));
     		messageConverterConfigurer.afterPropertiesSet();
    -		this.bindableChannelFactory = new DefaultBindableChannelFactory(messageConverterConfigurer);
    +		this.bindingTargetFactory = new SubscribableChannelBindingTargetFactory(messageConverterConfigurer);
     		dynamicDestinationsBindable = new DynamicDestinationsBindable();
    -		ChannelBindingService channelBindingService = new ChannelBindingService(channelBindingServiceProperties,
    +		BindingService bindingService = new BindingService(bindingServiceProperties,
     				binderFactory);
    -		this.resolver = new BinderAwareChannelResolver(channelBindingService, this.bindableChannelFactory,
    +		this.resolver = new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory,
     				dynamicDestinationsBindable);
     		this.resolver.setBeanFactory(context.getBeanFactory());
     		context.getBeanFactory().registerSingleton("channelResolver", this.resolver);
    @@ -98,7 +98,7 @@ public class ExtendedPropertiesBinderAwareChannelResolverTests extends BinderAwa
     		context.registerSingleton("other", DirectChannel.class);
     		context.registerSingleton(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME,
     				DefaultMessageBuilderFactory.class);
    -		context.getBeanFactory().registerSingleton("channelBindingService", channelBindingService);
    +		context.getBeanFactory().registerSingleton("bindingService", bindingService);
     		context.registerSingleton("inputBindingLifecycle", InputBindingLifecycle.class);
     		context.registerSingleton("outputBindingLifecycle", OutputBindingLifecycle.class);
     		context.refresh();
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/HealthIndicatorsConfigurationTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/HealthIndicatorsConfigurationTests.java
    index 49346c70f..dd43515ed 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/HealthIndicatorsConfigurationTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/HealthIndicatorsConfigurationTests.java
    @@ -39,6 +39,7 @@ import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
     import org.springframework.core.io.ClassPathResource;
     import org.springframework.core.io.DefaultResourceLoader;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.util.ObjectUtils;
     
     import static org.assertj.core.api.Assertions.assertThat;
    @@ -54,9 +55,9 @@ public class HealthIndicatorsConfigurationTests {
     	public void healthIndicatorsCheck() throws Exception {
     		ConfigurableApplicationContext context = createBinderTestContext(new String[] { "binder1", "binder2" },
     				"spring.cloud.stream.defaultBinder:binder2");
    -		Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1");
    +		Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).isInstanceOf(StubBinder1.class);
    -		Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2");
    +		Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2", MessageChannel.class);
     		assertThat(binder2).isInstanceOf(StubBinder2.class);
     		CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
     				CompositeHealthIndicator.class);
    @@ -81,9 +82,9 @@ public class HealthIndicatorsConfigurationTests {
     				"spring.cloud.stream.defaultBinder:binder2",
     				"management.health.binders.enabled:false");
     
    -		Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1");
    +		Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1", MessageChannel.class);
     		assertThat(binder1).isInstanceOf(StubBinder1.class);
    -		Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2");
    +		Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2", MessageChannel.class);
     		assertThat(binder2).isInstanceOf(StubBinder2.class);
     		try {
     			context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java
    index 01e8f400f..ace976474 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java
    @@ -30,6 +30,7 @@ import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.context.SmartLifecycle;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Import;
    +import org.springframework.messaging.MessageChannel;
     
     import static org.assertj.core.api.Assertions.assertThat;
     import static org.mockito.Matchers.anyString;
    @@ -48,7 +49,7 @@ public class InputOutputBindingOrderTest {
     	public void testInputOutputBindingOrder() {
     		ConfigurableApplicationContext applicationContext = SpringApplication.run(TestSource.class, "--server.port=-1");
     		@SuppressWarnings("rawtypes")
    -		Binder binder = applicationContext.getBean(BinderFactory.class).getBinder(null);
    +		Binder binder = applicationContext.getBean(BinderFactory.class).getBinder(null, MessageChannel.class);
     		Processor processor = applicationContext.getBean(Processor.class);
     		// input is bound after the context has been started
     		verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.any());
    @@ -84,7 +85,7 @@ public class InputOutputBindingOrderTest {
     		@Override
     		@SuppressWarnings("unchecked")
     		public synchronized void start() {
    -			Binder binder = this.binderFactory.getBinder(null);
    +			Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
     			verify(binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.any());
     			// input was not bound yet
     			verifyNoMoreInteractions(binder);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingWithBindingTargetsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingWithBindingTargetsTests.java
    index 269668cb0..c3fc52214 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingWithBindingTargetsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingWithBindingTargetsTests.java
    @@ -28,6 +28,7 @@ import org.springframework.cloud.stream.messaging.Processor;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
     import org.springframework.context.annotation.PropertySource;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.anyString;
    @@ -51,7 +52,7 @@ public class ProcessorBindingWithBindingTargetsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testSourceOutputChannelBound() {
    -		final Binder binder = binderFactory.getBinder(null);
    +		final Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindConsumer(eq("testtock.0"), anyString(),
     				eq(this.testProcessor.input()), Mockito.any());
     		verify(binder).bindProducer(eq("testtock.1"), eq(this.testProcessor.output()),
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingsWithDefaultsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingsWithDefaultsTests.java
    index b15ce4380..bc4b154c3 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingsWithDefaultsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingsWithDefaultsTests.java
    @@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
     import org.springframework.cloud.stream.messaging.Processor;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.anyString;
    @@ -50,7 +51,7 @@ public class ProcessorBindingsWithDefaultsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testSourceOutputChannelBound() {
    -		Binder binder = this.binderFactory.getBinder(null);
    +		Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
     		Mockito.verify(binder).bindConsumer(eq("input"), anyString(), eq(this.processor.input()),
     				Mockito.any());
     		Mockito.verify(binder).bindProducer(eq("output"), eq(this.processor.output()),
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultTargetsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultTargetsTests.java
    index dc7986792..2d7d1b550 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultTargetsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultTargetsTests.java
    @@ -28,6 +28,7 @@ import org.springframework.cloud.stream.messaging.Sink;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
     import org.springframework.context.annotation.PropertySource;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.anyString;
    @@ -52,7 +53,7 @@ public class SinkBindingWithDefaultTargetsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testSourceOutputChannelBound() {
    -		Binder binder = binderFactory.getBinder(null);
    +		Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindConsumer(eq("testtock"), anyString(), eq(this.testSink.input()),
     				Mockito.any());
     		verifyNoMoreInteractions(binder);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultsTests.java
    index 5e6984ece..1ce4a62fb 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingWithDefaultsTests.java
    @@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
     import org.springframework.cloud.stream.messaging.Sink;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.anyString;
    @@ -51,7 +52,7 @@ public class SinkBindingWithDefaultsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testSourceOutputChannelBound() {
    -		Binder binder = binderFactory.getBinder(null);
    +		Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindConsumer(eq("input"), anyString(), eq(this.testSink.input()),
     				Mockito.any());
     		verifyNoMoreInteractions(binder);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithBindingTargetsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithBindingTargetsTests.java
    index c14fd0ca8..17a2739f6 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithBindingTargetsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithBindingTargetsTests.java
    @@ -31,6 +31,7 @@ import org.springframework.context.annotation.Import;
     import org.springframework.context.annotation.PropertySource;
     import org.springframework.integration.channel.PublishSubscribeChannel;
     import org.springframework.integration.context.IntegrationContextUtils;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.eq;
    @@ -59,7 +60,7 @@ public class SourceBindingWithBindingTargetsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testSourceOutputChannelBound() {
    -		Binder binder = binderFactory.getBinder(null);
    +		Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindProducer(eq("testtock"), eq(this.testSource.output()),
     				Mockito.any());
     		verifyNoMoreInteractions(binder);
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithDefaultsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithDefaultsTests.java
    index b04ac6930..1e3d00519 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithDefaultsTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingWithDefaultsTests.java
    @@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
     import org.springframework.cloud.stream.messaging.Source;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.mockito.Matchers.eq;
    @@ -50,7 +51,7 @@ public class SourceBindingWithDefaultsTests {
     	@SuppressWarnings("unchecked")
     	@Test
     	public void testSourceOutputChannelBound() {
    -		Binder binder = binderFactory.getBinder(null);
    +		Binder binder = binderFactory.getBinder(null, MessageChannel.class);
     		verify(binder).bindProducer(eq("output"), eq(this.testSource.output()), Mockito.any());
     		verifyNoMoreInteractions(binder);
     	}
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java
    similarity index 79%
    rename from spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java
    rename to spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java
    index d5b5747fc..8df37b722 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java
    @@ -39,8 +39,9 @@ import org.springframework.cloud.stream.binder.ConsumerProperties;
     import org.springframework.cloud.stream.binder.DefaultBinderFactory;
     import org.springframework.cloud.stream.binder.ProducerProperties;
     import org.springframework.cloud.stream.config.BindingProperties;
    -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
    +import org.springframework.cloud.stream.config.BindingServiceProperties;
     import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
    +import org.springframework.cloud.stream.reflection.GenericsUtils;
     import org.springframework.cloud.stream.utils.MockBinderConfiguration;
     import org.springframework.integration.channel.DirectChannel;
     import org.springframework.messaging.MessageChannel;
    @@ -64,23 +65,23 @@ import static org.mockito.Mockito.when;
      * @author Marius Bogoevici
      * @author Ilayaperumal Gopinathan
      */
    -public class ChannelBindingServiceTests {
    +public class BindingServiceTests {
     
     	@Test
     	public void testDefaultGroup() throws Exception {
    -		ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
    +		BindingServiceProperties properties = new BindingServiceProperties();
     		Map bindingProperties = new HashMap<>();
     		BindingProperties props = new BindingProperties();
     		props.setDestination("foo");
     		final String inputChannelName = "input";
     		bindingProperties.put(inputChannelName, props);
     		properties.setBindings(bindingProperties);
    -		DefaultBinderFactory binderFactory =
    -				new DefaultBinderFactory<>(Collections.singletonMap("mock",
    +		DefaultBinderFactory binderFactory =
    +				new DefaultBinderFactory(Collections.singletonMap("mock",
     						new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
     								new Properties(), true, true)));
    -		Binder binder = binderFactory.getBinder("mock");
    -		ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
    +		Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
    +		BindingService service = new BindingService(properties, binderFactory);
     		MessageChannel inputChannel = new DirectChannel();
     		@SuppressWarnings("unchecked")
     		Binding mockBinding = Mockito.mock(Binding.class);
    @@ -100,7 +101,7 @@ public class ChannelBindingServiceTests {
     
     	@Test
     	public void testMultipleConsumerBindings() throws Exception {
    -		ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
    +		BindingServiceProperties properties = new BindingServiceProperties();
     		Map bindingProperties = new HashMap<>();
     		BindingProperties props = new BindingProperties();
     		props.setDestination("foo,bar");
    @@ -109,12 +110,12 @@ public class ChannelBindingServiceTests {
     
     		properties.setBindings(bindingProperties);
     
    -		DefaultBinderFactory binderFactory = new DefaultBinderFactory<>(Collections.singletonMap("mock",
    +		DefaultBinderFactory binderFactory = new DefaultBinderFactory(Collections.singletonMap("mock",
     				new BinderConfiguration(new BinderType("mock", new Class[] { MockBinderConfiguration.class }),
     						new Properties(), true, true)));
     
    -		Binder binder = binderFactory.getBinder("mock");
    -		ChannelBindingService service = new ChannelBindingService(properties,
    +		Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
    +		BindingService service = new BindingService(properties,
     				binderFactory);
     		MessageChannel inputChannel = new DirectChannel();
     
    @@ -153,7 +154,7 @@ public class ChannelBindingServiceTests {
     
     	@Test
     	public void testExplicitGroup() throws Exception {
    -		ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
    +		BindingServiceProperties properties = new BindingServiceProperties();
     		Map bindingProperties = new HashMap<>();
     		BindingProperties props = new BindingProperties();
     		props.setDestination("foo");
    @@ -161,7 +162,7 @@ public class ChannelBindingServiceTests {
     		final String inputChannelName = "input";
     		bindingProperties.put(inputChannelName, props);
     		properties.setBindings(bindingProperties);
    -		DefaultBinderFactory binderFactory = new DefaultBinderFactory<>(
    +		DefaultBinderFactory binderFactory = new DefaultBinderFactory(
     				Collections
     						.singletonMap("mock",
     								new BinderConfiguration(
    @@ -169,8 +170,8 @@ public class ChannelBindingServiceTests {
     												new Class[] {
     														MockBinderConfiguration.class }),
     										new Properties(), true, true)));
    -		Binder binder = binderFactory.getBinder("mock");
    -		ChannelBindingService service = new ChannelBindingService(properties,
    +		Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
    +		BindingService service = new BindingService(properties,
     				binderFactory);
     		MessageChannel inputChannel = new DirectChannel();
     		@SuppressWarnings("unchecked")
    @@ -192,8 +193,8 @@ public class ChannelBindingServiceTests {
     
     	@Test
     	public void checkDynamicBinding() {
    -		ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
    -		DefaultBinderFactory binderFactory = new DefaultBinderFactory<>(
    +		BindingServiceProperties properties = new BindingServiceProperties();
    +		DefaultBinderFactory binderFactory = new DefaultBinderFactory(
     				Collections
     						.singletonMap("mock",
     								new BinderConfiguration(
    @@ -201,19 +202,20 @@ public class ChannelBindingServiceTests {
     												new Class[] {
     														MockBinderConfiguration.class }),
     										new Properties(), true, true)));
    -		Binder binder = binderFactory.getBinder("mock");
    +		Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
     		@SuppressWarnings("unchecked")
     		Binding mockBinding = Mockito.mock(Binding.class);
     		@SuppressWarnings("unchecked")
     		final AtomicReference dynamic = new AtomicReference<>();
     		when(binder.bindProducer(matches("foo"), any(DirectChannel.class),
     				any(ProducerProperties.class))).thenReturn(mockBinding);
    -		ChannelBindingService channelBindingService = new ChannelBindingService(properties, binderFactory);
    +		BindingService bindingService = new BindingService(properties, binderFactory);
    +		SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory(
    +				new MessageConverterConfigurer(properties,
    +						new CompositeMessageConverterFactory()));
     		BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(
    -				channelBindingService,
    -				new DefaultBindableChannelFactory(new MessageConverterConfigurer(
    -						properties,
    -						new CompositeMessageConverterFactory())), new DynamicDestinationsBindable());
    +				bindingService, bindableSubscribableChannelFactory,
    +				new DynamicDestinationsBindable());
     		ConfigurableListableBeanFactory beanFactory = mock(
     				ConfigurableListableBeanFactory.class);
     		when(beanFactory.getBean("foo", MessageChannel.class))
    @@ -257,7 +259,7 @@ public class ChannelBindingServiceTests {
     
     	@Test
     	public void testProducerPropertiesValidation() {
    -		ChannelBindingServiceProperties serviceProperties = new ChannelBindingServiceProperties();
    +		BindingServiceProperties serviceProperties = new BindingServiceProperties();
     		Map bindingProperties = new HashMap<>();
     		BindingProperties props = new BindingProperties();
     		ProducerProperties producerProperties = new ProducerProperties();
    @@ -267,11 +269,11 @@ public class ChannelBindingServiceTests {
     		final String outputChannelName = "output";
     		bindingProperties.put(outputChannelName, props);
     		serviceProperties.setBindings(bindingProperties);
    -		DefaultBinderFactory binderFactory =
    -				new DefaultBinderFactory<>(Collections.singletonMap("mock",
    +		DefaultBinderFactory binderFactory =
    +				new DefaultBinderFactory(Collections.singletonMap("mock",
     						new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
     								new Properties(), true, true)));
    -		ChannelBindingService service = new ChannelBindingService(serviceProperties, binderFactory);
    +		BindingService service = new BindingService(serviceProperties, binderFactory);
     		MessageChannel outputChannel = new DirectChannel();
     		try {
     			service.bindProducer(outputChannel, outputChannelName);
    @@ -284,7 +286,7 @@ public class ChannelBindingServiceTests {
     
     	@Test
     	public void testConsumerPropertiesValidation() {
    -		ChannelBindingServiceProperties serviceProperties = new ChannelBindingServiceProperties();
    +		BindingServiceProperties serviceProperties = new BindingServiceProperties();
     		Map bindingProperties = new HashMap<>();
     		BindingProperties props = new BindingProperties();
     		ConsumerProperties consumerProperties = new ConsumerProperties();
    @@ -294,10 +296,10 @@ public class ChannelBindingServiceTests {
     		final String inputChannelName = "input";
     		bindingProperties.put(inputChannelName, props);
     		serviceProperties.setBindings(bindingProperties);
    -		DefaultBinderFactory binderFactory = new DefaultBinderFactory<>(Collections.singletonMap("mock",
    +		DefaultBinderFactory binderFactory = new DefaultBinderFactory(Collections.singletonMap("mock",
     				new BinderConfiguration(new BinderType("mock", new Class[] { MockBinderConfiguration.class }),
     						new Properties(), true, true)));
    -		ChannelBindingService service = new ChannelBindingService(serviceProperties,
    +		BindingService service = new BindingService(serviceProperties,
     				binderFactory);
     		MessageChannel inputChannel = new DirectChannel();
     		try {
    @@ -308,4 +310,30 @@ public class ChannelBindingServiceTests {
     			assertThat(e).hasMessageContaining("Concurrency should be greater than zero.");
     		}
     	}
    +	
    +	@Test
    +	public void testResolveBindableType() {
    +		Class bindableType = GenericsUtils.getParameterType(FooBinder.class, Binder.class, 0);
    +		assertThat(bindableType).isSameAs(SomeBindableType.class);
    +	}
    +	
    +	public static class FooBinder
    +			implements Binder {
    +		@Override
    +		public Binding bindConsumer(String name, String group,
    +				SomeBindableType inboundBindTarget,
    +				ConsumerProperties consumerProperties) {
    +			throw new UnsupportedOperationException();
    +		}
    +
    +		@Override
    +		public Binding bindProducer(String name,
    +				SomeBindableType outboundBindTarget,
    +				ProducerProperties producerProperties) {
    +			throw new UnsupportedOperationException();
    +		}
    +	}
    +	
    +	public static class SomeBindableType {
    +	}
     }
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java
    index 451c68567..2e1e7a2ba 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java
    @@ -35,6 +35,7 @@ import org.springframework.cloud.stream.messaging.Sink;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
     import org.springframework.context.annotation.PropertySource;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.hamcrest.Matchers.equalTo;
    @@ -62,7 +63,7 @@ public class PartitionedConsumerTest {
     	@Test
     	@SuppressWarnings("unchecked")
     	public void testBindingPartitionedConsumer() {
    -		Binder binder = this.binderFactory.getBinder(null);
    +		Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
     		ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ConsumerProperties.class);
     		verify(binder).bindConsumer(eq("partIn"), anyString(), eq(this.testSink.input()),
     				argumentCaptor.capture());
    diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java
    index 68c2e068a..d57efa63f 100644
    --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java
    +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java
    @@ -33,6 +33,7 @@ import org.springframework.cloud.stream.messaging.Source;
     import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
     import org.springframework.context.annotation.Import;
     import org.springframework.context.annotation.PropertySource;
    +import org.springframework.messaging.MessageChannel;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     
     import static org.hamcrest.Matchers.equalTo;
    @@ -59,7 +60,7 @@ public class PartitionedProducerTest {
     	@Test
     	@SuppressWarnings("unchecked")
     	public void testBindingPartitionedProducer() {
    -		Binder binder = this.binderFactory.getBinder(null);
    +		Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
     		ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ProducerProperties.class);
     		verify(binder).bindProducer(eq("partOut"), eq(this.testSource.output()), argumentCaptor.capture());
     		Assert.assertThat(argumentCaptor.getValue().getPartitionCount(), equalTo(3));