From c76b087025ff58bd2a9564475bd8498bedaea450 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Mon, 18 Aug 2025 11:29:03 +0200 Subject: [PATCH] Improve null-safety of core/spring-boot-autoconfigure See gh-46926 --- .../AutoConfigurationExcludeFilter.java | 10 +-- .../AutoConfigurationPackages.java | 4 +- .../AutoConfigurationSorter.java | 43 ++++++---- .../FilteringSpringBootCondition.java | 9 ++- .../condition/OnBeanCondition.java | 78 ++++++++++--------- .../condition/OnClassCondition.java | 4 +- 6 files changed, 87 insertions(+), 61 deletions(-) diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java index 1b6e149dad8..bbd6d33becf 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationExcludeFilter.java @@ -27,7 +27,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; -import org.springframework.util.Assert; /** * A {@link TypeFilter} implementation that matches registered auto-configuration classes. @@ -66,12 +65,13 @@ public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoad } protected List getAutoConfigurations() { - if (this.autoConfigurations == null) { + List autoConfigurations = this.autoConfigurations; + if (autoConfigurations == null) { ImportCandidates importCandidates = ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader); - this.autoConfigurations = importCandidates.getCandidates(); + autoConfigurations = importCandidates.getCandidates(); + this.autoConfigurations = autoConfigurations; } - Assert.state(this.autoConfigurations != null, "'autoConfigurations' must not be null"); - return this.autoConfigurations; + return autoConfigurations; } } diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java index 70d1ce680c8..0729d048690 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java @@ -110,8 +110,10 @@ public abstract class AutoConfigurationPackages { ValueHolder indexedArgumentValue = constructorArgumentValues.getIndexedArgumentValue(0, String[].class); Assert.state(indexedArgumentValue != null, "'indexedArgumentValue' must not be null"); String[] existingPackages = (String[]) indexedArgumentValue.getValue(); + Stream existingPackagesStream = (existingPackages != null) ? Stream.of(existingPackages) + : Stream.empty(); constructorArgumentValues.addIndexedArgumentValue(0, - Stream.concat(Stream.of(existingPackages), Stream.of(additionalBasePackages)) + Stream.concat(existingPackagesStream, Stream.of(additionalBasePackages)) .distinct() .toArray(String[]::new)); } diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java index 8a53a351844..f44b05573b2 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java @@ -202,17 +202,21 @@ class AutoConfigurationSorter { } Set getBefore() { - if (this.before == null) { - this.before = getClassNames("AutoConfigureBefore", AutoConfigureBefore.class); + Set before = this.before; + if (before == null) { + before = getClassNames("AutoConfigureBefore", AutoConfigureBefore.class); + this.before = before; } - return this.before; + return before; } Set getAfter() { - if (this.after == null) { - this.after = getClassNames("AutoConfigureAfter", AutoConfigureAfter.class); + Set after = this.after; + if (after == null) { + after = getClassNames("AutoConfigureAfter", AutoConfigureAfter.class); + this.after = after; } - return this.after; + return after; } private Set getClassNames(String metadataKey, Class annotation) { @@ -244,7 +248,12 @@ class AutoConfigurationSorter { } Map attributes = getAnnotationMetadata() .getAnnotationAttributes(AutoConfigureOrder.class.getName()); - return (attributes != null) ? (Integer) attributes.get("value") : AutoConfigureOrder.DEFAULT_ORDER; + if (attributes != null) { + Integer value = (Integer) attributes.get("value"); + Assert.state(value != null, "'value' must not be null"); + return value; + } + return AutoConfigureOrder.DEFAULT_ORDER; } private boolean wasProcessed() { @@ -258,23 +267,29 @@ class AutoConfigurationSorter { if (attributes == null) { return Collections.emptySet(); } - Set value = new LinkedHashSet<>(); - Collections.addAll(value, (String[]) attributes.get("value")); - Collections.addAll(value, (String[]) attributes.get("name")); - return value; + Set result = new LinkedHashSet<>(); + String[] value = (String[]) attributes.get("value"); + String[] name = (String[]) attributes.get("name"); + Assert.state(value != null, "'value' must not be null"); + Assert.state(name != null, "'name' must not be null"); + Collections.addAll(result, value); + Collections.addAll(result, name); + return result; } private AnnotationMetadata getAnnotationMetadata() { - if (this.annotationMetadata == null) { + AnnotationMetadata annotationMetadata = this.annotationMetadata; + if (annotationMetadata == null) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(this.className); - this.annotationMetadata = metadataReader.getAnnotationMetadata(); + annotationMetadata = metadataReader.getAnnotationMetadata(); + this.annotationMetadata = annotationMetadata; } catch (IOException ex) { throw new IllegalStateException("Unable to read meta-data for class " + this.className, ex); } } - return this.annotationMetadata; + return annotationMetadata; } } diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java index 774b3655747..804ecdb10a5 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/FilteringSpringBootCondition.java @@ -55,13 +55,14 @@ abstract class FilteringSpringBootCondition extends SpringBootCondition @Nullable ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata); boolean[] match = new boolean[outcomes.length]; for (int i = 0; i < outcomes.length; i++) { - match[i] = (outcomes[i] == null || outcomes[i].isMatch()); - if (!match[i] && outcomes[i] != null) { + ConditionOutcome outcome = outcomes[i]; + match[i] = (outcome == null || outcome.isMatch()); + if (!match[i] && outcome != null) { String autoConfigurationClass = autoConfigurationClasses[i]; Assert.state(autoConfigurationClass != null, "'autoConfigurationClass' must not be null"); - logOutcome(autoConfigurationClass, outcomes[i]); + logOutcome(autoConfigurationClass, outcome); if (report != null) { - report.recordConditionEvaluation(autoConfigurationClass, this, outcomes[i]); + report.recordConditionEvaluation(autoConfigurationClass, this, outcome); } } } diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index a548e0c31c3..a8c09108706 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -47,6 +47,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; +import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; @@ -177,7 +178,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat } ConfigurableListableBeanFactory beanFactory = spec.context.getBeanFactory(); Assert.state(beanFactory != null, "'beanFactory' must not be null"); - Map beanDefinitions = getBeanDefinitions(beanFactory, allBeans, + Map beanDefinitions = getBeanDefinitions(beanFactory, allBeans, spec.getStrategy() == SearchStrategy.ALL); List primaryBeans = getPrimaryBeans(beanDefinitions); if (primaryBeans.size() == 1) { @@ -217,7 +218,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat Set beansIgnoredByType = getNamesOfBeansIgnoredByType(beanFactory, considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers); for (ResolvableType type : spec.getTypes()) { - Map typeMatchedDefinitions = getBeanDefinitionsForType(beanFactory, + Map typeMatchedDefinitions = getBeanDefinitionsForType(beanFactory, considerHierarchy, type, parameterizedContainers); Set typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions, (name, definition) -> !ScopedProxyUtils.isScopedTarget(name) @@ -230,8 +231,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat } } for (String annotation : spec.getAnnotations()) { - Map annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader, - beanFactory, annotation, considerHierarchy); + Map annotationMatchedDefinitions = getBeanDefinitionsForAnnotation( + classLoader, beanFactory, annotation, considerHierarchy); Set annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions, (name, definition) -> isCandidate(beanFactory, name, definition, beansIgnoredByType)); if (annotationMatchedNames.isEmpty()) { @@ -265,7 +266,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return beanFactory; } - private Set matchedNamesFrom(Map namedDefinitions, + private Set matchedNamesFrom(Map namedDefinitions, BiPredicate filter) { Set matchedNames = new LinkedHashSet<>(namedDefinitions.size()); for (Entry namedDefinition : namedDefinitions.entrySet()) { @@ -316,16 +317,16 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return (result != null) ? result : Collections.emptySet(); } - private Map getBeanDefinitionsForType(ListableBeanFactory beanFactory, + private Map getBeanDefinitionsForType(ListableBeanFactory beanFactory, boolean considerHierarchy, ResolvableType type, Set parameterizedContainers) { - Map result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, type, - parameterizedContainers, null); - return (result != null) ? result : Collections.emptyMap(); + Map result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, + type, parameterizedContainers, null); + return (result != null) ? result : Collections.emptyMap(); } - private @Nullable Map collectBeanDefinitionsForType(ListableBeanFactory beanFactory, - boolean considerHierarchy, ResolvableType type, Set parameterizedContainers, - @Nullable Map result) { + private @Nullable Map collectBeanDefinitionsForType( + ListableBeanFactory beanFactory, boolean considerHierarchy, ResolvableType type, + Set parameterizedContainers, @Nullable Map result) { result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory); for (ResolvableType parameterizedContainer : parameterizedContainers) { Class resolved = parameterizedContainer.resolve(); @@ -343,9 +344,9 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return result; } - private Map getBeanDefinitionsForAnnotation(@Nullable ClassLoader classLoader, + private Map getBeanDefinitionsForAnnotation(@Nullable ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError { - Map result = null; + Map result = null; try { result = collectBeanDefinitionsForAnnotation(beanFactory, resolveAnnotationType(classLoader, type), considerHierarchy, result); @@ -353,7 +354,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat catch (ClassNotFoundException ex) { // Continue } - return (result != null) ? result : Collections.emptyMap(); + return (result != null) ? result : Collections.emptyMap(); } @SuppressWarnings("unchecked") @@ -362,9 +363,9 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return (Class) resolve(type, classLoader); } - private @Nullable Map collectBeanDefinitionsForAnnotation(ListableBeanFactory beanFactory, - Class annotationType, boolean considerHierarchy, - @Nullable Map result) { + private @Nullable Map collectBeanDefinitionsForAnnotation( + ListableBeanFactory beanFactory, Class annotationType, boolean considerHierarchy, + @Nullable Map result) { result = putAll(result, getBeanNamesForAnnotation(beanFactory, annotationType), beanFactory); if (considerHierarchy) { BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(); @@ -459,9 +460,9 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat } } - private Map getBeanDefinitions(ConfigurableListableBeanFactory beanFactory, + private Map getBeanDefinitions(ConfigurableListableBeanFactory beanFactory, Set beanNames, boolean considerHierarchy) { - Map definitions = new HashMap<>(beanNames.size()); + Map definitions = new HashMap<>(beanNames.size()); for (String beanName : beanNames) { BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName, considerHierarchy); definitions.put(beanName, beanDefinition); @@ -469,17 +470,20 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return definitions; } - private List getPrimaryBeans(Map beanDefinitions) { - return getMatchingBeans(beanDefinitions, BeanDefinition::isPrimary); + private List getPrimaryBeans(Map beanDefinitions) { + return getMatchingBeans(beanDefinitions, + (beanDefinition) -> beanDefinition != null && beanDefinition.isPrimary()); } - private List getNonFallbackBeans(Map beanDefinitions) { - return getMatchingBeans(beanDefinitions, Predicate.not(BeanDefinition::isFallback)); + private List getNonFallbackBeans(Map beanDefinitions) { + return getMatchingBeans(beanDefinitions, + Predicate.not((beanDefinition) -> beanDefinition != null && beanDefinition.isFallback())); } - private List getMatchingBeans(Map beanDefinitions, Predicate test) { + private List getMatchingBeans(Map beanDefinitions, + Predicate<@Nullable BeanDefinition> test) { List matches = new ArrayList<>(); - for (Entry namedBeanDefinition : beanDefinitions.entrySet()) { + for (Entry namedBeanDefinition : beanDefinitions.entrySet()) { if (test.test(namedBeanDefinition.getValue())) { matches.add(namedBeanDefinition.getKey()); } @@ -508,8 +512,9 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return result; } - private static @Nullable Map putAll(@Nullable Map result, - String[] beanNames, ListableBeanFactory beanFactory) { + private static @Nullable Map putAll( + @Nullable Map result, String[] beanNames, + ListableBeanFactory beanFactory) { if (ObjectUtils.isEmpty(beanNames)) { return result; } @@ -563,7 +568,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations, Class annotationType) { - MultiValueMap attributes = annotations.stream(annotationType) + MultiValueMap attributes = annotations.stream(annotationType) .filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)) .collect(MergedAnnotationCollectors.toMultiValueMap(Adapt.CLASS_TO_STRING)); MergedAnnotation annotation = annotations.get(annotationType); @@ -588,17 +593,18 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat validate(deductionException); } - protected Set extractTypes(MultiValueMap attributes) { + protected Set extractTypes(@Nullable MultiValueMap attributes) { return extract(attributes, "value", "type"); } - private Set extract(MultiValueMap attributes, String... attributeNames) { - if (attributes.isEmpty()) { + private Set extract(@Nullable MultiValueMap attributes, + String... attributeNames) { + if (CollectionUtils.isEmpty(attributes)) { return Collections.emptySet(); } Set result = new LinkedHashSet<>(); for (String attributeName : attributeNames) { - List values = attributes.getOrDefault(attributeName, Collections.emptyList()); + List<@Nullable Object> values = attributes.getOrDefault(attributeName, Collections.emptyList()); for (Object value : values) { if (value instanceof String[] stringArray) { merge(result, stringArray); @@ -743,11 +749,11 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return this.parameterizedContainers; } - private ConditionMessage.Builder message() { + private Builder message() { return ConditionMessage.forCondition(this.annotationType, this); } - private ConditionMessage.Builder message(ConditionMessage message) { + private Builder message(ConditionMessage message) { return message.andCondition(this.annotationType, this); } @@ -796,7 +802,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat } @Override - protected Set extractTypes(MultiValueMap attributes) { + protected Set extractTypes(@Nullable MultiValueMap attributes) { Set types = super.extractTypes(attributes); types.removeAll(FILTERED_TYPES); return types; diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java index 7e43351dd87..afbe00a2117 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java @@ -129,7 +129,9 @@ class OnClassCondition extends FilteringSpringBootCondition { private void addAll(List list, @Nullable List<@Nullable Object> itemsToAdd) { if (itemsToAdd != null) { for (Object item : itemsToAdd) { - Collections.addAll(list, (String[]) item); + if (item != null) { + Collections.addAll(list, (String[]) item); + } } } }