Make spring-boot actuator as optional dependency

- BinderFactoryConfiguration has a conditional configuration that creates `BinderFactory` with binder health indicators only if the spring-boot `actuator` is in classpath
 - Move Channels endpoint configuration into a separate conditional configuration class

Resolves #869

Use listener approach instead of subclassing

Use auto-configuration for binders healthIndicator configuration
This commit is contained in:
Ilayaperumal Gopinathan
2017-03-24 17:43:12 +05:30
committed by Marius Bogoevici
parent 9ea4ff324c
commit a309fa772e
10 changed files with 251 additions and 77 deletions

View File

@@ -0,0 +1,34 @@
/*
* 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

@@ -0,0 +1,49 @@
/*
* 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

@@ -0,0 +1,63 @@
/*
* 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

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-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.
@@ -18,6 +18,7 @@ package org.springframework.cloud.stream.binder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -26,14 +27,7 @@ import java.util.Properties;
import java.util.Set;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.Banner.Mode;
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.builder.SpringApplicationBuilder;
import org.springframework.cloud.stream.reflection.GenericsUtils;
import org.springframework.context.ApplicationContext;
@@ -47,7 +41,9 @@ import org.springframework.util.StringUtils;
/**
* Default {@link BinderFactory} implementation.
*
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
*/
public class DefaultBinderFactory implements BinderFactory, DisposableBean, ApplicationContextAware {
@@ -59,9 +55,9 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
private Map<String, String> defaultBinderForBindingTargetType = new HashMap<>();
private volatile String defaultBinder;
private Collection<BinderFactoryListener> binderFactoryListeners;
private volatile CompositeHealthIndicator bindersHealthIndicator;
private volatile String defaultBinder;
public DefaultBinderFactory(Map<String, BinderConfiguration> binderConfigurations) {
this.binderConfigurations = new HashMap<>(binderConfigurations);
@@ -73,16 +69,14 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
this.context = (ConfigurableApplicationContext) applicationContext;
}
@Autowired(required = false)
@Qualifier("bindersHealthIndicator")
public void setBindersHealthIndicator(CompositeHealthIndicator bindersHealthIndicator) {
this.bindersHealthIndicator = bindersHealthIndicator;
}
public void setDefaultBinder(String defaultBinder) {
this.defaultBinder = defaultBinder;
}
public void setBinderFactoryListeners(Collection<BinderFactoryListener> binderFactoryListeners) {
this.binderFactoryListeners = binderFactoryListeners;
}
@Override
public void destroy() throws Exception {
for (Map.Entry<String, BinderInstanceHolder> entry : this.binderInstanceCache.entrySet()) {
@@ -126,13 +120,15 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
if (candidatesForBindableType.size() == 1) {
configurationName = candidatesForBindableType.iterator().next();
this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
} else if (candidatesForBindableType.size() > 1) {
}
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 {
}
else {
throw new IllegalStateException("A default binder has been requested, but none of the " +
"registered binders can bind a '" + bindingTargetType + "': "
+ StringUtils.collectionToCommaDelimitedString(defaultCandidateConfigurations));
@@ -188,7 +184,7 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
Arrays.asList(binderConfiguration.getBinderType().getConfigurationClasses()));
SpringApplicationBuilder springApplicationBuilder =
new SpringApplicationBuilder()
.sources(configurationClasses.toArray(new Class<?>[] {}))
.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.
@@ -210,15 +206,10 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
springApplicationBuilder.run(args.toArray(new String[args.size()]));
@SuppressWarnings("unchecked")
Binder<T, ?, ?> binder = binderProducingContext.getBean(Binder.class);
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(configurationName, binderHealthIndicator);
if (this.binderFactoryListeners != null) {
for (BinderFactoryListener binderFactoryListener : binderFactoryListeners) {
binderFactoryListener.apply(configurationName, binderProducingContext);
}
}
this.binderInstanceCache.put(configurationName, new BinderInstanceHolder(binder,
binderProducingContext));
@@ -248,12 +239,4 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean, Appl
return this.binderContext;
}
}
private static class DefaultHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.unknown();
}
}
}

