Add nullability annotations to core/spring-boot-autoconfigure

See gh-46587
This commit is contained in:
Moritz Halbritter
2025-07-29 14:33:21 +02:00
parent 807f44487e
commit b6e4533296
85 changed files with 665 additions and 338 deletions
@@ -90,4 +90,5 @@
<suppress files="PemPrivateKeyParser\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
<suppress files="Bindable\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
<suppress files="LambdaSafe\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
<suppress files="ConditionMessage\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
</suppressions>
@@ -22,6 +22,8 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
@@ -50,7 +52,7 @@ public abstract class AbstractDependsOnBeanFactoryPostProcessor implements BeanF
private final Class<?> beanClass;
private final Class<? extends FactoryBean<?>> factoryBeanClass;
private final @Nullable Class<? extends FactoryBean<?>> factoryBeanClass;
private final Function<ListableBeanFactory, Set<String>> dependsOn;
@@ -61,7 +63,7 @@ public abstract class AbstractDependsOnBeanFactoryPostProcessor implements BeanF
* @param dependsOn dependency names
*/
protected AbstractDependsOnBeanFactoryPostProcessor(Class<?> beanClass,
Class<? extends FactoryBean<?>> factoryBeanClass, String... dependsOn) {
@Nullable Class<? extends FactoryBean<?>> factoryBeanClass, String... dependsOn) {
this.beanClass = beanClass;
this.factoryBeanClass = factoryBeanClass;
this.dependsOn = (beanFactory) -> new HashSet<>(Arrays.asList(dependsOn));
@@ -75,7 +77,7 @@ public abstract class AbstractDependsOnBeanFactoryPostProcessor implements BeanF
* @since 2.1.7
*/
protected AbstractDependsOnBeanFactoryPostProcessor(Class<?> beanClass,
Class<? extends FactoryBean<?>> factoryBeanClass, Class<?>... dependencyTypes) {
@Nullable Class<? extends FactoryBean<?>> factoryBeanClass, Class<?>... dependencyTypes) {
this.beanClass = beanClass;
this.factoryBeanClass = factoryBeanClass;
this.dependsOn = (beanFactory) -> Arrays.stream(dependencyTypes)
@@ -19,12 +19,15 @@ package org.springframework.boot.autoconfigure;
import java.io.IOException;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.boot.context.annotation.ImportCandidates;
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.
@@ -35,9 +38,10 @@ import org.springframework.core.type.filter.TypeFilter;
*/
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
@SuppressWarnings("NullAway.Init")
private ClassLoader beanClassLoader;
private volatile List<String> autoConfigurations;
private volatile @Nullable List<String> autoConfigurations;
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
@@ -66,6 +70,7 @@ public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoad
ImportCandidates importCandidates = ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader);
this.autoConfigurations = importCandidates.getCandidates();
}
Assert.state(this.autoConfigurations != null, "'autoConfigurations' must not be null");
return this.autoConfigurations;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.EnvironmentAware;
@@ -54,6 +56,6 @@ public interface AutoConfigurationImportFilter {
* {@code autoConfigurationClasses} parameter. Entries containing {@code false} will
* not be imported.
*/
boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
boolean[] match(@Nullable String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
@@ -89,23 +90,27 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
private final Class<?> autoConfigurationAnnotation;
@SuppressWarnings("NullAway.Init")
private ConfigurableListableBeanFactory beanFactory;
@SuppressWarnings("NullAway.Init")
private Environment environment;
@SuppressWarnings("NullAway.Init")
private ClassLoader beanClassLoader;
@SuppressWarnings("NullAway.Init")
private ResourceLoader resourceLoader;
private volatile ConfigurationClassFilter configurationClassFilter;
private volatile @Nullable ConfigurationClassFilter configurationClassFilter;
private volatile AutoConfigurationReplacements autoConfigurationReplacements;
private volatile @Nullable AutoConfigurationReplacements autoConfigurationReplacements;
public AutoConfigurationImportSelector() {
this(null);
}
AutoConfigurationImportSelector(Class<?> autoConfigurationAnnotation) {
AutoConfigurationImportSelector(@Nullable Class<?> autoConfigurationAnnotation) {
this.autoConfigurationAnnotation = (autoConfigurationAnnotation != null) ? autoConfigurationAnnotation
: AutoConfiguration.class;
}
@@ -168,7 +173,7 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
* @param metadata the annotation metadata
* @return annotation attributes
*/
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
protected @Nullable AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.state(attributes != null, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
@@ -192,7 +197,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
@Nullable AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
@@ -237,10 +243,12 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
* attributes}
* @return exclusions or an empty set
*/
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
protected Set<String> getExclusions(AnnotationMetadata metadata, @Nullable AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(asList(attributes, "excludeName"));
if (attributes != null) {
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(asList(attributes, "excludeName"));
}
excluded.addAll(getExcludeAutoConfigurationsProperty());
return getAutoConfigurationReplacements().replaceAll(excluded);
}
@@ -389,7 +397,7 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
@Nullable String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
@@ -426,15 +434,18 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
@SuppressWarnings("NullAway.Init")
private ClassLoader beanClassLoader;
@SuppressWarnings("NullAway.Init")
private BeanFactory beanFactory;
@SuppressWarnings("NullAway.Init")
private ResourceLoader resourceLoader;
private AutoConfigurationMetadata autoConfigurationMetadata;
private @Nullable AutoConfigurationMetadata autoConfigurationMetadata;
private AutoConfigurationReplacements autoConfigurationReplacements;
private @Nullable AutoConfigurationReplacements autoConfigurationReplacements;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
@@ -488,10 +499,16 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
.collect(Collectors.toCollection(LinkedHashSet::new));
processedConfigurations.removeAll(allExclusions);
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.map(this::getEntry)
.toList();
}
private Entry getEntry(String importClassName) {
AnnotationMetadata metadata = this.entries.get(importClassName);
Assert.state(metadata != null, "'metadata' must not be null");
return new Entry(metadata, importClassName);
}
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
if (this.autoConfigurationMetadata == null) {
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
@@ -501,6 +518,8 @@ public class AutoConfigurationImportSelector implements DeferredImportSelector,
private List<String> sortAutoConfigurations(Set<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
Assert.state(this.autoConfigurationReplacements != null,
"'autoConfigurationReplacements' must not be null");
return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata,
this.autoConfigurationReplacements::replace)
.getInPriorityOrder(configurations);
@@ -18,6 +18,10 @@ package org.springframework.boot.autoconfigure;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Contract;
/**
* Provides access to meta-data written by the auto-configure annotation processor.
*
@@ -40,7 +44,7 @@ public interface AutoConfigurationMetadata {
* @param key the meta-data key
* @return the meta-data value or {@code null}
*/
Integer getInteger(String className, String key);
@Nullable Integer getInteger(String className, String key);
/**
* Get an {@link Integer} value from the meta-data.
@@ -49,7 +53,8 @@ public interface AutoConfigurationMetadata {
* @param defaultValue the default value
* @return the meta-data value or {@code defaultValue}
*/
Integer getInteger(String className, String key, Integer defaultValue);
@Contract("_, _, !null -> !null")
@Nullable Integer getInteger(String className, String key, @Nullable Integer defaultValue);
/**
* Get a {@link Set} value from the meta-data.
@@ -57,7 +62,7 @@ public interface AutoConfigurationMetadata {
* @param key the meta-data key
* @return the meta-data value or {@code null}
*/
Set<String> getSet(String className, String key);
@Nullable Set<String> getSet(String className, String key);
/**
* Get a {@link Set} value from the meta-data.
@@ -66,7 +71,8 @@ public interface AutoConfigurationMetadata {
* @param defaultValue the default value
* @return the meta-data value or {@code defaultValue}
*/
Set<String> getSet(String className, String key, Set<String> defaultValue);
@Contract("_, _, !null -> !null")
@Nullable Set<String> getSet(String className, String key, @Nullable Set<String> defaultValue);
/**
* Get an {@link String} value from the meta-data.
@@ -74,7 +80,7 @@ public interface AutoConfigurationMetadata {
* @param key the meta-data key
* @return the meta-data value or {@code null}
*/
String get(String className, String key);
@Nullable String get(String className, String key);
/**
* Get an {@link String} value from the meta-data.
@@ -83,6 +89,7 @@ public interface AutoConfigurationMetadata {
* @param defaultValue the default value
* @return the meta-data value or {@code defaultValue}
*/
String get(String className, String key, String defaultValue);
@Contract("_, _, !null -> !null")
@Nullable String get(String className, String key, @Nullable String defaultValue);
}
@@ -22,6 +22,8 @@ import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;
@@ -42,7 +44,7 @@ final class AutoConfigurationMetadataLoader {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
static AutoConfigurationMetadata loadMetadata(@Nullable ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
@@ -78,34 +80,34 @@ final class AutoConfigurationMetadataLoader {
}
@Override
public Integer getInteger(String className, String key) {
public @Nullable Integer getInteger(String className, String key) {
return getInteger(className, key, null);
}
@Override
public Integer getInteger(String className, String key, Integer defaultValue) {
public @Nullable Integer getInteger(String className, String key, @Nullable Integer defaultValue) {
String value = get(className, key);
return (value != null) ? Integer.valueOf(value) : defaultValue;
}
@Override
public Set<String> getSet(String className, String key) {
public @Nullable Set<String> getSet(String className, String key) {
return getSet(className, key, null);
}
@Override
public Set<String> getSet(String className, String key, Set<String> defaultValue) {
public @Nullable Set<String> getSet(String className, String key, @Nullable Set<String> defaultValue) {
String value = get(className, key);
return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
}
@Override
public String get(String className, String key) {
public @Nullable String get(String className, String key) {
return get(className, key, null);
}
@Override
public String get(String className, String key, String defaultValue) {
public @Nullable String get(String className, String key, @Nullable String defaultValue) {
String value = this.properties.getProperty(className + "." + key);
return (value != null) ? value : defaultValue;
}
@@ -30,12 +30,14 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.annotation.DeterminableImports;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -105,8 +107,9 @@ public abstract class AutoConfigurationPackages {
private static void addBasePackages(BeanDefinition beanDefinition, String[] additionalBasePackages) {
ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
if (constructorArgumentValues.hasIndexedArgumentValue(0)) {
String[] existingPackages = (String[]) constructorArgumentValues.getIndexedArgumentValue(0, String[].class)
.getValue();
ValueHolder indexedArgumentValue = constructorArgumentValues.getIndexedArgumentValue(0, String[].class);
Assert.state(indexedArgumentValue != null, "'indexedArgumentValue' must not be null");
String[] existingPackages = (String[]) indexedArgumentValue.getValue();
constructorArgumentValues.addIndexedArgumentValue(0,
Stream.concat(Stream.of(existingPackages), Stream.of(additionalBasePackages))
.distinct()
@@ -145,6 +148,7 @@ public abstract class AutoConfigurationPackages {
PackageImports(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
Assert.state(attributes != null, "'attributes' must not be null");
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
@@ -28,6 +28,8 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert;
@@ -88,7 +90,7 @@ final class AutoConfigurationReplacements {
* @param classLoader class loader to use for loading
* @return list of names of annotated classes
*/
static AutoConfigurationReplacements load(Class<?> annotation, ClassLoader classLoader) {
static AutoConfigurationReplacements load(Class<?> annotation, @Nullable ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format(LOCATION, annotation.getName());
@@ -101,7 +103,7 @@ final class AutoConfigurationReplacements {
return new AutoConfigurationReplacements(replacements);
}
private static ClassLoader decideClassloader(ClassLoader classLoader) {
private static ClassLoader decideClassloader(@Nullable ClassLoader classLoader) {
if (classLoader == null) {
return ImportCandidates.class.getClassLoader();
}
@@ -30,6 +30,8 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.function.UnaryOperator;
import org.jspecify.annotations.Nullable;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -47,12 +49,13 @@ class AutoConfigurationSorter {
private final MetadataReaderFactory metadataReaderFactory;
private final AutoConfigurationMetadata autoConfigurationMetadata;
private final @Nullable AutoConfigurationMetadata autoConfigurationMetadata;
private final UnaryOperator<String> replacementMapper;
private final @Nullable UnaryOperator<String> replacementMapper;
AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory,
AutoConfigurationMetadata autoConfigurationMetadata, UnaryOperator<String> replacementMapper) {
@Nullable AutoConfigurationMetadata autoConfigurationMetadata,
@Nullable UnaryOperator<String> replacementMapper) {
Assert.notNull(metadataReaderFactory, "'metadataReaderFactory' must not be null");
this.metadataReaderFactory = metadataReaderFactory;
this.autoConfigurationMetadata = autoConfigurationMetadata;
@@ -91,7 +94,7 @@ class AutoConfigurationSorter {
}
private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, Set<String> sorted,
Set<String> processing, String current) {
Set<String> processing, @Nullable String current) {
if (current == null) {
current = toSort.remove(0);
}
@@ -118,7 +121,7 @@ class AutoConfigurationSorter {
private final Map<String, AutoConfigurationClass> classes = new LinkedHashMap<>();
AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,
AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {
@Nullable AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {
addToClasses(metadataReaderFactory, autoConfigurationMetadata, classNames, true);
}
@@ -127,7 +130,8 @@ class AutoConfigurationSorter {
}
private void addToClasses(MetadataReaderFactory metadataReaderFactory,
AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames, boolean required) {
@Nullable AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames,
boolean required) {
for (String className : classNames) {
if (!this.classes.containsKey(className)) {
AutoConfigurationClass autoConfigurationClass = new AutoConfigurationClass(className,
@@ -147,7 +151,9 @@ class AutoConfigurationSorter {
}
AutoConfigurationClass get(String className) {
return this.classes.get(className);
AutoConfigurationClass autoConfigurationClass = this.classes.get(className);
Assert.state(autoConfigurationClass != null, "'autoConfigurationClass' must not be null");
return autoConfigurationClass;
}
Set<String> getClassesRequestedAfter(String className) {
@@ -168,16 +174,16 @@ class AutoConfigurationSorter {
private final MetadataReaderFactory metadataReaderFactory;
private final AutoConfigurationMetadata autoConfigurationMetadata;
private final @Nullable AutoConfigurationMetadata autoConfigurationMetadata;
private volatile AnnotationMetadata annotationMetadata;
private volatile @Nullable AnnotationMetadata annotationMetadata;
private volatile Set<String> before;
private volatile @Nullable Set<String> before;
private volatile Set<String> after;
private volatile @Nullable Set<String> after;
AutoConfigurationClass(String className, MetadataReaderFactory metadataReaderFactory,
AutoConfigurationMetadata autoConfigurationMetadata) {
@Nullable AutoConfigurationMetadata autoConfigurationMetadata) {
this.className = className;
this.metadataReaderFactory = metadataReaderFactory;
this.autoConfigurationMetadata = autoConfigurationMetadata;
@@ -210,12 +216,15 @@ class AutoConfigurationSorter {
}
private Set<String> getClassNames(String metadataKey, Class<? extends Annotation> annotation) {
Set<String> annotationValue = wasProcessed()
? this.autoConfigurationMetadata.getSet(this.className, metadataKey, Collections.emptySet())
: getAnnotationValue(annotation);
Set<String> annotationValue = wasProcessed() ? getSet(metadataKey) : getAnnotationValue(annotation);
return applyReplacements(annotationValue);
}
private Set<String> getSet(String metadataKey) {
Assert.state(this.autoConfigurationMetadata != null, "'autoConfigurationMetadata' must not be null");
return this.autoConfigurationMetadata.getSet(this.className, metadataKey, Collections.emptySet());
}
private Set<String> applyReplacements(Set<String> values) {
if (AutoConfigurationSorter.this.replacementMapper == null) {
return values;
@@ -229,10 +238,11 @@ class AutoConfigurationSorter {
private int getOrder() {
if (wasProcessed()) {
Assert.state(this.autoConfigurationMetadata != null, "'autoConfigurationMetadata' must not be null");
return this.autoConfigurationMetadata.getInteger(this.className, "AutoConfigureOrder",
AutoConfigureOrder.DEFAULT_ORDER);
}
Map<String, Object> attributes = getAnnotationMetadata()
Map<String, @Nullable Object> attributes = getAnnotationMetadata()
.getAnnotationAttributes(AutoConfigureOrder.class.getName());
return (attributes != null) ? (Integer) attributes.get("value") : AutoConfigureOrder.DEFAULT_ORDER;
}
@@ -243,8 +253,8 @@ class AutoConfigurationSorter {
}
private Set<String> getAnnotationValue(Class<?> annotation) {
Map<String, Object> attributes = getAnnotationMetadata().getAnnotationAttributes(annotation.getName(),
true);
Map<String, @Nullable Object> attributes = getAnnotationMetadata()
.getAnnotationAttributes(annotation.getName(), true);
if (attributes == null) {
return Collections.emptySet();
}
@@ -28,6 +28,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.annotation.DeterminableImports;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -71,12 +73,13 @@ class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelec
}
@Override
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
protected @Nullable AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
return null;
}
@Override
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
@Nullable AnnotationAttributes attributes) {
List<String> candidates = new ArrayList<>();
Map<Class<?>, List<Annotation>> annotations = getAnnotations(metadata);
annotations.forEach(
@@ -93,13 +96,13 @@ class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelec
private Collection<String> getConfigurationsForAnnotation(Class<?> source, Annotation annotation) {
String[] classes = (String[]) AnnotationUtils.getAnnotationAttributes(annotation, true).get("classes");
if (classes.length > 0) {
if (classes != null && classes.length > 0) {
return Arrays.asList(classes);
}
return loadFactoryNames(source).stream().map(this::mapFactoryName).filter(Objects::nonNull).toList();
}
private String mapFactoryName(String name) {
private @Nullable String mapFactoryName(String name) {
if (!name.startsWith(OPTIONAL_PREFIX)) {
return name;
}
@@ -117,7 +120,7 @@ class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelec
}
@Override
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
protected Set<String> getExclusions(AnnotationMetadata metadata, @Nullable AnnotationAttributes attributes) {
Set<String> exclusions = new LinkedHashSet<>();
Class<?> source = ClassUtils.resolveClassName(metadata.getClassName(), getBeanClassLoader());
for (String annotationName : ANNOTATION_NAMES) {
@@ -148,7 +151,7 @@ class ImportAutoConfigurationImportSelector extends AutoConfigurationImportSelec
return Collections.unmodifiableMap(annotations);
}
private void collectAnnotations(Class<?> source, MultiValueMap<Class<?>, Annotation> annotations,
private void collectAnnotations(@Nullable Class<?> source, MultiValueMap<Class<?>, Annotation> annotations,
HashSet<Class<?>> seen) {
if (source != null && seen.add(source)) {
for (Annotation annotation : source.getDeclaredAnnotations()) {
@@ -191,6 +191,7 @@ class SharedMetadataReaderFactoryContextInitializer implements
implements FactoryBean<ConcurrentReferenceCachingMetadataReaderFactory>, ResourceLoaderAware,
ApplicationListener<ContextRefreshedEvent> {
@SuppressWarnings("NullAway.Init")
private ConcurrentReferenceCachingMetadataReaderFactory metadataReaderFactory;
@Override
@@ -17,4 +17,7 @@
/**
* Auto-configuration for admin-related features.
*/
@NullMarked
package org.springframework.boot.autoconfigure.admin;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for Spring AOP.
*/
@NullMarked
package org.springframework.boot.autoconfigure.aop;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for application availability features.
*/
@NullMarked
package org.springframework.boot.autoconfigure.availability;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration base classes for Caching support.
*/
@NullMarked
package org.springframework.boot.autoconfigure.cache;
import org.jspecify.annotations.NullMarked;
@@ -22,6 +22,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
@@ -153,8 +155,8 @@ public abstract class AbstractNestedCondition extends SpringBootCondition implem
@SuppressWarnings("unchecked")
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(),
true);
MultiValueMap<String, @Nullable Object> attributes = metadata
.getAllAnnotationAttributes(Conditional.class.getName(), true);
Object values = (attributes != null) ? attributes.get("value") : null;
return (List<String[]>) ((values != null) ? values : Collections.emptyList());
}
@@ -30,6 +30,8 @@ import java.util.TreeMap;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
@@ -58,7 +60,7 @@ public final class ConditionEvaluationReport {
private boolean addedAncestorOutcomes;
private ConditionEvaluationReport parent;
private @Nullable ConditionEvaluationReport parent;
private final List<String> exclusions = new ArrayList<>();
@@ -154,7 +156,7 @@ public final class ConditionEvaluationReport {
* The parent report (from a parent BeanFactory if there is one).
* @return the parent report (or null if there isn't one)
*/
public ConditionEvaluationReport getParent() {
public @Nullable ConditionEvaluationReport getParent() {
return this.parent;
}
@@ -164,7 +166,7 @@ public final class ConditionEvaluationReport {
* @param beanFactory the bean factory (may be {@code null})
* @return the {@link ConditionEvaluationReport} or {@code null}
*/
public static ConditionEvaluationReport find(BeanFactory beanFactory) {
public static @Nullable ConditionEvaluationReport find(BeanFactory beanFactory) {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
return ConditionEvaluationReport.get((ConfigurableListableBeanFactory) beanFactory);
}
@@ -191,7 +193,7 @@ public final class ConditionEvaluationReport {
}
}
private static void locateParent(BeanFactory beanFactory, ConditionEvaluationReport report) {
private static void locateParent(@Nullable BeanFactory beanFactory, ConditionEvaluationReport report) {
if (beanFactory != null && report.parent == null && beanFactory.containsBean(BEAN_NAME)) {
report.parent = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);
}
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.condition;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -32,7 +34,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurationImportListener;
class ConditionEvaluationReportAutoConfigurationImportListener
implements AutoConfigurationImportListener, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
private @Nullable ConfigurableListableBeanFactory beanFactory;
@Override
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
@@ -23,6 +23,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -38,13 +40,13 @@ import org.springframework.util.StringUtils;
*/
public final class ConditionMessage {
private final String message;
private final @Nullable String message;
private ConditionMessage() {
this(null);
}
private ConditionMessage(String message) {
private ConditionMessage(@Nullable String message) {
this.message = message;
}
@@ -87,7 +89,7 @@ public final class ConditionMessage {
* @param message the message to append
* @return a new {@link ConditionMessage} instance
*/
public ConditionMessage append(String message) {
public ConditionMessage append(@Nullable String message) {
if (!StringUtils.hasLength(message)) {
return this;
}
@@ -158,7 +160,7 @@ public final class ConditionMessage {
* @param messages the source messages (may be {@code null})
* @return a new {@link ConditionMessage} instance
*/
public static ConditionMessage of(Collection<? extends ConditionMessage> messages) {
public static ConditionMessage of(@Nullable Collection<? extends ConditionMessage> messages) {
ConditionMessage result = new ConditionMessage();
if (messages != null) {
for (ConditionMessage message : messages) {
@@ -296,7 +298,7 @@ public final class ConditionMessage {
* @param reason the reason for the message
* @return a built {@link ConditionMessage}
*/
public ConditionMessage because(String reason) {
public ConditionMessage because(@Nullable String reason) {
if (StringUtils.hasLength(reason)) {
return new ConditionMessage(ConditionMessage.this,
StringUtils.hasLength(this.condition) ? this.condition + " " + reason : reason);
@@ -343,7 +345,7 @@ public final class ConditionMessage {
* @param items the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Object... items) {
public ConditionMessage items(Object @Nullable ... items) {
return items(Style.NORMAL, items);
}
@@ -355,7 +357,7 @@ public final class ConditionMessage {
* @param items the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Style style, Object... items) {
public ConditionMessage items(Style style, Object @Nullable ... items) {
return items(style, (items != null) ? Arrays.asList(items) : null);
}
@@ -366,7 +368,7 @@ public final class ConditionMessage {
* @param items the source of the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Collection<?> items) {
public ConditionMessage items(@Nullable Collection<?> items) {
return items(Style.NORMAL, items);
}
@@ -378,7 +380,7 @@ public final class ConditionMessage {
* @param items the source of the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Style style, Collection<?> items) {
public ConditionMessage items(Style style, @Nullable Collection<?> items) {
Assert.notNull(style, "'style' must not be null");
StringBuilder message = new StringBuilder(this.reason);
items = style.applyTo(items);
@@ -408,7 +410,7 @@ public final class ConditionMessage {
NORMAL {
@Override
protected Object applyToItem(Object item) {
protected @Nullable Object applyToItem(@Nullable Object item) {
return item;
}
@@ -420,24 +422,27 @@ public final class ConditionMessage {
QUOTE {
@Override
protected String applyToItem(Object item) {
protected @Nullable String applyToItem(@Nullable Object item) {
return (item != null) ? "'" + item + "'" : null;
}
};
public Collection<?> applyTo(Collection<?> items) {
public @Nullable Collection<?> applyTo(@Nullable Collection<?> items) {
if (items == null) {
return null;
}
List<Object> result = new ArrayList<>(items.size());
for (Object item : items) {
result.add(applyToItem(item));
Object applied = applyToItem(item);
if (applied != null) {
result.add(applied);
}
}
return result;
}
protected abstract Object applyToItem(Object item);
protected abstract @Nullable Object applyToItem(@Nullable Object item);
}
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.condition;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -111,13 +113,13 @@ public class ConditionOutcome {
* Return an outcome message or {@code null}.
* @return the message or {@code null}
*/
public String getMessage() {
public @Nullable String getMessage() {
return this.message.isEmpty() ? null : this.message.toString();
}
/**
* Return an outcome message or {@code null}.
* @return the message or {@code null}
* Return an outcome message.
* @return the message
*/
public ConditionMessage getConditionMessage() {
return this.message;
@@ -21,12 +21,15 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -39,28 +42,33 @@ import org.springframework.util.CollectionUtils;
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
@SuppressWarnings("NullAway.Init")
private BeanFactory beanFactory;
@SuppressWarnings("NullAway.Init")
private ClassLoader beanClassLoader;
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
public boolean[] match(@Nullable String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
@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) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
String autoConfigurationClass = autoConfigurationClasses[i];
Assert.state(autoConfigurationClass != null, "'autoConfigurationClass' must not be null");
logOutcome(autoConfigurationClass, outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
report.recordConditionEvaluation(autoConfigurationClass, this, outcomes[i]);
}
}
}
return match;
}
protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
protected abstract @Nullable ConditionOutcome[] getOutcomes(@Nullable String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata);
@Override
@@ -81,8 +89,8 @@ abstract class FilteringSpringBootCondition extends SpringBootCondition
this.beanClassLoader = classLoader;
}
protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
ClassLoader classLoader) {
protected final List<String> filter(@Nullable Collection<String> classNames, ClassNameFilter classNameFilter,
@Nullable ClassLoader classLoader) {
if (CollectionUtils.isEmpty(classNames)) {
return Collections.emptyList();
}
@@ -103,7 +111,8 @@ abstract class FilteringSpringBootCondition extends SpringBootCondition
* @return a resolved class
* @throws ClassNotFoundException if the class cannot be found
*/
protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
protected static Class<?> resolve(String className, @Nullable ClassLoader classLoader)
throws ClassNotFoundException {
if (classLoader != null) {
return Class.forName(className, false, classLoader);
}
@@ -115,7 +124,7 @@ abstract class FilteringSpringBootCondition extends SpringBootCondition
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
public boolean matches(String className, @Nullable ClassLoader classLoader) {
return isPresent(className, classLoader);
}
@@ -124,15 +133,15 @@ abstract class FilteringSpringBootCondition extends SpringBootCondition
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
public boolean matches(String className, @Nullable ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
abstract boolean matches(String className, ClassLoader classLoader);
abstract boolean matches(String className, @Nullable ClassLoader classLoader);
private static boolean isPresent(String className, ClassLoader classLoader) {
private static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
@@ -34,6 +34,8 @@ import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
@@ -60,6 +62,7 @@ import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -90,9 +93,9 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
}
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
protected final @Nullable ConditionOutcome[] getOutcomes(@Nullable String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
@Nullable ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
@@ -108,7 +111,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return outcomes;
}
private ConditionOutcome getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) {
private @Nullable ConditionOutcome getOutcome(@Nullable Set<String> requiredBeanTypes,
Class<? extends Annotation> annotation) {
List<String> missing = filter(requiredBeanTypes, ClassNameFilter.MISSING, getBeanClassLoader());
if (!missing.isEmpty()) {
ConditionMessage message = ConditionMessage.forCondition(annotation)
@@ -171,7 +175,9 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return ConditionOutcome
.match(spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans));
}
Map<String, BeanDefinition> beanDefinitions = getBeanDefinitions(spec.context.getBeanFactory(), allBeans,
ConfigurableListableBeanFactory beanFactory = spec.context.getBeanFactory();
Assert.state(beanFactory != null, "'beanFactory' must not be null");
Map<String, BeanDefinition> beanDefinitions = getBeanDefinitions(beanFactory, allBeans,
spec.getStrategy() == SearchStrategy.ALL);
List<String> primaryBeans = getPrimaryBeans(beanDefinitions);
if (primaryBeans.size() == 1) {
@@ -249,6 +255,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
private ConfigurableListableBeanFactory getSearchBeanFactory(Spec<?> spec) {
ConfigurableListableBeanFactory beanFactory = spec.getContext().getBeanFactory();
Assert.state(beanFactory != null, "'beanFactory' must not be null'");
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.state(parent instanceof ConfigurableListableBeanFactory,
@@ -269,8 +276,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return matchedNames;
}
private boolean isCandidate(ConfigurableListableBeanFactory beanFactory, String name, BeanDefinition definition,
Set<String> ignoredBeans) {
private boolean isCandidate(ConfigurableListableBeanFactory beanFactory, String name,
@Nullable BeanDefinition definition, Set<String> ignoredBeans) {
return (!ignoredBeans.contains(name)) && (definition == null
|| isAutowireCandidate(beanFactory, name, definition) && isDefaultCandidate(definition));
}
@@ -316,12 +323,14 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return (result != null) ? result : Collections.emptyMap();
}
private Map<String, BeanDefinition> collectBeanDefinitionsForType(ListableBeanFactory beanFactory,
private @Nullable Map<String, BeanDefinition> collectBeanDefinitionsForType(ListableBeanFactory beanFactory,
boolean considerHierarchy, ResolvableType type, Set<ResolvableType> parameterizedContainers,
Map<String, BeanDefinition> result) {
@Nullable Map<String, BeanDefinition> result) {
result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory);
for (ResolvableType parameterizedContainer : parameterizedContainers) {
ResolvableType generic = ResolvableType.forClassWithGenerics(parameterizedContainer.resolve(), type);
Class<?> resolved = parameterizedContainer.resolve();
Assert.state(resolved != null, "'resolved' must not be null");
ResolvableType generic = ResolvableType.forClassWithGenerics(resolved, type);
result = putAll(result, beanFactory.getBeanNamesForType(generic, true, false), beanFactory);
}
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory) {
@@ -334,7 +343,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return result;
}
private Map<String, BeanDefinition> getBeanDefinitionsForAnnotation(ClassLoader classLoader,
private Map<String, BeanDefinition> getBeanDefinitionsForAnnotation(@Nullable ClassLoader classLoader,
ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError {
Map<String, BeanDefinition> result = null;
try {
@@ -348,13 +357,14 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
}
@SuppressWarnings("unchecked")
private Class<? extends Annotation> resolveAnnotationType(ClassLoader classLoader, String type)
private Class<? extends Annotation> resolveAnnotationType(@Nullable ClassLoader classLoader, String type)
throws ClassNotFoundException {
return (Class<? extends Annotation>) resolve(type, classLoader);
}
private Map<String, BeanDefinition> collectBeanDefinitionsForAnnotation(ListableBeanFactory beanFactory,
Class<? extends Annotation> annotationType, boolean considerHierarchy, Map<String, BeanDefinition> result) {
private @Nullable Map<String, BeanDefinition> collectBeanDefinitionsForAnnotation(ListableBeanFactory beanFactory,
Class<? extends Annotation> annotationType, boolean considerHierarchy,
@Nullable Map<String, BeanDefinition> result) {
result = putAll(result, getBeanNamesForAnnotation(beanFactory, annotationType), beanFactory);
if (considerHierarchy) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
@@ -477,7 +487,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return matches;
}
private BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName,
private @Nullable BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName,
boolean considerHierarchy) {
if (beanFactory.containsBeanDefinition(beanName)) {
return beanFactory.getBeanDefinition(beanName);
@@ -489,7 +499,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return null;
}
private static Set<String> addAll(Set<String> result, Collection<String> additional) {
private static @Nullable Set<String> addAll(@Nullable Set<String> result, @Nullable Collection<String> additional) {
if (CollectionUtils.isEmpty(additional)) {
return result;
}
@@ -498,8 +508,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return result;
}
private static Map<String, BeanDefinition> putAll(Map<String, BeanDefinition> result, String[] beanNames,
ListableBeanFactory beanFactory) {
private static @Nullable Map<String, BeanDefinition> putAll(@Nullable Map<String, BeanDefinition> result,
String[] beanNames, ListableBeanFactory beanFactory) {
if (ObjectUtils.isEmpty(beanNames)) {
return result;
}
@@ -517,7 +527,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return result;
}
private static BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) {
private static @Nullable BeanDefinition getBeanDefinition(String beanName,
ConfigurableListableBeanFactory beanFactory) {
try {
return beanFactory.getBeanDefinition(beanName);
}
@@ -548,7 +559,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
private final Set<ResolvableType> parameterizedContainers;
private final SearchStrategy strategy;
private final @Nullable SearchStrategy strategy;
Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations,
Class<A> annotationType) {
@@ -621,7 +632,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return resolved;
}
protected void validate(BeanTypeDeductionException ex) {
protected void validate(@Nullable BeanTypeDeductionException ex) {
if (!hasAtLeastOneElement(getTypes(), getNames(), getAnnotations())) {
String message = getAnnotationName() + " did not specify a bean using type, name or annotation";
if (ex == null) {
@@ -671,13 +682,13 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return returnType;
}
private boolean isParameterizedContainer(Class<?> type) {
private boolean isParameterizedContainer(@Nullable Class<?> type) {
return (type != null) && this.parameterizedContainers.stream()
.map(ResolvableType::resolve)
.anyMatch((container) -> container != null && container.isAssignableFrom(type));
}
private ResolvableType getMethodReturnType(MethodMetadata metadata, ClassLoader classLoader)
private ResolvableType getMethodReturnType(MethodMetadata metadata, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Class<?> declaringClass = resolve(metadata.getDeclaringClassName(), classLoader);
Method beanMethod = findBeanMethod(declaringClass, metadata.getMethodName());
@@ -698,7 +709,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
throw new IllegalStateException("Unable to find bean method " + methodName);
}
private boolean isBeanMethod(Method method) {
@Contract("null -> false")
private boolean isBeanMethod(@Nullable Method method) {
return method != null && MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.isPresent(Bean.class);
}
@@ -761,8 +773,10 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
string.append(StringUtils.collectionToCommaDelimitedString(this.ignoredTypes));
string.append("; ");
}
string.append("SearchStrategy: ");
string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH));
if (this.strategy != null) {
string.append("SearchStrategy: ");
string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH));
}
string.append(")");
return string.toString();
}
@@ -789,7 +803,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
}
@Override
protected void validate(BeanTypeDeductionException ex) {
protected void validate(@Nullable BeanTypeDeductionException ex) {
Assert.isTrue(getTypes().size() == 1,
() -> getAnnotationName() + " annotations must specify only one type (got "
+ StringUtils.collectionToCommaDelimitedString(getTypes()) + ")");
@@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
@@ -44,7 +46,7 @@ import org.springframework.util.StringUtils;
class OnClassCondition extends FilteringSpringBootCondition {
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
protected final @Nullable ConditionOutcome[] getOutcomes(@Nullable String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// Split the work and perform half in a background thread if more than one
// processor is available. Using a single additional thread seems to offer the
@@ -59,22 +61,22 @@ class OnClassCondition extends FilteringSpringBootCondition {
}
}
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
private @Nullable ConditionOutcome[] resolveOutcomesThreaded(@Nullable String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
int split = autoConfigurationClasses.length / 2;
OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
autoConfigurationMetadata);
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
@Nullable ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
@Nullable ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
@Nullable ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
private OutcomesResolver createOutcomesResolver(@Nullable String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end,
autoConfigurationMetadata, getBeanClassLoader());
@@ -112,8 +114,9 @@ class OnClassCondition extends FilteringSpringBootCondition {
return ConditionOutcome.match(matchMessage);
}
private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
private @Nullable List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
MultiValueMap<String, @Nullable Object> attributes = metadata
.getAllAnnotationAttributes(annotationType.getName(), true);
if (attributes == null) {
return null;
}
@@ -123,7 +126,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
return candidates;
}
private void addAll(List<String> list, List<Object> itemsToAdd) {
private void addAll(List<String> list, @Nullable List<@Nullable Object> itemsToAdd) {
if (itemsToAdd != null) {
for (Object item : itemsToAdd) {
Collections.addAll(list, (String[]) item);
@@ -133,7 +136,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
private interface OutcomesResolver {
ConditionOutcome[] resolveOutcomes();
@Nullable ConditionOutcome[] resolveOutcomes();
}
@@ -141,9 +144,9 @@ class OnClassCondition extends FilteringSpringBootCondition {
private final Thread thread;
private volatile ConditionOutcome[] outcomes;
private volatile @Nullable ConditionOutcome @Nullable [] outcomes;
private volatile Throwable failure;
private volatile @Nullable Throwable failure;
private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
this.thread = new Thread(() -> {
@@ -158,7 +161,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
}
@Override
public ConditionOutcome[] resolveOutcomes() {
public @Nullable ConditionOutcome[] resolveOutcomes() {
try {
this.thread.join();
}
@@ -169,7 +172,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
if (failure != null) {
ReflectionUtils.rethrowRuntimeException(failure);
}
ConditionOutcome[] outcomes = this.outcomes;
@Nullable ConditionOutcome[] outcomes = this.outcomes;
return (outcomes != null) ? outcomes : new ConditionOutcome[0];
}
@@ -177,7 +180,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
private static final class StandardOutcomesResolver implements OutcomesResolver {
private final String[] autoConfigurationClasses;
private final @Nullable String[] autoConfigurationClasses;
private final int start;
@@ -187,7 +190,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
private final ClassLoader beanClassLoader;
private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
private StandardOutcomesResolver(@Nullable String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {
this.autoConfigurationClasses = autoConfigurationClasses;
this.start = start;
@@ -197,13 +200,13 @@ class OnClassCondition extends FilteringSpringBootCondition {
}
@Override
public ConditionOutcome[] resolveOutcomes() {
public @Nullable ConditionOutcome[] resolveOutcomes() {
return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
private @Nullable ConditionOutcome[] getOutcomes(@Nullable String[] autoConfigurationClasses, int start,
int end, AutoConfigurationMetadata autoConfigurationMetadata) {
@Nullable ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for (int i = start; i < end; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
@@ -216,7 +219,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
return outcomes;
}
private ConditionOutcome getOutcome(String candidates) {
private @Nullable ConditionOutcome getOutcome(String candidates) {
try {
if (!candidates.contains(",")) {
return getOutcome(candidates, this.beanClassLoader);
@@ -234,7 +237,7 @@ class OnClassCondition extends FilteringSpringBootCondition {
return null;
}
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
private @Nullable ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
if (ClassNameFilter.MISSING.matches(className, classLoader)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class")
@@ -18,11 +18,14 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
/**
* {@link Condition} that checks for a required {@link CloudPlatform}.
@@ -34,8 +37,11 @@ class OnCloudPlatformCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnCloudPlatform.class.getName());
Map<String, @Nullable Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnCloudPlatform.class.getName());
Assert.state(attributes != null, "'attributes' must not be null");
CloudPlatform cloudPlatform = (CloudPlatform) attributes.get("value");
Assert.state(cloudPlatform != null, "'cloudPlatform' must not be null");
return getMatchOutcome(context.getEnvironment(), cloudPlatform);
}
@@ -16,6 +16,10 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -24,6 +28,7 @@ import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
/**
* A Condition that evaluates a SpEL expression.
@@ -37,8 +42,11 @@ class OnExpressionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String expression = (String) metadata.getAnnotationAttributes(ConditionalOnExpression.class.getName())
.get("value");
Map<String, @Nullable Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnExpression.class.getName());
Assert.state(attributes != null, "'attributes' must not be null");
String expression = (String) attributes.get("value");
Assert.state(expression != null, "'expression' must not be null");
expression = wrapIfNecessary(expression);
ConditionMessage.Builder messageBuilder = ConditionMessage.forCondition(ConditionalOnExpression.class,
"(" + expression + ")");
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
import org.springframework.boot.system.JavaVersion;
import org.springframework.context.annotation.Condition;
@@ -25,6 +27,7 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
/**
* {@link Condition} that checks for a required version of Java.
@@ -40,9 +43,12 @@ class OnJavaCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Map<String, @Nullable Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Assert.state(attributes != null, "'attributes' must not be null");
Range range = (Range) attributes.get("range");
Assert.state(range != null, "'range' must not be null");
JavaVersion version = (JavaVersion) attributes.get("value");
Assert.state(version != null, "'version' must not be null");
return getMatchOutcome(range, JVM_VERSION, version);
}
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.condition;
import javax.naming.NamingException;
import org.jspecify.annotations.Nullable;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
@@ -26,6 +28,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jndi.JndiLocatorDelegate;
import org.springframework.jndi.JndiLocatorSupport;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -39,9 +42,10 @@ class OnJndiCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
AnnotationAttributes annotationAttributes = AnnotationAttributes
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(ConditionalOnJndi.class.getName()));
String[] locations = annotationAttributes.getStringArray("value");
Assert.state(attributes != null, "'attributes' must not be null");
String[] locations = attributes.getStringArray("value");
try {
return getMatchOutcome(locations);
}
@@ -88,7 +92,7 @@ class OnJndiCondition extends SpringBootCondition {
this.locations = locations;
}
public String lookupFirstLocation() {
public @Nullable String lookupFirstLocation() {
for (String location : this.locations) {
try {
lookup(location);
@@ -22,6 +22,8 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
@@ -129,10 +131,16 @@ class OnPropertyCondition extends SpringBootCondition {
this.annotationType = annotationType;
this.prefix = (!annotationAttributes.containsKey("prefix")) ? "" : getPrefix(annotationAttributes);
this.names = getNames(annotationAttributes);
this.havingValue = annotationAttributes.get("havingValue").toString();
this.havingValue = getHavingValue(annotationAttributes);
this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
}
private static String getHavingValue(AnnotationAttributes annotationAttributes) {
Object havingValue = annotationAttributes.get("havingValue");
Assert.state(havingValue != null, "'havingValue' must not be null");
return havingValue.toString();
}
private String getPrefix(AnnotationAttributes annotationAttributes) {
String prefix = annotationAttributes.getString("prefix").trim();
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
@@ -144,6 +152,8 @@ class OnPropertyCondition extends SpringBootCondition {
private String[] getNames(AnnotationAttributes annotationAttributes) {
String[] value = (String[]) annotationAttributes.get("value");
String[] name = (String[]) annotationAttributes.get("name");
Assert.state(value != null, "'value' must not be null");
Assert.state(name != null, "'name' must not be null");
Assert.state(value.length > 0 || name.length > 0,
() -> "The name or value attribute of @%s must be specified"
.formatted(ClassUtils.getShortName(this.annotationType)));
@@ -169,7 +179,7 @@ class OnPropertyCondition extends SpringBootCondition {
}
}
private boolean isMatch(String value, String requiredValue) {
private boolean isMatch(@Nullable String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
}
@@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
@@ -40,11 +42,14 @@ class OnResourceCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
MultiValueMap<String, @Nullable Object> attributes = metadata
.getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);
Assert.state(attributes != null, "'attributes' must not be null");
ResourceLoader loader = context.getResourceLoader();
List<String> locations = new ArrayList<>();
collectValues(locations, attributes.get("resources"));
List<@Nullable Object> resources = attributes.get("resources");
Assert.state(resources != null, "'resources' must not be null");
collectValues(locations, resources);
Assert.state(!locations.isEmpty(),
"@ConditionalOnResource annotations must specify at least one resource location");
List<String> missing = new ArrayList<>();
@@ -64,10 +69,13 @@ class OnResourceCondition extends SpringBootCondition {
.items(locations));
}
private void collectValues(List<String> names, List<Object> values) {
for (Object value : values) {
for (Object item : (Object[]) value) {
names.add((String) item);
private void collectValues(List<String> names, List<@Nullable Object> resources) {
for (Object resource : resources) {
Object[] items = (Object[]) resource;
if (items != null) {
for (Object item : items) {
names.add((String) item);
}
}
}
}
@@ -18,11 +18,14 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.thread.Threading;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
/**
* {@link Condition} that checks for a required {@link Threading}.
@@ -34,8 +37,11 @@ class OnThreadingCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnThreading.class.getName());
Map<String, @Nullable Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnThreading.class.getName());
Assert.state(attributes != null, "'attributes' must not be null");
Threading threading = (Threading) attributes.get("value");
Assert.state(threading != null, "'threading' must not be null");
return getMatchOutcome(context.getEnvironment(), threading);
}
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.context.reactive.ConfigurableReactiveWebEnvironment;
@@ -27,6 +29,7 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.ConfigurableWebEnvironment;
@@ -49,9 +52,9 @@ class OnWebApplicationCondition extends FilteringSpringBootCondition {
private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";
@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
protected @Nullable ConditionOutcome[] getOutcomes(@Nullable String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
@Nullable ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
@@ -62,7 +65,7 @@ class OnWebApplicationCondition extends FilteringSpringBootCondition {
return outcomes;
}
private ConditionOutcome getOutcome(String type) {
private @Nullable ConditionOutcome getOutcome(@Nullable String type) {
if (type == null) {
return null;
}
@@ -157,9 +160,12 @@ class OnWebApplicationCondition extends FilteringSpringBootCondition {
}
private Type deduceType(AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
Map<String, @Nullable Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
if (attributes != null) {
return (Type) attributes.get("type");
Object type = attributes.get("type");
Assert.state(type != null, "'type' must not be null");
return (Type) type;
}
return Type.ANY;
}
@@ -17,4 +17,7 @@
/**
* {@code @Condition} annotations and supporting classes.
*/
@NullMarked
package org.springframework.boot.autoconfigure.condition;
import org.jspecify.annotations.NullMarked;
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.container;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.AttributeAccessor;
@@ -29,7 +31,7 @@ import org.springframework.core.AttributeAccessor;
* @author Phillip Webb
* @since 3.4.0
*/
public record ContainerImageMetadata(String imageName) {
public record ContainerImageMetadata(@Nullable String imageName) {
static final String NAME = ContainerImageMetadata.class.getName();
@@ -37,7 +39,7 @@ public record ContainerImageMetadata(String imageName) {
* Add this container image metadata to the given attributes.
* @param attributes the attributes to add the metadata to
*/
public void addTo(AttributeAccessor attributes) {
public void addTo(@Nullable AttributeAccessor attributes) {
if (attributes != null) {
attributes.setAttribute(NAME, this);
}
@@ -49,7 +51,7 @@ public record ContainerImageMetadata(String imageName) {
* @param attributes the attributes to check
* @return if metadata is present
*/
public static boolean isPresent(AttributeAccessor attributes) {
public static boolean isPresent(@Nullable AttributeAccessor attributes) {
return getFrom(attributes) != null;
}
@@ -59,7 +61,7 @@ public record ContainerImageMetadata(String imageName) {
* @param attributes the attributes
* @return the metadata or {@code null}
*/
public static ContainerImageMetadata getFrom(AttributeAccessor attributes) {
public static @Nullable ContainerImageMetadata getFrom(@Nullable AttributeAccessor attributes) {
return (attributes != null) ? (ContainerImageMetadata) attributes.getAttribute(NAME) : null;
}
@@ -17,4 +17,7 @@
/**
* Support classes related to auto-configuration involving containers.
*/
@NullMarked
package org.springframework.boot.autoconfigure.container;
import org.jspecify.annotations.NullMarked;
@@ -22,6 +22,8 @@ import java.time.Duration;
import java.util.List;
import java.util.Properties;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -92,7 +94,7 @@ public final class MessageSourceAutoConfiguration {
return messageSource;
}
private Properties loadCommonMessages(List<Resource> resources) {
private @Nullable Properties loadCommonMessages(@Nullable List<Resource> resources) {
if (CollectionUtils.isEmpty(resources)) {
return null;
}
@@ -135,7 +137,7 @@ public final class MessageSourceAutoConfiguration {
return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
}
private Resource[] getResources(ClassLoader classLoader, String name) {
private Resource[] getResources(@Nullable ClassLoader classLoader, String name) {
String target = name.replace('.', '/');
try {
return new PathMatchingResourcePatternResolver(classLoader)
@@ -151,7 +153,7 @@ public final class MessageSourceAutoConfiguration {
static class MessageSourceRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.resources().registerPattern("messages.properties").registerPattern("messages_*.properties");
}
@@ -23,6 +23,8 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.core.io.Resource;
@@ -49,7 +51,7 @@ public class MessageSourceProperties {
/**
* List of locale-independent property file resources containing common messages.
*/
private List<Resource> commonMessages;
private @Nullable List<Resource> commonMessages;
/**
* Message bundles encoding.
@@ -61,7 +63,7 @@ public class MessageSourceProperties {
* forever. If a duration suffix is not specified, seconds will be used.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration cacheDuration;
private @Nullable Duration cacheDuration;
/**
* Whether to fall back to the system Locale if no files for a specific Locale have
@@ -98,11 +100,11 @@ public class MessageSourceProperties {
this.encoding = encoding;
}
public Duration getCacheDuration() {
public @Nullable Duration getCacheDuration() {
return this.cacheDuration;
}
public void setCacheDuration(Duration cacheDuration) {
public void setCacheDuration(@Nullable Duration cacheDuration) {
this.cacheDuration = cacheDuration;
}
@@ -130,11 +132,11 @@ public class MessageSourceProperties {
this.useCodeAsDefaultMessage = useCodeAsDefaultMessage;
}
public List<Resource> getCommonMessages() {
public @Nullable List<Resource> getCommonMessages() {
return this.commonMessages;
}
public void setCommonMessages(List<Resource> commonMessages) {
public void setCommonMessages(@Nullable List<Resource> commonMessages) {
this.commonMessages = commonMessages;
}
@@ -17,4 +17,7 @@
/**
* Auto-configuration for the Spring context.
*/
@NullMarked
package org.springframework.boot.autoconfigure.context;
import org.jspecify.annotations.NullMarked;
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.data;
import java.lang.annotation.Annotation;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -48,15 +50,18 @@ import org.springframework.data.util.Streamable;
public abstract class AbstractRepositoryConfigurationSourceSupport
implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware {
@SuppressWarnings("NullAway.Init")
private ResourceLoader resourceLoader;
@SuppressWarnings("NullAway.Init")
private BeanFactory beanFactory;
@SuppressWarnings("NullAway.Init")
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
@Nullable BeanNameGenerator importBeanNameGenerator) {
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(
getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment);
delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension());
@@ -68,7 +73,7 @@ public abstract class AbstractRepositoryConfigurationSourceSupport
}
private AnnotationRepositoryConfigurationSource getConfigurationSource(BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
@Nullable BeanNameGenerator importBeanNameGenerator) {
AnnotationMetadata metadata = AnnotationMetadata.introspect(getConfiguration());
return new AutoConfiguredAnnotationRepositoryConfigurationSource(metadata, getAnnotation(), this.resourceLoader,
this.environment, registry, importBeanNameGenerator) {
@@ -129,7 +134,7 @@ public abstract class AbstractRepositoryConfigurationSourceSupport
AutoConfiguredAnnotationRepositoryConfigurationSource(AnnotationMetadata metadata,
Class<? extends Annotation> annotation, ResourceLoader resourceLoader, Environment environment,
BeanDefinitionRegistry registry, BeanNameGenerator generator) {
BeanDefinitionRegistry registry, @Nullable BeanNameGenerator generator) {
super(metadata, annotation, resourceLoader, environment, registry, generator);
}
@@ -19,12 +19,15 @@ package org.springframework.boot.autoconfigure.data;
import java.util.Locale;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
/**
* {@link SpringBootCondition} for controlling what type of Spring Data repositories are
@@ -36,10 +39,14 @@ class OnRepositoryTypeCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnRepositoryType.class.getName(),
true);
RepositoryType configuredType = getTypeProperty(context.getEnvironment(), (String) attributes.get("store"));
Map<String, @Nullable Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnRepositoryType.class.getName(), true);
Assert.state(attributes != null, "'attributes' must not be null");
String store = (String) attributes.get("store");
Assert.state(store != null, "'store' must not be null");
RepositoryType configuredType = getTypeProperty(context.getEnvironment(), store);
RepositoryType requiredType = (RepositoryType) attributes.get("type");
Assert.state(requiredType != null, "'requiredType' must not be null");
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnRepositoryType.class);
if (configuredType == requiredType || configuredType == RepositoryType.AUTO) {
return ConditionOutcome
@@ -17,4 +17,7 @@
/**
* Auto-configuration base classes for Spring Data.
*/
@NullMarked
package org.springframework.boot.autoconfigure.data;
import org.jspecify.annotations.NullMarked;
@@ -25,6 +25,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InjectionPoint;
@@ -74,7 +76,8 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
}
@Override
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause, String description) {
protected @Nullable FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause,
@Nullable String description) {
if (cause.getNumberOfBeansFound() != 0) {
return null;
}
@@ -118,7 +121,9 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
}
private Class<?> extractBeanType(ResolvableType resolvableType) {
return resolvableType.getRawClass();
Class<?> rawClass = resolvableType.getRawClass();
Assert.state(rawClass != null, "'rawClass' must not be null");
return rawClass;
}
private List<AutoConfigurationResult> getAutoConfigurationResults(NoSuchBeanDefinitionException cause) {
@@ -140,7 +145,7 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
.toList();
}
private MethodMetadata getFactoryMethodMetadata(String beanName) {
private @Nullable MethodMetadata getFactoryMethodMetadata(String beanName) {
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(beanName);
if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
return annotatedBeanDefinition.getFactoryMethodMetadata();
@@ -183,7 +188,7 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
}
}
private InjectionPoint findInjectionPoint(Throwable failure) {
private @Nullable InjectionPoint findInjectionPoint(Throwable failure) {
UnsatisfiedDependencyException unsatisfiedDependencyException = findCause(failure,
UnsatisfiedDependencyException.class);
if (unsatisfiedDependencyException == null) {
@@ -196,7 +201,7 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
private final String className;
private final String methodName;
private final @Nullable String methodName;
Source(String source) {
String[] tokens = source.split("#");
@@ -208,7 +213,7 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
return this.className;
}
String getMethodName() {
@Nullable String getMethodName() {
return this.methodName;
}
@@ -252,7 +257,7 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
}
private boolean hasName(MethodMetadata methodMetadata, String name) {
Map<String, Object> attributes = methodMetadata.getAnnotationAttributes(Bean.class.getName());
Map<String, @Nullable Object> attributes = methodMetadata.getAnnotationAttributes(Bean.class.getName());
String[] candidates = (attributes != null) ? (String[]) attributes.get("name") : null;
if (candidates != null) {
for (String candidate : candidates) {
@@ -309,11 +314,11 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz
private static class UserConfigurationResult {
private final MethodMetadata methodMetadata;
private final @Nullable MethodMetadata methodMetadata;
private final boolean nullBean;
UserConfigurationResult(MethodMetadata methodMetadata, boolean nullBean) {
UserConfigurationResult(@Nullable MethodMetadata methodMetadata, boolean nullBean) {
this.methodMetadata = methodMetadata;
this.nullBean = nullBean;
}
@@ -18,4 +18,7 @@
* Internal {@link org.springframework.boot.diagnostics.FailureAnalyzer} implementations
* related to auto-configuration.
*/
@NullMarked
package org.springframework.boot.autoconfigure.diagnostics.analyzer;
import org.jspecify.annotations.NullMarked;
@@ -139,6 +139,7 @@ public class EntityScanPackages {
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(EntityScan.class.getName()));
Assert.state(attributes != null, "'attributes' must not be null");
Set<String> packagesToScan = new LinkedHashSet<>();
for (String basePackage : attributes.getStringArray("basePackages")) {
String[] tokenized = StringUtils.tokenizeToStringArray(
@@ -72,7 +72,9 @@ public class EntityScanner {
for (String basePackage : packages) {
if (StringUtils.hasText(basePackage)) {
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
entitySet.add(ClassUtils.forName(candidate.getBeanClassName(), this.context.getClassLoader()));
String beanClassName = candidate.getBeanClassName();
Assert.state(beanClassName != null, "'beanClassName' must not be null");
entitySet.add(ClassUtils.forName(beanClassName, this.context.getClassLoader()));
}
}
}
@@ -17,4 +17,7 @@
/**
* General purpose domain annotations and classes.
*/
@NullMarked
package org.springframework.boot.autoconfigure.domain;
import org.jspecify.annotations.NullMarked;
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Properties;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
@@ -85,7 +87,7 @@ public final class ProjectInfoAutoConfiguration {
return target;
}
private Properties loadSource(Resource location, Charset encoding) throws IOException {
private Properties loadSource(Resource location, @Nullable Charset encoding) throws IOException {
if (encoding != null) {
return PropertiesLoaderUtils.loadProperties(new EncodedResource(location, encoding));
}
@@ -17,4 +17,7 @@
/**
* Auto-configuration for project information.
*/
@NullMarked
package org.springframework.boot.autoconfigure.info;
import org.jspecify.annotations.NullMarked;
@@ -34,6 +34,7 @@ import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.annotation.AnnotationMBeanExporter;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
import org.springframework.jmx.support.MBeanServerFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -94,7 +95,9 @@ public final class JmxAutoConfiguration {
MBeanServerFactoryBean factory = new MBeanServerFactoryBean();
factory.setLocateExistingServerIfPossible(true);
factory.afterPropertiesSet();
return factory.getObject();
MBeanServer mBeanServer = factory.getObject();
Assert.state(mBeanServer != null, "'mBeanServer' must not be null");
return mBeanServer;
}
}
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.jmx;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.jmx.support.RegistrationPolicy;
@@ -46,7 +48,7 @@ public class JmxProperties {
/**
* JMX domain name.
*/
private String defaultDomain;
private @Nullable String defaultDomain;
/**
* JMX Registration policy.
@@ -77,11 +79,11 @@ public class JmxProperties {
this.server = server;
}
public String getDefaultDomain() {
public @Nullable String getDefaultDomain() {
return this.defaultDomain;
}
public void setDefaultDomain(String defaultDomain) {
public void setDefaultDomain(@Nullable String defaultDomain) {
this.defaultDomain = defaultDomain;
}
@@ -21,6 +21,8 @@ import java.util.Hashtable;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -28,6 +30,7 @@ import org.springframework.jmx.export.metadata.JmxAttributeSource;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.support.JmxUtils;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
@@ -39,6 +42,7 @@ import org.springframework.util.ObjectUtils;
*/
public class ParentAwareNamingStrategy extends MetadataNamingStrategy implements ApplicationContextAware {
@SuppressWarnings("NullAway.Init")
private ApplicationContext applicationContext;
private boolean ensureUniqueRuntimeObjectNames;
@@ -62,7 +66,7 @@ public class ParentAwareNamingStrategy extends MetadataNamingStrategy implements
}
@Override
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
public ObjectName getObjectName(Object managedBean, @Nullable String beanKey) throws MalformedObjectNameException {
ObjectName name = super.getObjectName(managedBean, beanKey);
if (this.ensureUniqueRuntimeObjectNames) {
return JmxUtils.appendIdentityToObjectName(name, managedBean);
@@ -73,12 +77,15 @@ public class ParentAwareNamingStrategy extends MetadataNamingStrategy implements
return name;
}
private boolean parentContextContainsSameBean(ApplicationContext context, String beanKey) {
private boolean parentContextContainsSameBean(ApplicationContext context, @Nullable String beanKey) {
if (context.getParent() == null) {
return false;
}
try {
this.applicationContext.getParent().getBean(beanKey);
ApplicationContext parent = this.applicationContext.getParent();
Assert.state(parent != null, "'parent' must not be null");
Assert.state(beanKey != null, "'beanKey' must not be null");
parent.getBean(beanKey);
return true;
}
catch (BeansException ex) {
@@ -17,4 +17,7 @@
/**
* Auto-configuration for JMX.
*/
@NullMarked
package org.springframework.boot.autoconfigure.jmx;
import org.jspecify.annotations.NullMarked;
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.logging;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.logging.LogLevel;
@@ -124,7 +126,7 @@ public class ConditionEvaluationReportLoggingListener
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
public boolean supportsSourceType(@Nullable Class<?> sourceType) {
return true;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.logging;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -31,7 +33,8 @@ import org.springframework.boot.logging.LogLevel;
class ConditionEvaluationReportLoggingProcessor implements BeanFactoryInitializationAotProcessor {
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory) {
logConditionEvaluationReport(beanFactory);
return null;
}
@@ -27,6 +27,7 @@ import java.util.Set;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -133,6 +134,7 @@ public class ConditionEvaluationReportMessage {
Map<String, ConditionAndOutcomes> result = new LinkedHashMap<>();
for (String shortName : shortNames) {
List<String> fullyQualifiedNames = map.get(shortName);
Assert.state(fullyQualifiedNames != null, "'fullyQualifiedNames' must not be null");
if (fullyQualifiedNames.size() > 1) {
fullyQualifiedNames
.forEach((fullyQualifiedName) -> result.put(fullyQualifiedName, outcomes.get(fullyQualifiedName)));
@@ -17,4 +17,7 @@
/**
* Auto-configuration for logging.
*/
@NullMarked
package org.springframework.boot.autoconfigure.logging;
import org.jspecify.annotations.NullMarked;
@@ -19,4 +19,7 @@
*
* @see org.springframework.boot.autoconfigure.EnableAutoConfiguration
*/
@NullMarked
package org.springframework.boot.autoconfigure;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Capabilities to preinitialize code in the background to improve startup performance.
*/
@NullMarked
package org.springframework.boot.autoconfigure.preinitialize;
import org.jspecify.annotations.NullMarked;
@@ -26,6 +26,7 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -53,7 +54,7 @@ public class ConnectionDetailsFactories {
* @param classLoader the class loader used to load factories
* @since 3.5.0
*/
public ConnectionDetailsFactories(ClassLoader classLoader) {
public ConnectionDetailsFactories(@Nullable ClassLoader classLoader) {
this(SpringFactoriesLoader.forDefaultResourceLocation(classLoader));
}
@@ -126,9 +127,10 @@ public class ConnectionDetailsFactories {
ConnectionDetailsFactory<S, D> factory) {
@SuppressWarnings("unchecked")
private static <S, D extends ConnectionDetails> Registration<S, D> get(ConnectionDetailsFactory<S, D> factory) {
private static <S, D extends ConnectionDetails> @Nullable Registration<S, D> get(
ConnectionDetailsFactory<S, D> factory) {
ResolvableType type = ResolvableType.forClass(ConnectionDetailsFactory.class, factory.getClass());
Class<?>[] generics = type.resolveGenerics();
@Nullable Class<?>[] generics = type.resolveGenerics();
Class<S> sourceType = (Class<S>) generics[0];
Class<D> connectionDetailsType = (Class<D>) generics[1];
return (sourceType != null && connectionDetailsType != null)
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.service.connection;
import org.jspecify.annotations.Nullable;
/**
* A factory to create {@link ConnectionDetails} from a given {@code source}.
* Implementations should be registered in {@code META-INF/spring.factories}.
@@ -36,6 +38,6 @@ public interface ConnectionDetailsFactory<S, D extends ConnectionDetails> {
* @param source the source
* @return the connection details or {@code null}
*/
D getConnectionDetails(S source);
@Nullable D getConnectionDetails(S source);
}
@@ -17,4 +17,7 @@
/**
* Support for service connections that affect auto-configuration.
*/
@NullMarked
package org.springframework.boot.autoconfigure.service.connection;
import org.jspecify.annotations.NullMarked;
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.ssl;
import java.nio.file.Path;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ssl.pem.PemContent;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@@ -33,7 +35,7 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Moritz Halbritter
*/
record BundleContentProperty(String name, String value) {
record BundleContentProperty(String name, @Nullable String value) {
/**
* Return if the property value is PEM content.
@@ -54,6 +56,7 @@ record BundleContentProperty(String name, String value) {
Path toWatchPath(ResourceLoader resourceLoader) {
try {
Assert.state(!isPemContent(), "Value contains PEM content");
Assert.state(this.value != null, "Value must not be null");
Resource resource = resourceLoader.getResource(this.value);
if (!resource.isFile()) {
throw new BundleContentNotWatchableException(this);
@@ -26,6 +26,8 @@ import java.security.cert.Certificate;
import java.util.List;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -47,17 +49,18 @@ class CertificateMatcher {
private final Signature signature;
private final byte[] generatedSignature;
private final byte @Nullable [] generatedSignature;
CertificateMatcher(PrivateKey privateKey) {
Assert.notNull(privateKey, "'privateKey' must not be null");
this.privateKey = privateKey;
this.signature = createSignature(privateKey);
Assert.state(this.signature != null, "Failed to create signature");
this.generatedSignature = sign(this.signature, privateKey);
Signature signature = createSignature(privateKey);
Assert.state(signature != null, "Failed to create signature");
this.signature = signature;
this.generatedSignature = sign(signature, privateKey);
}
private Signature createSignature(PrivateKey privateKey) {
private @Nullable Signature createSignature(PrivateKey privateKey) {
try {
String algorithm = getSignatureAlgorithm(privateKey);
return (algorithm != null) ? Signature.getInstance(algorithm) : null;
@@ -67,7 +70,7 @@ class CertificateMatcher {
}
}
private static String getSignatureAlgorithm(PrivateKey privateKey) {
private static @Nullable String getSignatureAlgorithm(PrivateKey privateKey) {
// https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#signature-algorithms
// https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#keypairgenerator-algorithms
return switch (privateKey.getAlgorithm()) {
@@ -103,7 +106,7 @@ class CertificateMatcher {
}
}
private static byte[] sign(Signature signature, PrivateKey privateKey) {
private static byte @Nullable [] sign(Signature signature, PrivateKey privateKey) {
try {
signature.initSign(privateKey);
signature.update(DATA);
@@ -39,6 +39,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
@@ -57,7 +58,7 @@ class FileWatcher implements Closeable {
private final Object lock = new Object();
private WatcherThread thread;
private @Nullable WatcherThread thread;
/**
* Create a new {@link FileWatcher} instance.
@@ -241,6 +242,7 @@ class FileWatcher implements Closeable {
Path directory = (Path) key.watchable();
for (WatchEvent<?> event : key.pollEvents()) {
Path file = directory.resolve((Path) event.context());
Assert.state(registrations != null, "'registrations' must not be null");
for (Registration registration : registrations) {
if (registration.manages(file)) {
actions.add(registration.action());
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.ssl;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
/**
@@ -54,52 +56,52 @@ public class JksSslBundleProperties extends SslBundleProperties {
/**
* Type of the store to create, e.g. JKS.
*/
private String type;
private @Nullable String type;
/**
* Provider for the store.
*/
private String provider;
private @Nullable String provider;
/**
* Location of the resource containing the store content.
*/
private String location;
private @Nullable String location;
/**
* Password used to access the store.
*/
private String password;
private @Nullable String password;
public String getType() {
public @Nullable String getType() {
return this.type;
}
public void setType(String type) {
public void setType(@Nullable String type) {
this.type = type;
}
public String getProvider() {
public @Nullable String getProvider() {
return this.provider;
}
public void setProvider(String provider) {
public void setProvider(@Nullable String provider) {
this.provider = provider;
}
public String getLocation() {
public @Nullable String getLocation() {
return this.location;
}
public void setLocation(String location) {
public void setLocation(@Nullable String location) {
this.location = location;
}
public String getPassword() {
public @Nullable String getPassword() {
return this.password;
}
public void setPassword(String password) {
public void setPassword(@Nullable String password) {
this.password = password;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.ssl;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
/**
@@ -55,57 +57,57 @@ public class PemSslBundleProperties extends SslBundleProperties {
/**
* Type of the store to create, e.g. JKS.
*/
private String type;
private @Nullable String type;
/**
* Location or content of the certificate or certificate chain in PEM format.
*/
private String certificate;
private @Nullable String certificate;
/**
* Location or content of the private key in PEM format.
*/
private String privateKey;
private @Nullable String privateKey;
/**
* Password used to decrypt an encrypted private key.
*/
private String privateKeyPassword;
private @Nullable String privateKeyPassword;
/**
* Whether to verify that the private key matches the public key.
*/
private boolean verifyKeys;
public String getType() {
public @Nullable String getType() {
return this.type;
}
public void setType(String type) {
public void setType(@Nullable String type) {
this.type = type;
}
public String getCertificate() {
public @Nullable String getCertificate() {
return this.certificate;
}
public void setCertificate(String certificate) {
public void setCertificate(@Nullable String certificate) {
this.certificate = certificate;
}
public String getPrivateKey() {
public @Nullable String getPrivateKey() {
return this.privateKey;
}
public void setPrivateKey(String privateKey) {
public void setPrivateKey(@Nullable String privateKey) {
this.privateKey = privateKey;
}
public String getPrivateKeyPassword() {
public @Nullable String getPrivateKeyPassword() {
return this.privateKeyPassword;
}
public void setPrivateKeyPassword(String privateKeyPassword) {
public void setPrivateKeyPassword(@Nullable String privateKeyPassword) {
this.privateKeyPassword = privateKeyPassword;
}
@@ -16,6 +16,12 @@
package org.springframework.boot.autoconfigure.ssl;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.boot.ssl.SslBundle;
@@ -60,11 +66,11 @@ public final class PropertiesSslBundle implements SslBundle {
this.managers = SslManagerBundle.from(this.stores, this.key);
}
private static SslBundleKey asSslKeyReference(Key key) {
private static SslBundleKey asSslKeyReference(@Nullable Key key) {
return (key != null) ? SslBundleKey.of(key.getPassword(), key.getAlias()) : SslBundleKey.NONE;
}
private static SslOptions asSslOptions(SslBundleProperties.Options options) {
private static SslOptions asSslOptions(SslBundleProperties.@Nullable Options options) {
return (options != null) ? SslOptions.of(options.getCiphers(), options.getEnabledProtocols()) : SslOptions.NONE;
}
@@ -120,13 +126,18 @@ public final class PropertiesSslBundle implements SslBundle {
return new PropertiesSslBundle(storeBundle, properties);
}
private static PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties,
private static @Nullable PemSslStore getPemSslStore(String propertyName, PemSslBundleProperties.Store properties,
ResourceLoader resourceLoader) {
PemSslStoreDetails details = asPemSslStoreDetails(properties);
PemSslStore pemSslStore = PemSslStore.load(details, resourceLoader);
if (properties.isVerifyKeys()) {
CertificateMatcher certificateMatcher = new CertificateMatcher(pemSslStore.privateKey());
Assert.state(certificateMatcher.matchesAny(pemSslStore.certificates()),
Assert.state(pemSslStore != null, "'pemSslStore' must not be null");
PrivateKey privateKey = pemSslStore.privateKey();
Assert.state(privateKey != null, "'privateKey' must not be null");
CertificateMatcher certificateMatcher = new CertificateMatcher(privateKey);
List<X509Certificate> certificates = pemSslStore.certificates();
Assert.state(certificates != null, "'certificates' must not be null");
Assert.state(certificateMatcher.matchesAny(certificates),
() -> "Private key in %s matches none of the certificates in the chain".formatted(propertyName));
}
return pemSslStore;
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.ssl;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ssl.SslBundle;
/**
@@ -79,26 +81,26 @@ public abstract class SslBundleProperties {
/**
* Supported SSL ciphers.
*/
private Set<String> ciphers;
private @Nullable Set<String> ciphers;
/**
* Enabled SSL protocols.
*/
private Set<String> enabledProtocols;
private @Nullable Set<String> enabledProtocols;
public Set<String> getCiphers() {
public @Nullable Set<String> getCiphers() {
return this.ciphers;
}
public void setCiphers(Set<String> ciphers) {
public void setCiphers(@Nullable Set<String> ciphers) {
this.ciphers = ciphers;
}
public Set<String> getEnabledProtocols() {
public @Nullable Set<String> getEnabledProtocols() {
return this.enabledProtocols;
}
public void setEnabledProtocols(Set<String> enabledProtocols) {
public void setEnabledProtocols(@Nullable Set<String> enabledProtocols) {
this.enabledProtocols = enabledProtocols;
}
@@ -109,26 +111,26 @@ public abstract class SslBundleProperties {
/**
* The password used to access the key in the key store.
*/
private String password;
private @Nullable String password;
/**
* The alias that identifies the key in the key store.
*/
private String alias;
private @Nullable String alias;
public String getPassword() {
public @Nullable String getPassword() {
return this.password;
}
public void setPassword(String password) {
public void setPassword(@Nullable String password) {
this.password = password;
}
public String getAlias() {
public @Nullable String getAlias() {
return this.alias;
}
public void setAlias(String alias) {
public void setAlias(@Nullable String alias) {
this.alias = alias;
}
@@ -17,4 +17,7 @@
/**
* Auto-configuration for SSL bundles.
*/
@NullMarked
package org.springframework.boot.autoconfigure.ssl;
import org.jspecify.annotations.NullMarked;
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.task;
import java.time.Duration;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -86,7 +88,7 @@ public class TaskExecutionProperties {
* Set the maximum number of parallel accesses allowed. -1 indicates no
* concurrency limit at all.
*/
private Integer concurrencyLimit;
private @Nullable Integer concurrencyLimit;
public boolean isRejectTasksWhenLimitReached() {
return this.rejectTasksWhenLimitReached;
@@ -96,11 +98,11 @@ public class TaskExecutionProperties {
this.rejectTasksWhenLimitReached = rejectTasksWhenLimitReached;
}
public Integer getConcurrencyLimit() {
public @Nullable Integer getConcurrencyLimit() {
return this.concurrencyLimit;
}
public void setConcurrencyLimit(Integer concurrencyLimit) {
public void setConcurrencyLimit(@Nullable Integer concurrencyLimit) {
this.concurrencyLimit = concurrencyLimit;
}
@@ -215,7 +217,7 @@ public class TaskExecutionProperties {
/**
* Maximum time the executor should wait for remaining tasks to complete.
*/
private Duration awaitTerminationPeriod;
private @Nullable Duration awaitTerminationPeriod;
public boolean isAwaitTermination() {
return this.awaitTermination;
@@ -225,11 +227,11 @@ public class TaskExecutionProperties {
this.awaitTermination = awaitTermination;
}
public Duration getAwaitTerminationPeriod() {
public @Nullable Duration getAwaitTerminationPeriod() {
return this.awaitTerminationPeriod;
}
public void setAwaitTerminationPeriod(Duration awaitTerminationPeriod) {
public void setAwaitTerminationPeriod(@Nullable Duration awaitTerminationPeriod) {
this.awaitTerminationPeriod = awaitTerminationPeriod;
}
@@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.task;
import java.util.List;
import java.util.concurrent.Executor;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@@ -54,7 +56,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
*/
class TaskExecutorConfigurations {
private static TaskDecorator getTaskDecorator(ObjectProvider<TaskDecorator> taskDecorator) {
private static @Nullable TaskDecorator getTaskDecorator(ObjectProvider<TaskDecorator> taskDecorator) {
List<TaskDecorator> taskDecorators = taskDecorator.orderedStream().toList();
if (taskDecorators.size() == 1) {
return taskDecorators.get(0);
@@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.task;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -45,7 +47,7 @@ import org.springframework.scheduling.config.TaskManagementConfigUtils;
*/
class TaskSchedulingConfigurations {
private static TaskDecorator getTaskDecorator(ObjectProvider<TaskDecorator> taskDecorator) {
private static @Nullable TaskDecorator getTaskDecorator(ObjectProvider<TaskDecorator> taskDecorator) {
List<TaskDecorator> taskDecorators = taskDecorator.orderedStream().toList();
if (taskDecorators.size() == 1) {
return taskDecorators.get(0);
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.task;
import java.time.Duration;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -84,13 +86,13 @@ public class TaskSchedulingProperties {
* Set the maximum number of parallel accesses allowed. -1 indicates no
* concurrency limit at all.
*/
private Integer concurrencyLimit;
private @Nullable Integer concurrencyLimit;
public Integer getConcurrencyLimit() {
public @Nullable Integer getConcurrencyLimit() {
return this.concurrencyLimit;
}
public void setConcurrencyLimit(Integer concurrencyLimit) {
public void setConcurrencyLimit(@Nullable Integer concurrencyLimit) {
this.concurrencyLimit = concurrencyLimit;
}
@@ -106,7 +108,7 @@ public class TaskSchedulingProperties {
/**
* Maximum time the executor should wait for remaining tasks to complete.
*/
private Duration awaitTerminationPeriod;
private @Nullable Duration awaitTerminationPeriod;
public boolean isAwaitTermination() {
return this.awaitTermination;
@@ -116,11 +118,11 @@ public class TaskSchedulingProperties {
this.awaitTermination = awaitTermination;
}
public Duration getAwaitTerminationPeriod() {
public @Nullable Duration getAwaitTerminationPeriod() {
return this.awaitTerminationPeriod;
}
public void setAwaitTerminationPeriod(Duration awaitTerminationPeriod) {
public void setAwaitTerminationPeriod(@Nullable Duration awaitTerminationPeriod) {
this.awaitTerminationPeriod = awaitTerminationPeriod;
}
@@ -17,4 +17,7 @@
/**
* Auto-configuration for task execution and scheduling.
*/
@NullMarked
package org.springframework.boot.autoconfigure.task;
import org.jspecify.annotations.NullMarked;
@@ -23,6 +23,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
@@ -72,7 +74,14 @@ public class TemplateAvailabilityProviders {
* @param applicationContext the source application context
*/
public TemplateAvailabilityProviders(ApplicationContext applicationContext) {
this((applicationContext != null) ? applicationContext.getClassLoader() : null);
this(getClassLoader(applicationContext));
}
private static ClassLoader getClassLoader(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "'applicationContext' must not be null");
ClassLoader classLoader = applicationContext.getClassLoader();
Assert.state(classLoader != null, "'classLoader' must not be null");
return classLoader;
}
/**
@@ -107,10 +116,11 @@ public class TemplateAvailabilityProviders {
* @param applicationContext the application context
* @return a {@link TemplateAvailabilityProvider} or null
*/
public TemplateAvailabilityProvider getProvider(String view, ApplicationContext applicationContext) {
public @Nullable TemplateAvailabilityProvider getProvider(String view, ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "'applicationContext' must not be null");
return getProvider(view, applicationContext.getEnvironment(), applicationContext.getClassLoader(),
applicationContext);
ClassLoader classLoader = applicationContext.getClassLoader();
Assert.state(classLoader != null, "'classLoader' must not be null");
return getProvider(view, applicationContext.getEnvironment(), classLoader, applicationContext);
}
/**
@@ -121,8 +131,8 @@ public class TemplateAvailabilityProviders {
* @param resourceLoader the resource loader
* @return a {@link TemplateAvailabilityProvider} or null
*/
public TemplateAvailabilityProvider getProvider(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
public @Nullable TemplateAvailabilityProvider getProvider(String view, Environment environment,
ClassLoader classLoader, ResourceLoader resourceLoader) {
Assert.notNull(view, "'view' must not be null");
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(classLoader, "'classLoader' must not be null");
@@ -143,8 +153,8 @@ public class TemplateAvailabilityProviders {
return (provider != NONE) ? provider : null;
}
private TemplateAvailabilityProvider findProvider(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
private @Nullable TemplateAvailabilityProvider findProvider(String view, Environment environment,
ClassLoader classLoader, ResourceLoader resourceLoader) {
for (TemplateAvailabilityProvider candidate : this.providers) {
if (candidate.isTemplateAvailable(view, environment, classLoader, resourceLoader)) {
return candidate;
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.template;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -27,7 +29,7 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
class TemplateRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.resources().registerPatternIfPresent(classLoader, "templates", (hint) -> hint.includes("templates/**"));
}
@@ -17,4 +17,7 @@
/**
* Base classes for template Auto-configuration.
*/
@NullMarked
package org.springframework.boot.autoconfigure.template;
import org.jspecify.annotations.NullMarked;
@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.web;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
@@ -24,6 +26,7 @@ import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.lang.Contract;
import org.springframework.util.ClassUtils;
/**
@@ -64,8 +67,13 @@ class OnEnabledResourceChainCondition extends SpringBootCondition {
return ConditionOutcome.noMatch(message.because("disabled"));
}
private Boolean getEnabledProperty(ConfigurableEnvironment environment, String key, Boolean defaultValue) {
@Contract("_, _, !null -> !null")
private @Nullable Boolean getEnabledProperty(ConfigurableEnvironment environment, String key,
@Nullable Boolean defaultValue) {
String name = "spring.web.resources.chain." + key + "enabled";
if (defaultValue == null) {
return environment.getProperty(name, Boolean.class);
}
return environment.getProperty(name, Boolean.class, defaultValue);
}
@@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.DurationUnit;
@@ -39,7 +41,7 @@ public class WebProperties {
* Locale to use. By default, this locale is overridden by the "Accept-Language"
* header.
*/
private Locale locale;
private @Nullable Locale locale;
/**
* Define how the locale should be resolved.
@@ -48,11 +50,11 @@ public class WebProperties {
private final Resources resources = new Resources();
public Locale getLocale() {
public @Nullable Locale getLocale() {
return this.locale;
}
public void setLocale(Locale locale) {
public void setLocale(@Nullable Locale locale) {
this.locale = locale;
}
@@ -155,7 +157,7 @@ public class WebProperties {
* Whether to enable the Spring Resource Handling chain. By default, disabled
* unless at least one strategy has been enabled.
*/
private Boolean enabled;
private @Nullable Boolean enabled;
/**
* Whether to enable caching in the Resource chain.
@@ -177,7 +179,7 @@ public class WebProperties {
* @return whether the resource chain is enabled or {@code null} if no
* specified settings are present.
*/
public Boolean getEnabled() {
public @Nullable Boolean getEnabled() {
return getEnabled(getStrategy().getFixed().isEnabled(), getStrategy().getContent().isEnabled(),
this.enabled);
}
@@ -213,7 +215,8 @@ public class WebProperties {
this.customized = true;
}
static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled, Boolean chainEnabled) {
static @Nullable Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled,
@Nullable Boolean chainEnabled) {
return (fixedEnabled || contentEnabled) ? Boolean.TRUE : chainEnabled;
}
@@ -299,7 +302,7 @@ public class WebProperties {
/**
* Version string to use for the fixed Version Strategy.
*/
private String version;
private @Nullable String version;
public boolean isEnabled() {
return this.enabled;
@@ -319,11 +322,11 @@ public class WebProperties {
this.paths = paths;
}
public String getVersion() {
public @Nullable String getVersion() {
return this.version;
}
public void setVersion(String version) {
public void setVersion(@Nullable String version) {
this.customized = true;
this.version = version;
}
@@ -351,7 +354,7 @@ public class WebProperties {
* by the 'spring.web.resources.cache.cachecontrol' properties.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration period;
private @Nullable Duration period;
/**
* Cache control HTTP headers, only allows valid directive combinations.
@@ -365,11 +368,11 @@ public class WebProperties {
*/
private boolean useLastModified = true;
public Duration getPeriod() {
public @Nullable Duration getPeriod() {
return this.period;
}
public void setPeriod(Duration period) {
public void setPeriod(@Nullable Duration period) {
this.customized = true;
this.period = period;
}
@@ -402,169 +405,169 @@ public class WebProperties {
* suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge;
private @Nullable Duration maxAge;
/**
* Indicate that the cached response can be reused only if re-validated
* with the server.
*/
private Boolean noCache;
private @Nullable Boolean noCache;
/**
* Indicate to not cache the response in any case.
*/
private Boolean noStore;
private @Nullable Boolean noStore;
/**
* Indicate that once it has become stale, a cache must not use the
* response without re-validating it with the server.
*/
private Boolean mustRevalidate;
private @Nullable Boolean mustRevalidate;
/**
* Indicate intermediaries (caches and others) that they should not
* transform the response content.
*/
private Boolean noTransform;
private @Nullable Boolean noTransform;
/**
* Indicate that any cache may store the response.
*/
private Boolean cachePublic;
private @Nullable Boolean cachePublic;
/**
* Indicate that the response message is intended for a single user and
* must not be stored by a shared cache.
*/
private Boolean cachePrivate;
private @Nullable Boolean cachePrivate;
/**
* Same meaning as the "must-revalidate" directive, except that it does
* not apply to private caches.
*/
private Boolean proxyRevalidate;
private @Nullable Boolean proxyRevalidate;
/**
* Maximum time the response can be served after it becomes stale, in
* seconds if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleWhileRevalidate;
private @Nullable Duration staleWhileRevalidate;
/**
* Maximum time the response may be used when errors are encountered, in
* seconds if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleIfError;
private @Nullable Duration staleIfError;
/**
* Maximum time the response should be cached by shared caches, in seconds
* if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration sMaxAge;
private @Nullable Duration sMaxAge;
public Duration getMaxAge() {
public @Nullable Duration getMaxAge() {
return this.maxAge;
}
public void setMaxAge(Duration maxAge) {
public void setMaxAge(@Nullable Duration maxAge) {
this.customized = true;
this.maxAge = maxAge;
}
public Boolean getNoCache() {
public @Nullable Boolean getNoCache() {
return this.noCache;
}
public void setNoCache(Boolean noCache) {
public void setNoCache(@Nullable Boolean noCache) {
this.customized = true;
this.noCache = noCache;
}
public Boolean getNoStore() {
public @Nullable Boolean getNoStore() {
return this.noStore;
}
public void setNoStore(Boolean noStore) {
public void setNoStore(@Nullable Boolean noStore) {
this.customized = true;
this.noStore = noStore;
}
public Boolean getMustRevalidate() {
public @Nullable Boolean getMustRevalidate() {
return this.mustRevalidate;
}
public void setMustRevalidate(Boolean mustRevalidate) {
public void setMustRevalidate(@Nullable Boolean mustRevalidate) {
this.customized = true;
this.mustRevalidate = mustRevalidate;
}
public Boolean getNoTransform() {
public @Nullable Boolean getNoTransform() {
return this.noTransform;
}
public void setNoTransform(Boolean noTransform) {
public void setNoTransform(@Nullable Boolean noTransform) {
this.customized = true;
this.noTransform = noTransform;
}
public Boolean getCachePublic() {
public @Nullable Boolean getCachePublic() {
return this.cachePublic;
}
public void setCachePublic(Boolean cachePublic) {
public void setCachePublic(@Nullable Boolean cachePublic) {
this.customized = true;
this.cachePublic = cachePublic;
}
public Boolean getCachePrivate() {
public @Nullable Boolean getCachePrivate() {
return this.cachePrivate;
}
public void setCachePrivate(Boolean cachePrivate) {
public void setCachePrivate(@Nullable Boolean cachePrivate) {
this.customized = true;
this.cachePrivate = cachePrivate;
}
public Boolean getProxyRevalidate() {
public @Nullable Boolean getProxyRevalidate() {
return this.proxyRevalidate;
}
public void setProxyRevalidate(Boolean proxyRevalidate) {
public void setProxyRevalidate(@Nullable Boolean proxyRevalidate) {
this.customized = true;
this.proxyRevalidate = proxyRevalidate;
}
public Duration getStaleWhileRevalidate() {
public @Nullable Duration getStaleWhileRevalidate() {
return this.staleWhileRevalidate;
}
public void setStaleWhileRevalidate(Duration staleWhileRevalidate) {
public void setStaleWhileRevalidate(@Nullable Duration staleWhileRevalidate) {
this.customized = true;
this.staleWhileRevalidate = staleWhileRevalidate;
}
public Duration getStaleIfError() {
public @Nullable Duration getStaleIfError() {
return this.staleIfError;
}
public void setStaleIfError(Duration staleIfError) {
public void setStaleIfError(@Nullable Duration staleIfError) {
this.customized = true;
this.staleIfError = staleIfError;
}
public Duration getSMaxAge() {
public @Nullable Duration getSMaxAge() {
return this.sMaxAge;
}
public void setSMaxAge(Duration sMaxAge) {
public void setSMaxAge(@Nullable Duration sMaxAge) {
this.customized = true;
this.sMaxAge = sMaxAge;
}
public CacheControl toHttpCacheControl() {
public @Nullable CacheControl toHttpCacheControl() {
PropertyMapper map = PropertyMapper.get();
CacheControl control = createCacheControl();
map.from(this::getMustRevalidate).whenTrue().toCall(control::mustRevalidate);
@@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.web;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -33,7 +35,7 @@ public class WebResourcesRuntimeHints implements RuntimeHintsRegistrar {
"public/");
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = (classLoader != null) ? classLoader : getClass().getClassLoader();
String[] locations = DEFAULT_LOCATIONS.stream()
.filter((candidate) -> classLoaderToUse.getResource(candidate) != null)
@@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.web.format;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import org.jspecify.annotations.Nullable;
import org.springframework.util.StringUtils;
/**
@@ -30,13 +32,13 @@ import org.springframework.util.StringUtils;
*/
public class DateTimeFormatters {
private DateTimeFormatter dateFormatter;
private @Nullable DateTimeFormatter dateFormatter;
private String datePattern;
private @Nullable String datePattern;
private DateTimeFormatter timeFormatter;
private @Nullable DateTimeFormatter timeFormatter;
private DateTimeFormatter dateTimeFormatter;
private @Nullable DateTimeFormatter dateTimeFormatter;
/**
* Configures the date format using the given {@code pattern}.
@@ -77,19 +79,19 @@ public class DateTimeFormatters {
return this;
}
DateTimeFormatter getDateFormatter() {
@Nullable DateTimeFormatter getDateFormatter() {
return this.dateFormatter;
}
String getDatePattern() {
@Nullable String getDatePattern() {
return this.datePattern;
}
DateTimeFormatter getTimeFormatter() {
@Nullable DateTimeFormatter getTimeFormatter() {
return this.timeFormatter;
}
DateTimeFormatter getDateTimeFormatter() {
@Nullable DateTimeFormatter getDateTimeFormatter() {
return this.dateTimeFormatter;
}
@@ -97,7 +99,7 @@ public class DateTimeFormatters {
return this.dateFormatter != null || this.timeFormatter != null || this.dateTimeFormatter != null;
}
private static DateTimeFormatter formatter(String pattern) {
private static @Nullable DateTimeFormatter formatter(String pattern) {
return StringUtils.hasText(pattern)
? DateTimeFormatter.ofPattern(pattern).withResolverStyle(ResolverStyle.SMART) : null;
}
@@ -20,6 +20,8 @@ import java.time.format.DateTimeFormatter;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
@@ -79,7 +81,7 @@ public class WebConversionService extends DefaultFormattingConversionService {
dateTime.registerFormatters(this);
}
private void configure(Supplier<DateTimeFormatter> supplier, Consumer<DateTimeFormatter> consumer) {
private void configure(Supplier<@Nullable DateTimeFormatter> supplier, Consumer<DateTimeFormatter> consumer) {
DateTimeFormatter formatter = supplier.get();
if (formatter != null) {
consumer.accept(formatter);
@@ -17,4 +17,7 @@
/**
* Support classes for web-specific formatting.
*/
@NullMarked
package org.springframework.boot.autoconfigure.web.format;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for common web concerns.
*/
@NullMarked
package org.springframework.boot.autoconfigure.web;
import org.jspecify.annotations.NullMarked;
@@ -73,7 +73,7 @@ class TemplateAvailabilityProvidersTests {
void createWhenApplicationContextIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new TemplateAvailabilityProviders((ApplicationContext) null))
.withMessageContaining("'classLoader' must not be null");
.withMessageContaining("'applicationContext' must not be null");
}
@Test