spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

Propose new spring.autoconfigure.exclusion property binding as Map to support multiple property sources

Open tmoschou opened this issue 6 months ago • 6 comments

Currently the spring.autoconfigure.exclude property is bound as a List<Class<?>>. This is a problem as collections cannot be merged across different property sources or individual elements removed. Additionally it requires property sources with higher precedence to have global knowledge of all preexisting exclusion from other sources in order to append a new exclusion.

This is a frequent pain-point for us and others. See #27414 - Consider merging spring.autoconfigure.exclude from multiple profiles and https://github.com/spring-projects/spring-boot/issues/9137

Understandably merging / overriding logic for collections is difficult - refer to https://github.com/spring-projects/spring-boot/issues/12444#issuecomment-372410949

I propose a new configuration property (E.g. spring.autoconfigure.exclusions or perhaps some other property if too similarly named) that is bound as a Map<Class<?>, Boolean>. Binding to a map will allow merging from different property sources and profiles. E.g.

---
spring:
  config.activate.on-profile: nosecurity
  autoconfigure:
    exclusions: 
      org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration: true
      org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration: true
 
---
spring:
  config.activate.on-profile: noredis
  autoconfigure:
    exclusions: 
      org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration: true

Including the class name in the key is not too different from the logging.level.<class> properties and similar to management.health.<name>.enabled.

I have a custom EnvironmentPostProcessor that I think achieves my goal by rebinding the spring.autoconfigure.exclude property from my custom spring.autoconfigure.exclusions property? But it would be nice if spring-boot had first class support for this. For posterity here it is

@Order(Ordered.LOWEST_PRECEDENCE)
public class AutoConfigureExcludeEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private static final String SPRING_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    private static final String SPRING_AUTOCONFIGURE_EXCLUDEMAP = "spring.autoconfigure.exclusion";

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Binder binder = Binder.get(environment);
        Set<String> disabled = new HashSet<>();
        binder.bind(SPRING_AUTOCONFIGURE_EXCLUDE, Bindable.listOf(String.class)).ifBound(disabled::addAll);
        Map<String, Boolean> excludeMap =
            binder.bind(SPRING_AUTOCONFIGURE_EXCLUDEMAP, Bindable.mapOf(String.class, Boolean.class))
                .orElseGet(Collections::emptyMap);

        for (Map.Entry<String, Boolean> entry : excludeMap.entrySet()) {
            if (entry.getValue()) {
                disabled.add(entry.getKey());
            } else {
                // override if class is present in 'spring.autoconfigure.exclude' property
                disabled.remove(entry.getKey());
            }
        }

        if (!disabled.isEmpty() || environment.containsProperty(SPRING_AUTOCONFIGURE_EXCLUDE)) {
            environment.getPropertySources().addFirst(new MapPropertySource(
                AutoConfigureExcludeEnvironmentPostProcessor.class.getSimpleName(),
                Map.of(SPRING_AUTOCONFIGURE_EXCLUDE, disabled)
            ));
        }
    }
}

tmoschou avatar Aug 01 '24 11:08 tmoschou