Upgrade spring-boot to 3.5.0, an issue where a property source binds to java objects slowly, for deep nested collection (consists of 4 depth), around 1 minutes. Number of scans of property with exponential growth. Because the ManagementContextAutoConfiguration automatically adds an un enumerable property source to the environment,resulting in the inability to confirm whether the property source contains bindable property, requiring guessing and traversing property one by one based on the index 0-12
Root cause is changes in 93113a415f1516b75a21822c4912e7946f8868ae
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.env.PropertySource;
import org.springframework.lang.NonNull;
import java.util.List;
@Slf4j
public class SpringBindIssue {
public static void main(String[] args) {
var propertySources = List.of(ConfigurationPropertySource.from(new PropertySource<>("xxxx") {
@Override
public Object getProperty(@NonNull String name) {
if (name.equals("xxxx")) {
return "xxx";
}
return null;
}
}));
AbstractBindHandler abstractBindHandler = new AbstractBindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
log.info("Start bind [{}] [{}]", target.getType(), name);
return super.onStart(name, target, context);
}
};
new Binder(propertySources, null, new ApplicationConversionService(), null, abstractBindHandler).bind("", NestedObject1.class);
}
@Setter
@Getter
public static class NestedObject1 {
@NestedConfigurationProperty
private List<NestedObject2> children;
}
@Setter
@Getter
public static class NestedObject2 {
@NestedConfigurationProperty
private List<NestedObject3> children;
}
@Setter
@Getter
public static class NestedObject3 {
@NestedConfigurationProperty
private List<NestedObject4> children;
}
@Setter
@Getter
public static class NestedObject4 {
@NestedConfigurationProperty
private List<String> children;
}
}
Start bind [SpringBindIssue$NestedObject1] []
Start bind [java.util.List<SpringBindIssue$NestedObject2>] [children]
Start bind [SpringBindIssue$NestedObject2] [children[0]]
Start bind [java.util.List<SpringBindIssue$NestedObject3>] [children[0].children]
Start bind [SpringBindIssue$NestedObject3] [children[0].children[0]]
Start bind [java.util.List<SpringBindIssue$NestedObject4>] [children[0].children[0].children]
Start bind [SpringBindIssue$NestedObject4] [children[0].children[0].children[0]]
Start bind [java.util.List<java.lang.String>] [children[0].children[0].children[0].children]
Start bind [java.lang.String] [children[0].children[0].children[0].children[0]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[1]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[2]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[3]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[4]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[5]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[6]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[7]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[8]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[9]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[10]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[11]]
Start bind [java.lang.String] [children[0].children[0].children[0].children[12]]
Start bind [SpringBindIssue$NestedObject4] [children[0].children[0].children[1]]
Start bind [java.util.List<java.lang.String>] [children[0].children[0].children[1].children]
Start bind [java.lang.String] [children[0].children[0].children[1].children[0]]
Start bind [java.lang.String] [children[0].children[0].children[1].children[1]]
...
...
...
...
Comment From: wilkinsona
Here's the reproducer without Lombok:
package gh45970;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.env.PropertySource;
import org.springframework.lang.NonNull;
public class SpringBindIssue {
private static final Logger log = LoggerFactory.getLogger(SpringBindIssue.class);
public static void main(String[] args) {
var propertySources = List.of(ConfigurationPropertySource.from(new PropertySource<>("xxxx") {
@Override
public Object getProperty(@NonNull String name) {
if (name.equals("xxxx")) {
return "xxx";
}
return null;
}
}));
AbstractBindHandler abstractBindHandler = new AbstractBindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
log.info("Start bind [{}] [{}]", target.getType(), name);
return super.onStart(name, target, context);
}
};
new Binder(propertySources, null, new ApplicationConversionService(), null, abstractBindHandler).bind("",
NestedObject1.class);
}
public static class NestedObject1 {
@NestedConfigurationProperty
private List<NestedObject2> children;
public List<NestedObject2> getChildren() {
return this.children;
}
public void setChildren(List<NestedObject2> children) {
this.children = children;
}
}
public static class NestedObject2 {
@NestedConfigurationProperty
private List<NestedObject3> children;
public List<NestedObject3> getChildren() {
return this.children;
}
public void setChildren(List<NestedObject3> children) {
this.children = children;
}
}
public static class NestedObject3 {
@NestedConfigurationProperty
private List<NestedObject4> children;
public List<NestedObject4> getChildren() {
return this.children;
}
public void setChildren(List<NestedObject4> children) {
this.children = children;
}
}
public static class NestedObject4 {
@NestedConfigurationProperty
private List<String> children;
public List<String> getChildren() {
return this.children;
}
public void setChildren(List<String> children) {
this.children = children;
}
}
}
The logging with 3.4 is minimal:
06:37:35.041 [main] INFO temp.SpringBindIssue -- Start bind [temp.SpringBindIssue$NestedObject1] []
06:37:35.061 [main] INFO temp.SpringBindIssue -- Start bind [java.util.List<temp.SpringBindIssue$NestedObject2>] [children]
06:37:35.064 [main] INFO temp.SpringBindIssue -- Start bind [temp.SpringBindIssue$NestedObject2] [children[0]]
06:37:35.065 [main] INFO temp.SpringBindIssue -- Start bind [java.util.List<temp.SpringBindIssue$NestedObject3>] [children[0].children]
06:37:35.065 [main] INFO temp.SpringBindIssue -- Start bind [temp.SpringBindIssue$NestedObject3] [children[0].children[0]]
06:37:35.066 [main] INFO temp.SpringBindIssue -- Start bind [java.util.List<temp.SpringBindIssue$NestedObject4>] [children[0].children[0].children]
06:37:35.066 [main] INFO temp.SpringBindIssue -- Start bind [temp.SpringBindIssue$NestedObject4] [children[0].children[0].children[0]]
06:37:35.066 [main] INFO temp.SpringBindIssue -- Start bind [java.util.List<java.lang.String>] [children[0].children[0].children[0].children]
06:37:35.066 [main] INFO temp.SpringBindIssue -- Start bind [java.lang.String] [children[0].children[0].children[0].children[0]]
With 3.5, it's over 33000 lines.