Prevent listeners being added multiple times on restart

(and at other times potentially - e.g. if 2 contexts share a parent).

Plus some general tidy up on compiler warnings.

Fixes gh-613
This commit is contained in:
Dave Syer
2019-12-17 16:31:11 +00:00
parent 4cc23b4b71
commit bfbadec0f0
9 changed files with 85 additions and 44 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.Health;
@@ -68,7 +69,7 @@ public class RefreshEndpointAutoConfiguration {
@Bean
@ConditionalOnBean(ContextRefresher.class)
@ConditionalOnEnabledEndpoint
@ConditionalOnAvailableEndpoint
@ConditionalOnMissingBean
public RefreshEndpoint refreshEndpoint(ContextRefresher contextRefresher) {
return new RefreshEndpoint(contextRefresher);

View File

@@ -302,28 +302,43 @@ public class BootstrapApplicationListener
}
@SuppressWarnings("unchecked")
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
return;
}
application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
@SuppressWarnings("rawtypes")
List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
ApplicationContextInitializer.class);
application.addInitializers(initializers
.toArray(new ApplicationContextInitializer[initializers.size()]));
Set target = new LinkedHashSet<>(application.getInitializers());
target.addAll(
getOrderedBeansOfType(context, ApplicationContextInitializer.class));
application.setInitializers(target);
addBootstrapDecryptInitializer(application);
}
@SuppressWarnings("unchecked")
private void addBootstrapDecryptInitializer(SpringApplication application) {
DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
Set<ApplicationContextInitializer<?>> initializers = new LinkedHashSet<>();
for (ApplicationContextInitializer<?> ini : application.getInitializers()) {
if (ini instanceof EnvironmentDecryptApplicationInitializer) {
@SuppressWarnings("unchecked")
ApplicationContextInitializer del = (ApplicationContextInitializer) ini;
@SuppressWarnings("rawtypes")
ApplicationContextInitializer del = ini;
decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(del);
initializers.add(ini);
initializers.add(decrypter);
}
else if (ini instanceof DelegatingEnvironmentDecryptApplicationInitializer) {
// do nothing
}
else {
initializers.add(ini);
}
}
if (decrypter != null) {
application.addInitializers(decrypter);
}
ArrayList<ApplicationContextInitializer<?>> target = new ArrayList<ApplicationContextInitializer<?>>(
initializers);
application.setInitializers(target);
}
private <T> List<T> getOrderedBeansOfType(ListableBeanFactory context,
@@ -345,6 +360,10 @@ public class BootstrapApplicationListener
this.order = order;
}
private static class BootstrapMarkerConfiguration {
}
private static class AncestorInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

View File