View File

@@ -152,12 +152,13 @@ public class BindingService {
@SuppressWarnings("unchecked")
private <T> Binder<T, ?, ?> getBinder(String channelName, Class<T> bindableType) {
String transport = this.bindingServiceProperties.getBinder(channelName);
return binderFactory.getBinder(transport, bindableType);
String binderConfigurationName = this.bindingServiceProperties.getBinder(channelName);
return binderFactory.getBinder(binderConfigurationName, bindableType);
}
/**
* Provided for backwards compatibility. Will be removed in a future version.
*
* @return
*/
@Deprecated

View File

@@ -28,10 +28,12 @@ import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
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;
@@ -57,15 +59,32 @@ public class BinderFactoryConfiguration {
private static final String SELF_CONTAINED_APP_PROPERTY_NAME = SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".selfContained";
private static final String BINDER_CONFIGURATIONS_BEAN_NAME = "spring.cloud.stream.binderConfigruations";
@Value("${" + SELF_CONTAINED_APP_PROPERTY_NAME + ":}")
private String selfContained;
@Autowired
private BinderTypeRegistry binderTypeRegistry;
@Autowired
private BindingServiceProperties bindingServiceProperties;
@Autowired(required = false)
private Collection<BinderFactoryListener> binderFactoryListeners;
@Bean
@ConditionalOnMissingBean(BinderFactory.class)
public BinderFactory binderFactory(BinderTypeRegistry binderTypeRegistry,
BindingServiceProperties bindingServiceProperties) {
public BinderFactory binderFactory() {
DefaultBinderFactory binderFactory = new DefaultBinderFactory(getBinderConfigurations());
binderFactory.setDefaultBinder(bindingServiceProperties.getDefaultBinder());
binderFactory.setBinderFactoryListeners(binderFactoryListeners);
return binderFactory;
}
public Map<String, BinderConfiguration> getBinderConfigurations() {
Map<String, BinderConfiguration> binderConfigurations = new HashMap<>();
Map<String, BinderProperties> declaredBinders = bindingServiceProperties.getBinders();
Map<String, BinderProperties> declaredBinders = this.bindingServiceProperties.getBinders();
boolean defaultCandidatesExist = false;
Iterator<Map.Entry<String, BinderProperties>> binderPropertiesIterator = declaredBinders.entrySet().iterator();
while (!defaultCandidatesExist && binderPropertiesIterator.hasNext()) {
@@ -74,9 +93,9 @@ public class BinderFactoryConfiguration {
List<String> existingBinderConfigurations = new ArrayList<>();
for (Map.Entry<String, BinderProperties> binderEntry : declaredBinders.entrySet()) {
BinderProperties binderProperties = binderEntry.getValue();
if (binderTypeRegistry.get(binderEntry.getKey()) != null) {
if (this.binderTypeRegistry.get(binderEntry.getKey()) != null) {
binderConfigurations.put(binderEntry.getKey(),
new BinderConfiguration(binderTypeRegistry.get(binderEntry.getKey()),
new BinderConfiguration(this.binderTypeRegistry.get(binderEntry.getKey()),
binderProperties.getEnvironment(), binderProperties.isInheritEnvironment(),
binderProperties.isDefaultCandidate()));
existingBinderConfigurations.add(binderEntry.getKey());
@@ -84,7 +103,7 @@ public class BinderFactoryConfiguration {
else {
Assert.hasText(binderProperties.getType(),
"No 'type' property present for custom binder " + binderEntry.getKey());
BinderType binderType = binderTypeRegistry.get(binderProperties.getType());
BinderType binderType = this.binderTypeRegistry.get(binderProperties.getType());
Assert.notNull(binderType, "Binder type " + binderProperties.getType() + " is not defined");
binderConfigurations.put(binderEntry.getKey(),
new BinderConfiguration(binderType, binderProperties.getEnvironment(),
@@ -92,22 +111,20 @@ public class BinderFactoryConfiguration {
existingBinderConfigurations.add(binderEntry.getKey());
}
}
for (Map.Entry<String, BinderConfiguration> configurationEntry: binderConfigurations.entrySet()) {
for (Map.Entry<String, BinderConfiguration> configurationEntry : binderConfigurations.entrySet()) {
if (configurationEntry.getValue().isDefaultCandidate()) {
defaultCandidatesExist = true;
}
}
if (!defaultCandidatesExist) {
for (Map.Entry<String, BinderType> binderEntry : binderTypeRegistry.getAll().entrySet()) {
for (Map.Entry<String, BinderType> binderEntry : this.binderTypeRegistry.getAll().entrySet()) {
if (!existingBinderConfigurations.contains(binderEntry.getKey())) {
binderConfigurations.put(binderEntry.getKey(), new BinderConfiguration(binderEntry.getValue(),
new Properties(), true, true));
}
}
}
DefaultBinderFactory binderFactory = new DefaultBinderFactory(binderConfigurations);
binderFactory.setDefaultBinder(bindingServiceProperties.getDefaultBinder());
return binderFactory;
return binderConfigurations;
}
@Bean
@@ -128,7 +145,7 @@ public class BinderFactoryConfiguration {
URL url = resources.nextElement();
UrlResource resource = new UrlResource(url);
for (BinderType binderType : parseBinderConfigurations(classLoader, resource)) {
binderTypes.put(binderType.getDefaultName(), binderType);
binderTypes.put(binderType.getDefaultName(), binderType);
}
}
}

View File

@@ -68,6 +68,7 @@ import org.springframework.util.CollectionUtils;
/**
* Configuration class that provides necessary beans for {@link MessageChannel} binding.
*
* @author Dave Syer
* @author David Turanski
* @author Marius Bogoevici

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-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.
@@ -16,22 +16,14 @@
package org.springframework.cloud.stream.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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.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.BindingService;
import org.springframework.cloud.stream.endpoint.ChannelsEndpoint;
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;
@@ -41,34 +33,20 @@ import org.springframework.messaging.MessageChannel;
*
* @author Dave Syer
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
*/
@Configuration
@ConditionalOnBean(BindingService.class)
@EnableConfigurationProperties(DefaultPollerProperties.class)
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@Import(ChannelsEndpointConfiguration.class)
public class ChannelBindingAutoConfiguration {
@Autowired
private DefaultPollerProperties poller;
@Autowired(required = false)
private List<Bindable> adapters;
@Bean(name = PollerMetadata.DEFAULT_POLLER)
@ConditionalOnMissingBean(PollerMetadata.class)
public PollerMetadata defaultPoller() {
return this.poller.getPollerMetadata();
}
@Bean
public ChannelsEndpoint channelsEndpoint(BindingServiceProperties properties) {
return new ChannelsEndpoint(this.adapters, properties);
}
@Bean
@ConditionalOnEnabledHealthIndicator("binders")
@ConditionalOnMissingBean(name = "bindersHealthIndicator")
public CompositeHealthIndicator bindersHealthIndicator() {
return new CompositeHealthIndicator(new OrderedHealthAggregator());
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2015-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.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.ConditionalOnClass;
import org.springframework.cloud.stream.binding.Bindable;
import org.springframework.cloud.stream.endpoint.ChannelsEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
*/
@Configuration
@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.Endpoint")
@AutoConfigureAfter(EndpointAutoConfiguration.class)
public class ChannelsEndpointConfiguration {
@Autowired(required = false)
private List<Bindable> adapters;
@Bean
public ChannelsEndpoint channelsEndpoint(BindingServiceProperties properties) {
return new ChannelsEndpoint(this.adapters, properties);
}
}

View File

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