Additional refactoring

- Rename `BindingFactoryListener` to `Listener` and reduce
  its visibility, making it effective only when used with
  a `DefaultBinderFactory` implementation.
- Rename a couple methods
- Make `ChannelEndpointConfiguration` an autoconfiguration
  in its own right
This commit is contained in:
Marius Bogoevici
2017-04-03 22:20:51 -04:00
parent a309fa772e
commit e7fec479ee
9 changed files with 153 additions and 181 deletions

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2017 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.binder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* This interface adds additional capabilities to the binder when it is created.
*
* @author Ilayaperumal Gopinathan
*/
public interface BinderFactoryListener {
/**
* Applying additional capabilities to the binder when creating the new binder instance.
* @param configurationName the binder configuration name
* @param binderProducingContext the application context of the binder
*/
void apply(String configurationName, ConfigurableApplicationContext binderProducingContext);
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2017 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.binder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Ilayaperumal Gopinathan
*/
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.HealthIndicator")
@ConditionalOnEnabledHealthIndicator("binders")
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@Configuration
public class BindersHealthIndicatorAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "bindersHealthIndicator")
public CompositeHealthIndicator bindersHealthIndicator() {
return new CompositeHealthIndicator(new OrderedHealthAggregator());
}
@Bean
public BinderFactoryListener bindersHealthIndicatorListener(@Qualifier("bindersHealthIndicator") CompositeHealthIndicator compositeHealthIndicator) {
return new BindersHealthIndicatorListener(compositeHealthIndicator);
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2017 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.binder;
import java.util.Map;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.context.ConfigurableApplicationContext;
/**
* {@link BinderFactoryListener} that provides {@link HealthIndicator} support.
*
* @author Ilayaperumal Gopinathan
*/
public class BindersHealthIndicatorListener implements BinderFactoryListener {
private final CompositeHealthIndicator bindersHealthIndicator;
public BindersHealthIndicatorListener(CompositeHealthIndicator bindersHealthIndicator) {
this.bindersHealthIndicator = bindersHealthIndicator;
}
@Override
public void apply(String binderConfigurationName, ConfigurableApplicationContext binderProducingContext) {
if (this.bindersHealthIndicator != null) {
OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
Map<String, HealthIndicator> indicators = binderProducingContext.getBeansOfType(HealthIndicator.class);
// if there are no health indicators in the child context, we just mark the binder's health as unknown
// this can happen due to the fact that configuration is inherited
HealthIndicator binderHealthIndicator =
indicators.isEmpty() ? new DefaultHealthIndicator() : new CompositeHealthIndicator(
healthAggregator, indicators);
this.bindersHealthIndicator.addHealthIndicator(binderConfigurationName, binderHealthIndicator);
}
}
private static class DefaultHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.unknown();
}
}
}

View File