@@ -102,9 +102,9 @@ public class PropertySourceBootstrapConfiguration implements
if (source == null || source.size() == 0) {
continue;
}
List sourceList = new ArrayList<>();
for (PropertySource p : source) {
sourceList.add(new BootstrapPropertySource(p));
List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {
sourceList.add(new BootstrapPropertySource<>(p));
}
logger.info("Located property source: " + sourceList);
composite.addAll(sourceList);
@@ -114,7 +114,7 @@ public class PropertySourceBootstrapConfiguration implements
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
for (PropertySource p : environment.getPropertySources()) {
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
@@ -166,13 +166,13 @@ public class PropertySourceBootstrapConfiguration implements
private void insertPropertySources(MutablePropertySources propertySources,
List<PropertySource<?>> composite) {
MutablePropertySources incoming = new MutablePropertySources();
List<PropertySource<?>> reversedComposite = new ArrayList(composite);
List<PropertySource<?>> reversedComposite = new ArrayList<>(composite);
// Reverse the list so that when we call addFirst below we are maintaining the
// same order of PropertySournces
// same order of PropertySources
// Wherever we call addLast we can use the order in the List since the first item
// will end up before the rest
Collections.reverse(reversedComposite);
for (PropertySource p : reversedComposite) {
for (PropertySource<?> p : reversedComposite) {
incoming.addFirst(p);
}
PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
@@ -180,31 +180,31 @@ public class PropertySourceBootstrapConfiguration implements
Bindable.ofInstance(remoteProperties));
if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone()
&& remoteProperties.isOverrideSystemProperties())) {
for (PropertySource p : reversedComposite) {
for (PropertySource<?> p : reversedComposite) {
propertySources.addFirst(p);
}
return;
}
if (remoteProperties.isOverrideNone()) {
for (PropertySource p : composite) {
for (PropertySource<?> p : composite) {
propertySources.addLast(p);
}
return;
}
if (propertySources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
if (!remoteProperties.isOverrideSystemProperties()) {
for (PropertySource p : composite) {
for (PropertySource<?> p : composite) {
propertySources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
}
}
else {
for (PropertySource p : reversedComposite) {
for (PropertySource<?> p : reversedComposite) {
propertySources.addBefore(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
}
}
}
else {
for (PropertySource p : composite) {
for (PropertySource<?> p : composite) {
propertySources.addLast(p);
}
}
@@ -276,7 +276,7 @@ class BootstrapPropertySource<T> extends EnumerablePropertySource<T> {
@Override
public Object getProperty(String name) {
return p.getProperty(name);
return this.p.getProperty(name);
}
@Override
@@ -285,9 +285,10 @@ class BootstrapPropertySource<T> extends EnumerablePropertySource<T> {
if (!(this.p instanceof EnumerablePropertySource)) {
throw new IllegalStateException(
"Failed to enumerate property names due to non-enumerable property source: "
+ p);
+ this.p);
}
names.addAll(Arrays.asList(((EnumerablePropertySource<?>) p).getPropertyNames()));
names.addAll(
Arrays.asList(((EnumerablePropertySource<?>) this.p).getPropertyNames()));
return StringUtils.toStringArray(names);
}

View File

@@ -44,15 +44,15 @@ public interface PropertySourceLocator {
PropertySource<?> locate(Environment environment);
default Collection<PropertySource<?>> locateCollection(Environment environment) {
PropertySource propertySource = locate(environment);
PropertySource<?> propertySource = locate(environment);
if (propertySource == null) {
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
.getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource p : sources) {
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
@@ -60,7 +60,7 @@ public interface PropertySourceLocator {
return filteredSources;
}
else {
return (List) Arrays.asList(propertySource);
return Arrays.asList(propertySource);
}
}

View File

@@ -34,7 +34,7 @@ import org.springframework.lang.Nullable;
*/
class ClientFactoryObjectProvider<T> implements ObjectProvider<T> {
private final NamedContextFactory clientFactory;
private final NamedContextFactory<?> clientFactory;
private final String name;
@@ -42,7 +42,7 @@ class ClientFactoryObjectProvider<T> implements ObjectProvider<T> {
private ObjectProvider<T> provider;
ClientFactoryObjectProvider(NamedContextFactory clientFactory, String name,
ClientFactoryObjectProvider(NamedContextFactory<?> clientFactory, String name,
Class<T> type) {
this.clientFactory = clientFactory;
this.name = name;
@@ -111,7 +111,6 @@ class ClientFactoryObjectProvider<T> implements ObjectProvider<T> {
return delegate().spliterator();
}
@SuppressWarnings("unchecked")
private ObjectProvider<T> delegate() {
if (this.provider == null) {
this.provider = this.clientFactory.getProvider(this.name, this.type);

View File

@@ -23,15 +23,19 @@ import java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.util.ClassUtils;
@@ -83,6 +87,7 @@ public class RestartEndpoint implements ApplicationListener<ApplicationPreparedE
this.context = this.event.getApplicationContext();
this.args = this.event.getArgs();
this.application = this.event.getSpringApplication();
this.application.addInitializers(new PostProcessorInitializer());
}
}
@@ -175,6 +180,29 @@ public class RestartEndpoint implements ApplicationListener<ApplicationPreparedE
this.application.getClass().getClassLoader());
}
class PostProcessorInitializer
implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext context) {
context.registerBean(PostProcessor.class, () -> new PostProcessor());
}
}
class PostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RestartEndpoint) {
return RestartEndpoint.this;
}
return bean;
}
}
/**
* Pause endpoint configuration.
*/

View File

@@ -45,14 +45,7 @@ import org.springframework.jmx.export.annotation.ManagedResource;
*
* <p>
* Note that all beans in this scope are <em>only</em> initialized when first accessed, so
* the scope forces lazy initialization semantics. The implementation involves creating a
* proxy for every bean in the scope, so there is a flag
* {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy
* creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces
* implemented by a bean. If callers need access to other methods, then the flag needs to
* be set (and CGLib must be present on the classpath). Because this scope automatically
* proxies all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to
* any bean definitions.
* the scope forces lazy initialization semantics.
* </p>
*
* <p>

View File

@@ -28,9 +28,9 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
/**
* Calls {@link RefreshEventListener#refresh} when a {@link RefreshEvent} is received.
* Only responds to {@link RefreshEvent} after receiving an {@link ApplicationReadyEvent},
* as the RefreshEvents might come too early in the application lifecycle.
* Calls {@link ContextRefresher#refresh} when a {@link RefreshEvent} is received. Only
* responds to {@link RefreshEvent} after receiving an {@link ApplicationReadyEvent}, as
* the RefreshEvents might come too early in the application lifecycle.
*
* @author Spencer Gibb
*/

View File

@@ -59,7 +59,7 @@ public class RestartIntegrationTests {
then(this.context.getParent().getParent()).isNull();
RestartEndpoint next = this.context.getBean(RestartEndpoint.class);
then(next).isNotSameAs(endpoint);
then(next).isSameAs(endpoint);
this.context = next.doRestart();
then(this.context).isNotNull();