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

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

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
/** /**
@ -37,8 +38,11 @@ import org.springframework.core.ResolvableType;
class JavaBeanBinder implements BeanBinder { class JavaBeanBinder implements BeanBinder {
@Override @Override
public <T> T bind(Bindable<T> target, boolean hasKnownBindableProperties, public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
BeanPropertyBinder propertyBinder) { 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); Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) { if (bean == null) {
return null; return null;

Loading…
Cancel
Save