@@ -55,7 +55,7 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
private Map<String, String> defaultBinderForBindingTargetType = new HashMap<>();
private Collection<BinderFactoryListener> binderFactoryListeners;
private Collection<Listener> listeners;
private volatile String defaultBinder;
@@ -73,8 +73,8 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
this.defaultBinder = defaultBinder;
}
public void setBinderFactoryListeners(Collection<BinderFactoryListener> binderFactoryListeners) {
this.binderFactoryListeners = binderFactoryListeners;
public void setListeners(Collection<Listener> listeners) {
this.listeners = listeners;
}
@Override
@@ -112,7 +112,8 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
List<String> candidatesForBindableType = new ArrayList<>();
for (String defaultCandidateConfiguration : defaultCandidateConfigurations) {
Binder<Object, ?, ?> binderInstance = getBinderInstance(defaultCandidateConfiguration);
Class<?> binderType = GenericsUtils.getParameterType(binderInstance.getClass(), Binder.class, 0);
Class<?> binderType = GenericsUtils.getParameterType(binderInstance.getClass(),
Binder.class, 0);
if (binderType.isAssignableFrom(bindingTargetType)) {
candidatesForBindableType.add(defaultCandidateConfiguration);
}
@@ -129,8 +130,8 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
+ ", 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 + "': "
throw new IllegalStateException("A default binder has been requested, but none of the "
+ "registered binders can bind a '" + bindingTargetType + "': "
+ StringUtils.collectionToCommaDelimitedString(defaultCandidateConfigurations));
}
}
@@ -163,12 +164,14 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
throw new IllegalStateException("Unknown binder configuration: " + configurationName);
}
Properties binderProperties = binderConfiguration.getProperties();
// Convert all properties to arguments, so that they receive maximum precedence
// Convert all properties to arguments, so that they receive maximum
// precedence
ArrayList<String> args = new ArrayList<>();
for (Map.Entry<Object, Object> property : binderProperties.entrySet()) {
args.add(String.format("--%s=%s", property.getKey(), property.getValue()));
}
// Initialize the domain with a unique name based on the bootstrapping context setting
// Initialize the domain with a unique name based on the bootstrapping context
// setting
ConfigurableEnvironment environment = this.context != null ? this.context.getEnvironment() : null;
String defaultDomain = environment != null ? environment.getProperty("spring.jmx.default-domain") : null;
if (defaultDomain == null) {
@@ -179,17 +182,16 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
}
args.add("--spring.jmx.default-domain=" + defaultDomain + "binder." + configurationName);
args.add("--spring.main.applicationContextClass=" + AnnotationConfigApplicationContext.class.getName());
List<Class<?>> configurationClasses =
new ArrayList<Class<?>>(
Arrays.asList(binderConfiguration.getBinderType().getConfigurationClasses()));
SpringApplicationBuilder springApplicationBuilder =
new SpringApplicationBuilder()
.sources(configurationClasses.toArray(new Class<?>[]{}))
.bannerMode(Mode.OFF)
.web(false);
// If the environment is not customized and a main context is available, we will set the latter as parent.
// This ensures that the defaults and user-defined customizations (e.g. custom connection factory beans)
// are propagated to the binder context. If the environment is customized, then the binder context should
List<Class<?>> configurationClasses = new ArrayList<Class<?>>(
Arrays.asList(binderConfiguration.getBinderType().getConfigurationClasses()));
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder()
.sources(configurationClasses.toArray(new Class<?>[] {})).bannerMode(Mode.OFF).web(false);
// If the environment is not customized and a main context is available, we
// will set the latter as parent.
// This ensures that the defaults and user-defined customizations (e.g. custom
// connection factory beans)
// are propagated to the binder context. If the environment is customized,
// then the binder context should
// not inherit any beans from the parent
boolean useApplicationContextAsParent = binderProperties.isEmpty() && this.context != null;
if (useApplicationContextAsParent) {
@@ -202,23 +204,23 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
springApplicationBuilder.environment(binderEnvironment);
}
}
ConfigurableApplicationContext binderProducingContext =
springApplicationBuilder.run(args.toArray(new String[args.size()]));
ConfigurableApplicationContext binderProducingContext = springApplicationBuilder
.run(args.toArray(new String[args.size()]));
@SuppressWarnings("unchecked")
Binder<T, ?, ?> binder = binderProducingContext.getBean(Binder.class);
if (this.binderFactoryListeners != null) {
for (BinderFactoryListener binderFactoryListener : binderFactoryListeners) {
binderFactoryListener.apply(configurationName, binderProducingContext);
if (this.listeners != null) {
for (Listener binderFactoryListener : listeners) {
binderFactoryListener.afterBinderContextInitialized(configurationName, binderProducingContext);
}
}
this.binderInstanceCache.put(configurationName, new BinderInstanceHolder(binder,
binderProducingContext));
this.binderInstanceCache.put(configurationName, new BinderInstanceHolder(binder, binderProducingContext));
}
return (Binder<T, ?, ?>) this.binderInstanceCache.get(configurationName).getBinderInstance();
}
/**
* Utility class for storing {@link Binder} instances, along with their associated contexts.
* Utility class for storing {@link Binder} instances, along with their associated
* contexts.
*/
private static final class BinderInstanceHolder {
@@ -239,4 +241,23 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
return this.binderContext;
}
}
/**
* A listener that can be registered with the {@link DefaultBinderFactory} that
* allows the registration of additional configuration.
*
* @author Ilayaperumal Gopinathan
*/
public interface Listener {
/**
* Applying additional capabilities to the binder after the binder context
* has been initialized.
*
* @param configurationName the binder configuration name
* @param binderContext the application context of the binder
*/
void afterBinderContextInitialized(String configurationName,
ConfigurableApplicationContext binderContext);
}
}

View File

