Short circuit binding if possible

Update the `Binder` to short circuit potentially expensive bean binding
if there are no known child properties. The shortcut can only be applied
when all used `ConfigurationPropertySources` return a non empty
`containsDescendantOf` result. This should be the case for most
Spring Boot applications, the exception being any apps that are running
in a security restricted environment.

Fixes gh-9023
pull/9050/merge
Phillip Webb 8 years ago
parent c22a21e386
commit 8b1625b41d

@ -16,6 +16,7 @@
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
/**
@ -29,15 +30,16 @@ interface BeanBinder {
/**
* Return a bound bean instance or {@code null} if the {@link BeanBinder} does not
* support the specified {@link Bindable}.
* @param target the bindable to bind
* @param hasKnownBindableProperties if this binder has known bindable elements. If
* names from underlying {@link ConfigurationPropertySource} cannot be iterated this
* method can be {@code false}, even though binding may ultimately succeed.
* @param name the name being bound
* @param target the bindable to bind names from underlying
* {@link ConfigurationPropertySource} cannot be iterated this method can be
* {@code false}, even though binding may ultimately succeed.
* @param context the bind context
* @param propertyBinder property binder
* @param <T> The source type
* @return a bound instance or {@code null}
*/
<T> T bind(Bindable<T> target, boolean hasKnownBindableProperties,
<T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindContext context,
BeanPropertyBinder propertyBinder);
}

@ -16,6 +16,8 @@
package org.springframework.boot.context.properties.bind;
import java.util.stream.Stream;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
@ -38,12 +40,19 @@ public interface BindContext {
int getDepth();
/**
* Return the {@link ConfigurationPropertySource sources} being used by the
* {@link Binder}.
* Return an {@link Iterable} of the {@link ConfigurationPropertySource sources} being
* used by the {@link Binder}.
* @return the sources
*/
Iterable<ConfigurationPropertySource> getSources();
/**
* Return a {@link Stream} of the {@link ConfigurationPropertySource sources} being
* used by the {@link Binder}.
* @return the sources
*/
Stream<ConfigurationPropertySource> streamSources();
/**
* Return the {@link ConfigurationProperty} actually being bound or {@code null} if
* the property has not yet been determined.

@ -36,7 +36,6 @@ import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@ -289,8 +288,8 @@ public class Binder {
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
BindHandler handler, Context context) {
boolean hasKnownBindableProperties = hasKnownBindableProperties(name, context);
if (!hasKnownBindableProperties && isUnbindableBean(target)) {
if (containsNoDescendantOf(context.streamSources(), name)
|| isUnbindableBean(name, target, context)) {
return null;
}
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
@ -300,22 +299,19 @@ public class Binder {
return null;
}
return context.withBean(type, () -> {
Stream<?> boundBeans = BEAN_BINDERS.stream().map(
(b) -> b.bind(target, hasKnownBindableProperties, propertyBinder));
Stream<?> boundBeans = BEAN_BINDERS.stream()
.map((b) -> b.bind(name, target, context, propertyBinder));
return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
});
}
private boolean hasKnownBindableProperties(ConfigurationPropertyName name,
private boolean isUnbindableBean(ConfigurationPropertyName name, Bindable<?> target,
Context context) {
Stream<IterableConfigurationPropertySource> sources = context.streamSources()
.filter(IterableConfigurationPropertySource.class::isInstance)
.map(IterableConfigurationPropertySource.class::cast);
return sources.flatMap((s) -> s.filter(name::isAncestorOf).stream()).findAny()
.isPresent();
}
private boolean isUnbindableBean(Bindable<?> target) {
if (context.streamSources().map((s) -> s.containsDescendantOf(name).orElse(false))
.anyMatch(Boolean.TRUE::equals)) {
// We know there are properties to bind so we can't bypass anything
return false;
}
Class<?> resolved = target.getType().resolve();
if (resolved.isPrimitive() || NON_BEAN_CLASSES.contains(resolved)) {
return true;
@ -324,6 +320,12 @@ public class Binder {
return packageName.startsWith("java.");
}
private boolean containsNoDescendantOf(Stream<ConfigurationPropertySource> sources,
ConfigurationPropertyName name) {
return sources.map((s) -> s.containsDescendantOf(name).orElse(true))
.allMatch(Boolean.FALSE::equals);
}
/**
* Create a new {@link Binder} instance from the specified environment.
* @param environment the environment (must be a {@link ConfigurableEnvironment})
@ -400,19 +402,20 @@ public class Binder {
}
}
private Stream<ConfigurationPropertySource> streamSources() {
@Override
public Iterable<ConfigurationPropertySource> getSources() {
if (this.sourcePushCount > 0) {
return this.source.stream();
return this.source;
}
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
return Binder.this.sources;
}
@Override
public Iterable<ConfigurationPropertySource> getSources() {
public Stream<ConfigurationPropertySource> streamSources() {
if (this.sourcePushCount > 0) {
return this.source;
return this.source.stream();
}
return Binder.this.sources;
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
}
public boolean hasBoundBean(Class<?> bean) {

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.ResolvableType;
/**
@ -37,8 +38,11 @@ import org.springframework.core.ResolvableType;
class JavaBeanBinder implements BeanBinder {
@Override
public <T> T bind(Bindable<T> target, boolean hasKnownBindableProperties,
BeanPropertyBinder propertyBinder) {
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
BindContext context, BeanPropertyBinder propertyBinder) {
boolean hasKnownBindableProperties = context.streamSources()
.map((s) -> s.containsDescendantOf(name).orElse(false))
.anyMatch(Boolean.TRUE::equals);
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) {
return null;

Loading…
Cancel
Save