@@ -33,7 +33,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.stream.binder.BinderConfiguration;
import org.springframework.cloud.stream.binder.BinderFactory;
import org.springframework.cloud.stream.binder.BinderFactoryListener;
import org.springframework.cloud.stream.binder.BinderType;
import org.springframework.cloud.stream.binder.BinderTypeRegistry;
import org.springframework.cloud.stream.binder.DefaultBinderFactory;
@@ -71,14 +70,14 @@ public class BinderFactoryConfiguration {
private BindingServiceProperties bindingServiceProperties;
@Autowired(required = false)
private Collection<BinderFactoryListener> binderFactoryListeners;
private Collection<DefaultBinderFactory.Listener> binderFactoryListeners;
@Bean
@ConditionalOnMissingBean(BinderFactory.class)
public BinderFactory binderFactory() {
public DefaultBinderFactory binderFactory() {
DefaultBinderFactory binderFactory = new DefaultBinderFactory(getBinderConfigurations());
binderFactory.setDefaultBinder(bindingServiceProperties.getDefaultBinder());
binderFactory.setBinderFactoryListeners(binderFactoryListeners);
binderFactory.setListeners(binderFactoryListeners);
return binderFactory;
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2017 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.Map;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
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.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.stream.binder.DefaultBinderFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Ilayaperumal Gopinathan
*/
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.HealthIndicator")
@ConditionalOnEnabledHealthIndicator("binders")
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@ConditionalOnBean(DefaultBinderFactory.class)
@Configuration
public class BindersHealthIndicatorAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "bindersHealthIndicator")
public CompositeHealthIndicator bindersHealthIndicator() {
return new CompositeHealthIndicator(new OrderedHealthAggregator());
}
@Bean
public DefaultBinderFactory.Listener bindersHealthIndicatorListener(
@Qualifier("bindersHealthIndicator") CompositeHealthIndicator compositeHealthIndicator) {
return new BindersHealthIndicatorListener(compositeHealthIndicator);
}
/**
* A {@link DefaultBinderFactory.Listener} that provides {@link HealthIndicator} support.
*
* @author Ilayaperumal Gopinathan
*/
private static class BindersHealthIndicatorListener implements DefaultBinderFactory.Listener {
private final CompositeHealthIndicator bindersHealthIndicator;
BindersHealthIndicatorListener(CompositeHealthIndicator bindersHealthIndicator) {
this.bindersHealthIndicator = bindersHealthIndicator;
}
@Override
public void afterBinderContextInitialized(String binderConfigurationName, ConfigurableApplicationContext binderContext) {
if (this.bindersHealthIndicator != null) {
OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
Map<String, HealthIndicator> indicators = binderContext.getBeansOfType(HealthIndicator.class);
// if there are no health indicators in the child context, we just mark
// the binder's health as unknown
// this can happen due to the fact that configuration is inherited
HealthIndicator binderHealthIndicator = indicators.isEmpty() ? new DefaultHealthIndicator()
: new CompositeHealthIndicator(healthAggregator, indicators);
this.bindersHealthIndicator.addHealthIndicator(binderConfigurationName, binderHealthIndicator);
}
}
private static class DefaultHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.unknown();
}
}
}
}

View File

@@ -23,7 +23,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.stream.binding.BindingService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.messaging.MessageChannel;
@@ -38,7 +37,6 @@ import org.springframework.messaging.MessageChannel;
@Configuration
@ConditionalOnBean(BindingService.class)
@EnableConfigurationProperties(DefaultPollerProperties.class)
@Import(ChannelsEndpointConfiguration.class)
public class ChannelBindingAutoConfiguration {
@Autowired

View File

@@ -21,8 +21,10 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.stream.binding.Bindable;
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;
@@ -34,8 +36,9 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.Endpoint")
@ConditionalOnBean(BindingService.class)
@AutoConfigureAfter(EndpointAutoConfiguration.class)
public class ChannelsEndpointConfiguration {
public class ChannelsEndpointAutoConfiguration {
@Autowired(required = false)
private List<Bindable> adapters;

View File

@@ -1,3 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration,\
org.springframework.cloud.stream.binder.BindersHealthIndicatorAutoConfiguration
org.springframework.cloud.stream.config.BindersHealthIndicatorAutoConfiguration,\
org.springframework.cloud.stream.config.ChannelsEndpointAutoConfiguration