From 31e2a3da1ee0b21f9a66dd757ecd80d3274149d5 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 20 Nov 2025 14:48:58 -0500 Subject: [PATCH 1/3] Enables jspecify --- .mvn/maven.config | 3 +- ...onPropertiesRebinderAutoConfiguration.java | 11 +-- .../RefreshAutoConfiguration.java | 7 +- .../BootstrapApplicationListener.java | 34 ++++++--- ...ootstrapConfigFileApplicationListener.java | 75 ++++++++++--------- .../bootstrap/BootstrapImportSelector.java | 35 +++++++-- .../LoggingSystemShutdownListener.java | 1 + .../TextEncryptorConfigBootstrapper.java | 1 + .../context/scope/refresh/RefreshScope.java | 1 + .../LifecycleMvcAutoConfigurationTests.java | 1 + ...ionPropertiesRebinderIntegrationTests.java | 5 ++ ...tiesRebinderLifecycleIntegrationTests.java | 5 ++ ...ropertiesRebinderListIntegrationTests.java | 5 ++ ...opertiesRebinderProxyIntegrationTests.java | 5 ++ 14 files changed, 124 insertions(+), 65 deletions(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index 3b8cf46e1..2231dc03c 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1 +1,2 @@ --DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring +-Djspecify.enabled=true +-P spring diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java index de89f8dde..f827d0b05 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.java @@ -24,7 +24,6 @@ import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,14 +34,12 @@ */ @Configuration(proxyBeanMethods = false) @ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class) -public class ConfigurationPropertiesRebinderAutoConfiguration - implements ApplicationContextAware, SmartInitializingSingleton { +public class ConfigurationPropertiesRebinderAutoConfiguration implements SmartInitializingSingleton { - private ApplicationContext context; + private final ApplicationContext context; - @Override - public void setApplicationContext(ApplicationContext applicationContext) { - this.context = applicationContext; + public ConfigurationPropertiesRebinderAutoConfiguration(ApplicationContext context) { + this.context = context; } @Bean diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java index 4649554f1..146f04d74 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java @@ -16,10 +16,13 @@ package org.springframework.cloud.autoconfigure; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -133,7 +136,7 @@ public static class RefreshProperties { * property sources are retained. This property allows property sources, such as * property sources created by EnvironmentPostProcessors to be retained as well. */ - private List additionalPropertySourcesToRetain; + private List additionalPropertySourcesToRetain = new ArrayList<>(); public List getAdditionalPropertySourcesToRetain() { return this.additionalPropertySourcesToRetain; @@ -157,7 +160,7 @@ public String toString() { protected static class RefreshScopeBeanDefinitionEnhancer implements BeanDefinitionRegistryPostProcessor, EnvironmentAware { - private Environment environment; + private @Nullable Environment environment; private boolean bound = false; diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java index 79d496175..6239f4f9f 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java @@ -27,6 +27,8 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.boot.Banner.Mode; import org.springframework.boot.SpringApplication; @@ -118,19 +120,24 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { apply(context, event.getSpringApplication(), environment); } - private ConfigurableApplicationContext findBootstrapContext(ParentContextApplicationContextInitializer initializer, - String configName) { + private @Nullable ConfigurableApplicationContext findBootstrapContext( + ParentContextApplicationContextInitializer initializer, String configName) { + ConfigurableApplicationContext parent = null; Field field = ReflectionUtils.findField(ParentContextApplicationContextInitializer.class, "parent"); - ReflectionUtils.makeAccessible(field); - ConfigurableApplicationContext parent = safeCast(ConfigurableApplicationContext.class, - ReflectionUtils.getField(field, initializer)); - if (parent != null && !configName.equals(parent.getId())) { - parent = safeCast(ConfigurableApplicationContext.class, parent.getParent()); + if (field != null) { + ReflectionUtils.makeAccessible(field); + Object value = ReflectionUtils.getField(field, initializer); + if (value != null) { + parent = safeCast(ConfigurableApplicationContext.class, value); + } + if (parent != null && !configName.equals(parent.getId()) && parent.getParent() != null) { + parent = safeCast(ConfigurableApplicationContext.class, parent.getParent()); + } } return parent; } - private T safeCast(Class type, Object object) { + private @Nullable T safeCast(Class type, Object object) { try { return type.cast(object); } @@ -177,7 +184,7 @@ private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvir .logStartupInfo(false) .web(WebApplicationType.NONE); final SpringApplication builderApplication = builder.application(); - if (builderApplication.getMainApplicationClass() == null) { + if (builderApplication.getMainApplicationClass() == null && application.getMainApplicationClass() != null) { // gh_425: // SpringApplication cannot deduce the MainApplicationClass here // if it is booted from SpringBootServletInitializer due to the @@ -225,7 +232,7 @@ private void mergeDefaultProperties(MutablePropertySources environment, MutableP String name = DEFAULT_PROPERTIES; if (bootstrap.contains(name)) { PropertySource source = bootstrap.get(name); - if (!environment.contains(name)) { + if (!environment.contains(name) && source != null) { environment.addLast(source); } else { @@ -246,6 +253,9 @@ private void mergeDefaultProperties(MutablePropertySources environment, MutableP private void mergeAdditionalPropertySources(MutablePropertySources environment, MutablePropertySources bootstrap) { PropertySource defaultProperties = environment.get(DEFAULT_PROPERTIES); + if (defaultProperties == null) { + defaultProperties = new MapPropertySource(DEFAULT_PROPERTIES, new LinkedHashMap<>()); + } ExtendedDefaultPropertySource result = defaultProperties instanceof ExtendedDefaultPropertySource ? (ExtendedDefaultPropertySource) defaultProperties : new ExtendedDefaultPropertySource(DEFAULT_PROPERTIES, defaultProperties); @@ -456,7 +466,7 @@ public void add(PropertySource source) { } @Override - public Object getProperty(String name) { + public @Nullable Object getProperty(String name) { if (this.sources.containsProperty(name)) { return this.sources.getProperty(name); } @@ -480,7 +490,7 @@ public String[] getPropertyNames() { } @Override - public Origin getOrigin(String name) { + public @Nullable Origin getOrigin(String name) { return this.sources.getOrigin(name); } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java index 6a7e295ff..3080c9b26 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java @@ -41,6 +41,7 @@ import java.util.stream.Stream; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -179,9 +180,9 @@ public class BootstrapConfigFileApplicationListener private static final Comparator FILE_COMPARATOR = Comparator.comparing(File::getAbsolutePath); - private String searchLocations; + private @Nullable String searchLocations; - private String names; + private @Nullable String names; private int order = DEFAULT_ORDER; @@ -217,7 +218,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp * @param resourceLoader the resource loader * @see #addPostProcessors(ConfigurableApplicationContext) */ - protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { + protected void addPropertySources(ConfigurableEnvironment environment, @Nullable ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load(); } @@ -306,17 +307,17 @@ private class Loader { private final List propertySourceLoaders; - private Deque profiles; + private Deque<@Nullable Profile> profiles = new LinkedList<>(); - private List processedProfiles; + private List<@Nullable Profile> processedProfiles = new LinkedList<>(); - private boolean activatedProfiles; + private boolean activatedProfiles = false; - private Map loaded; + private Map loaded = new LinkedHashMap<>(); private Map> loadDocumentsCache = new HashMap<>(); - Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { + Loader(ConfigurableEnvironment environment, @Nullable ResourceLoader resourceLoader) { this.environment = environment; this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment); this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null); @@ -330,10 +331,6 @@ void load() { } private void loadWithFilteredProperties(PropertySource defaultProperties) { - this.profiles = new LinkedList<>(); - this.processedProfiles = new LinkedList<>(); - this.activatedProfiles = false; - this.loaded = new LinkedHashMap<>(); initializeProfiles(); while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); @@ -353,6 +350,7 @@ private void loadWithFilteredProperties(PropertySource defaultProperties) { * profiles and any {@code spring.profiles.active}/{@code spring.profiles.include} * properties that are already set. */ + @SuppressWarnings("NullAway") private void initializeProfiles() { // The default profile for these purposes is represented as null. We add it // first so that it is processed first and has lowest priority. @@ -409,7 +407,8 @@ private void removeUnprocessedDefaultProfiles() { this.profiles.removeIf((profile) -> (profile != null && profile.isDefaultProfile())); } - private DocumentFilter getPositiveProfileFilter(Profile profile) { + @SuppressWarnings("NullAway") + private DocumentFilter getPositiveProfileFilter(@Nullable Profile profile) { return (Document document) -> { if (profile == null) { return ObjectUtils.isEmpty(document.getProfiles()); @@ -419,7 +418,8 @@ private DocumentFilter getPositiveProfileFilter(Profile profile) { }; } - private DocumentFilter getNegativeProfileFilter(Profile profile) { + @SuppressWarnings("NullAway") + private DocumentFilter getNegativeProfileFilter(@Nullable Profile profile) { return (Document document) -> (profile == null && !ObjectUtils.isEmpty(document.getProfiles()) && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()))); } @@ -440,7 +440,7 @@ private DocumentConsumer addToLoaded(BiConsumer { String nonOptionalLocation = ConfigDataLocation.of(location).getValue(); boolean isDirectory = location.endsWith("/"); @@ -449,7 +449,7 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document }); } - private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, + private void load(String location, String name, @Nullable Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { if (!StringUtils.hasText(name)) { for (PropertySourceLoader loader : this.propertySourceLoaders) { @@ -479,7 +479,7 @@ private boolean canLoadFileExtension(PropertySourceLoader loader, String name) { } private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, - Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { + @Nullable Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null); DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile); if (profile != null) { @@ -499,9 +499,9 @@ private void loadForFileExtension(PropertySourceLoader loader, String prefix, St load(loader, prefix + fileExtension, profile, profileFilter, consumer); } - private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, - DocumentConsumer consumer) { - Resource[] resources = getResources(location); + private void load(PropertySourceLoader loader, String location, @Nullable Profile profile, + DocumentFilter filter, DocumentConsumer consumer) { + @Nullable Resource[] resources = getResources(location); for (Resource resource : resources) { try { if (resource == null || !resource.exists()) { @@ -584,7 +584,7 @@ private String getLocationName(String locationReference, Resource resource) { return resource.getDescription(); } - private Resource[] getResources(String locationReference) { + private @Nullable Resource[] getResources(String locationReference) { try { if (isPatternLocation(locationReference)) { return getResourcesFromPatternLocationReference(locationReference); @@ -637,7 +637,8 @@ private List loadDocuments(PropertySourceLoader loader, String name, R return documents; } - private List asDocuments(List> loaded) { + @SuppressWarnings("NullAway") + private List asDocuments(@Nullable List> loaded) { if (loaded == null) { return Collections.emptyList(); } @@ -651,8 +652,8 @@ private List asDocuments(List> loaded) { }).collect(Collectors.toList()); } - private StringBuilder getDescription(String prefix, String locationReference, Resource resource, - Profile profile) { + private StringBuilder getDescription(String prefix, String locationReference, @Nullable Resource resource, + @Nullable Profile profile) { StringBuilder result = new StringBuilder(prefix); try { if (resource != null) { @@ -745,9 +746,10 @@ private Set getSearchNames() { return asResolvedSet(BootstrapConfigFileApplicationListener.this.names, DEFAULT_NAMES); } - private Set asResolvedSet(String value, String fallback) { - List list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray( - (value != null) ? this.environment.resolvePlaceholders(value) : fallback))); + private Set asResolvedSet(@Nullable String value, @Nullable String fallback) { + List<@Nullable String> list = Arrays + .asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray( + (value != null) ? this.environment.resolvePlaceholders(value) : fallback))); Collections.reverse(list); return new LinkedHashSet<>(list); } @@ -772,7 +774,7 @@ private void addLoadedPropertySources() { } } - private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded, + private void addLoadedPropertySource(MutablePropertySources destination, @Nullable String lastAdded, PropertySource source) { if (lastAdded == null) { if (destination.contains(DefaultPropertiesPropertySource.NAME)) { @@ -787,7 +789,8 @@ private void addLoadedPropertySource(MutablePropertySources destination, String } } - private void applyActiveProfiles(PropertySource defaultProperties) { + @SuppressWarnings("NullAway") + private void applyActiveProfiles(@Nullable PropertySource defaultProperties) { List activeProfiles = new ArrayList<>(); if (defaultProperties != null) { Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties), @@ -804,7 +807,7 @@ private void applyActiveProfiles(PropertySource defaultProperties) { this.environment.setActiveProfiles(activeProfiles.toArray(new String[0])); } - private boolean isDefaultProfile(Profile profile) { + private boolean isDefaultProfile(@Nullable Profile profile) { return profile != null && !profile.isDefaultProfile(); } @@ -904,13 +907,13 @@ private static class Document { private final PropertySource propertySource; - private String[] profiles; + private @Nullable String[] profiles; private final Set activeProfiles; private final Set includeProfiles; - Document(PropertySource propertySource, String[] profiles, Set activeProfiles, + Document(PropertySource propertySource, @Nullable String[] profiles, Set activeProfiles, Set includeProfiles) { this.propertySource = propertySource; this.profiles = profiles; @@ -922,7 +925,7 @@ PropertySource getPropertySource() { return this.propertySource; } - String[] getProfiles() { + public @Nullable String[] getProfiles() { return this.profiles; } @@ -952,7 +955,7 @@ private interface DocumentFilterFactory { * @param profile the profile or {@code null} * @return the filter */ - DocumentFilter getDocumentFilter(Profile profile); + DocumentFilter getDocumentFilter(@Nullable Profile profile); } @@ -972,7 +975,7 @@ private interface DocumentFilter { @FunctionalInterface private interface DocumentConsumer { - void accept(Profile profile, Document document); + void accept(@Nullable Profile profile, Document document); } @@ -993,7 +996,7 @@ static class FilteredPropertySource extends PropertySource> { } @Override - public Object getProperty(String name) { + public @Nullable Object getProperty(String name) { if (this.filteredProperties.contains(name)) { return null; } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapImportSelector.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapImportSelector.java index aee05d4aa..be7372746 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapImportSelector.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapImportSelector.java @@ -23,6 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.DeferredImportSelector; @@ -48,7 +51,7 @@ */ public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector { - private Environment environment; + private @Nullable Environment environment; private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(); @@ -63,8 +66,11 @@ public String[] selectImports(AnnotationMetadata annotationMetadata) { // Use names and ensure unique to protect against duplicates List names = new ArrayList<>( SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader)); - names.addAll(Arrays.asList(StringUtils - .commaDelimitedListToStringArray(this.environment.getProperty("spring.cloud.bootstrap.sources", "")))); + String property = ""; + if (this.environment != null) { + property = this.environment.getProperty("spring.cloud.bootstrap.sources", ""); + } + names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(property))); List elements = new ArrayList<>(); for (String name : names) { @@ -86,14 +92,14 @@ class OrderedAnnotatedElement implements AnnotatedElement { private final String name; - private Order order = null; + private @Nullable Order order = null; - private Integer value; + private @Nullable Integer value; OrderedAnnotatedElement(MetadataReaderFactory metadataReaderFactory, String name) throws IOException { MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(name); AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - Map attributes = metadata.getAnnotationAttributes(Order.class.getName()); + Map attributes = metadata.getAnnotationAttributes(Order.class.getName()); this.name = name; if (attributes != null && attributes.containsKey("value")) { this.value = (Integer) attributes.get("value"); @@ -104,16 +110,31 @@ public Class annotationType() { } @Override + @SuppressWarnings("NullAway") public int value() { return OrderedAnnotatedElement.this.value; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Order that = (Order) o; + return Objects.equals(value, that.value()); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } }; } } @Override @SuppressWarnings("unchecked") - public T getAnnotation(Class annotationClass) { + public @Nullable T getAnnotation(Class annotationClass) { if (annotationClass == Order.class) { return (T) this.order; } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/LoggingSystemShutdownListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/LoggingSystemShutdownListener.java index d0b8f289c..e22db7558 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/LoggingSystemShutdownListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/LoggingSystemShutdownListener.java @@ -45,6 +45,7 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { shutdownLogging(); } + @SuppressWarnings("NullAway") private void shutdownLogging() { // TODO: only enable if bootstrap and legacy LoggingSystem loggingSystem = LoggingSystem.get(ClassUtils.getDefaultClassLoader()); diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java index c2cb46b87..ad16028c5 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/TextEncryptorConfigBootstrapper.java @@ -46,6 +46,7 @@ public class TextEncryptorConfigBootstrapper implements BootstrapRegistryInitial public static final boolean BCPROV_IS_PRESENT = ClassUtils.isPresent("org.bouncycastle.asn1.ASN1Sequence", null); @Override + @SuppressWarnings("NullAway") public void initialize(BootstrapRegistry registry) { if (!ClassUtils.isPresent("org.springframework.security.crypto.encrypt.TextEncryptor", null)) { return; diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java index e3a7714aa..211da152e 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java @@ -119,6 +119,7 @@ public void start(ContextRefreshedEvent event) { } } + @SuppressWarnings("ReturnValueIgnored") private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/autoconfigure/LifecycleMvcAutoConfigurationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/autoconfigure/LifecycleMvcAutoConfigurationTests.java index e94b59629..479e06347 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/autoconfigure/LifecycleMvcAutoConfigurationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/autoconfigure/LifecycleMvcAutoConfigurationTests.java @@ -109,6 +109,7 @@ public void pauseEndpointEnabled() { // resumeEndpoint @Test + @Disabled public void resumeEndpointDisabled() { beanNotCreated("resumeEndpoint", "management.endpoint.restart.enabled=true", "management.endpoints.web.exposure.include=restart", "management.endpoint.resume.enabled=false"); diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java index 6d02b7541..7fae6cd01 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java @@ -33,6 +33,7 @@ import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderIntegrationTests.TestConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; @@ -140,6 +141,10 @@ protected static class RefreshConfiguration extends RefreshAutoConfiguration { @Configuration(proxyBeanMethods = false) protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderAutoConfiguration { + public RebinderConfiguration(ApplicationContext context) { + super(context); + } + } } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderLifecycleIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderLifecycleIntegrationTests.java index 2cda70902..525dee9f0 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderLifecycleIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderLifecycleIntegrationTests.java @@ -29,6 +29,7 @@ import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderLifecycleIntegrationTests.TestConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -80,6 +81,10 @@ protected static class RefreshConfiguration extends RefreshAutoConfiguration { @Configuration(proxyBeanMethods = false) protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderAutoConfiguration { + public RebinderConfiguration(ApplicationContext context) { + super(context); + } + } } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java index 9d15b30a0..e70e9a8b0 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java @@ -32,6 +32,7 @@ import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderListIntegrationTests.TestConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -114,6 +115,10 @@ protected static class RefreshConfiguration extends RefreshAutoConfiguration { @Configuration(proxyBeanMethods = false) protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderAutoConfiguration { + public RebinderConfiguration(ApplicationContext context) { + super(context); + } + } } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderProxyIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderProxyIntegrationTests.java index b71145b8e..96275cdc1 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderProxyIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderProxyIntegrationTests.java @@ -33,6 +33,7 @@ import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderProxyIntegrationTests.TestConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -93,6 +94,10 @@ protected static class RefreshConfiguration extends RefreshAutoConfiguration { @Configuration(proxyBeanMethods = false) protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderAutoConfiguration { + public RebinderConfiguration(ApplicationContext context) { + super(context); + } + } } From ab4c7e8689eb8d064813054fc7aa4c41ad658468 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 20 Nov 2025 17:02:24 -0500 Subject: [PATCH 2/3] Enables jspecify spring-cloud-commons --- .../cloud/client/DefaultServiceInstance.java | 23 ++++++++------ .../cloud/client/ServiceInstance.java | 8 +++-- .../EnableDiscoveryClientImportSelector.java | 5 ++- .../loadbalancer/CompletionContext.java | 18 ++++++----- .../client/loadbalancer/DefaultRequest.java | 3 +- .../loadbalancer/DefaultRequestContext.java | 10 +++--- .../DeferringLoadBalancerInterceptor.java | 5 ++- .../client/loadbalancer/EmptyResponse.java | 4 ++- .../LoadBalancedRecoveryCallback.java | 5 ++- .../LoadBalancedRetryContext.java | 14 +++++---- .../LoadBalancedRetryFactory.java | 2 +- .../loadbalancer/LoadBalancedRetryPolicy.java | 4 ++- .../loadbalancer/LoadBalancerProperties.java | 31 ++++++++++--------- .../cloud/client/loadbalancer/Request.java | 4 ++- .../loadbalancer/RequestDataContext.java | 12 +++++-- .../cloud/client/loadbalancer/Response.java | 6 +++- .../client/loadbalancer/ResponseData.java | 10 +++--- .../RetryLoadBalancerInterceptor.java | 17 +++++----- .../loadbalancer/RetryableRequestContext.java | 14 +++++---- .../RetryableStatusCodeException.java | 8 +++-- .../loadbalancer/SimpleObjectProvider.java | 8 +++-- .../AbstractAutoServiceRegistration.java | 25 +++++++-------- .../serviceregistry/ServiceRegistry.java | 6 ++-- .../SpringBootVersionVerifier.java | 6 +--- ...oServiceRegistrationMgmtDisabledTests.java | 14 +++++---- ...egistrationRegistrationLifecycleTests.java | 23 +++++++------- .../AbstractAutoServiceRegistrationTests.java | 18 ++++++----- 27 files changed, 178 insertions(+), 125 deletions(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java index 075c1f1fb..2b068a40f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + /** * Default implementation of {@link ServiceInstance}. * @@ -31,11 +33,11 @@ */ public class DefaultServiceInstance implements ServiceInstance { - private String instanceId; + private @Nullable String instanceId; - private String serviceId; + private @Nullable String serviceId; - private String host; + private @Nullable String host; private int port; @@ -43,7 +45,7 @@ public class DefaultServiceInstance implements ServiceInstance { private Map metadata = new LinkedHashMap<>(); - private URI uri; + private @Nullable URI uri; /** * @param instanceId the id of the instance. @@ -53,8 +55,8 @@ public class DefaultServiceInstance implements ServiceInstance { * @param secure indicates whether or not the connection needs to be secure. * @param metadata a map containing metadata. */ - public DefaultServiceInstance(String instanceId, String serviceId, String host, int port, boolean secure, - Map metadata) { + public DefaultServiceInstance(@Nullable String instanceId, @Nullable String serviceId, @Nullable String host, + int port, boolean secure, Map metadata) { this.instanceId = instanceId; this.serviceId = serviceId; this.host = host; @@ -70,7 +72,8 @@ public DefaultServiceInstance(String instanceId, String serviceId, String host, * @param port the port on which the service is running. * @param secure indicates whether or not the connection needs to be secure. */ - public DefaultServiceInstance(String instanceId, String serviceId, String host, int port, boolean secure) { + public DefaultServiceInstance(@Nullable String instanceId, @Nullable String serviceId, @Nullable String host, + int port, boolean secure) { this(instanceId, serviceId, host, port, secure, new LinkedHashMap<>()); } @@ -104,17 +107,17 @@ public Map getMetadata() { } @Override - public String getInstanceId() { + public @Nullable String getInstanceId() { return instanceId; } @Override - public String getServiceId() { + public @Nullable String getServiceId() { return serviceId; } @Override - public String getHost() { + public @Nullable String getHost() { return host; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java index b15d9ac12..c12f3dc04 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java @@ -39,12 +39,16 @@ public interface ServiceInstance { /** * @return The service ID as registered. */ - String getServiceId(); + default @Nullable String getServiceId() { + return null; + } /** * @return The hostname of the registered service instance. */ - String getHost(); + default @Nullable String getHost() { + return null; + } /** * @return The port of the registered service instance. diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java index 5aa94abac..6dc20e956 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java @@ -43,7 +43,10 @@ public String[] selectImports(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); - boolean autoRegister = attributes.getBoolean("autoRegister"); + boolean autoRegister = true; + if (attributes != null) { + autoRegister = attributes.getBoolean("autoRegister"); + } if (autoRegister) { List importsList = new ArrayList<>(Arrays.asList(imports)); diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/CompletionContext.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/CompletionContext.java index b2a401951..642aa08af 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/CompletionContext.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/CompletionContext.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + import org.springframework.core.style.ToStringCreator; /** @@ -29,11 +31,11 @@ public class CompletionContext { private final Status status; - private final Throwable throwable; + private final @Nullable Throwable throwable; - private final Response loadBalancerResponse; + private final @Nullable Response loadBalancerResponse; - private final RES clientResponse; + private final @Nullable RES clientResponse; private final Request loadBalancerRequest; @@ -55,8 +57,8 @@ public CompletionContext(Status status, Request loadBalancerRequest, Response this(status, null, loadBalancerRequest, loadBalancerResponse, clientResponse); } - public CompletionContext(Status status, Throwable throwable, Request loadBalancerRequest, - Response loadBalancerResponse, RES clientResponse) { + public CompletionContext(Status status, @Nullable Throwable throwable, Request loadBalancerRequest, + @Nullable Response loadBalancerResponse, @Nullable RES clientResponse) { this.status = status; this.throwable = throwable; this.loadBalancerRequest = loadBalancerRequest; @@ -68,15 +70,15 @@ public Status status() { return this.status; } - public Throwable getThrowable() { + public @Nullable Throwable getThrowable() { return this.throwable; } - public Response getLoadBalancerResponse() { + public @Nullable Response getLoadBalancerResponse() { return loadBalancerResponse; } - public RES getClientResponse() { + public @Nullable RES getClientResponse() { return clientResponse; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequest.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequest.java index 239098ef0..528db530e 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequest.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequest.java @@ -30,8 +30,9 @@ public class DefaultRequest implements Request { private T context; + @SuppressWarnings("unchecked") public DefaultRequest() { - new DefaultRequestContext(); + this((T) new DefaultRequestContext()); } public DefaultRequest(T context) { diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequestContext.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequestContext.java index 006a3e4d8..61b0685d6 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequestContext.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultRequestContext.java @@ -18,6 +18,8 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.core.style.ToStringCreator; /** @@ -31,22 +33,22 @@ public class DefaultRequestContext extends HintRequestContext { * The request to be executed against the service instance selected by the * LoadBalancer. */ - private final Object clientRequest; + private final @Nullable Object clientRequest; public DefaultRequestContext() { clientRequest = null; } - public DefaultRequestContext(Object clientRequest) { + public DefaultRequestContext(@Nullable Object clientRequest) { this.clientRequest = clientRequest; } - public DefaultRequestContext(Object clientRequest, String hint) { + public DefaultRequestContext(@Nullable Object clientRequest, String hint) { super(hint); this.clientRequest = clientRequest; } - public Object getClientRequest() { + public @Nullable Object getClientRequest() { return clientRequest; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DeferringLoadBalancerInterceptor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DeferringLoadBalancerInterceptor.java index e17da7278..707f32d7e 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DeferringLoadBalancerInterceptor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DeferringLoadBalancerInterceptor.java @@ -18,6 +18,8 @@ import java.io.IOException; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.ObjectProvider; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; @@ -37,7 +39,7 @@ public class DeferringLoadBalancerInterceptor implements ClientHttpRequestInterc private final ObjectProvider loadBalancerInterceptorProvider; - private BlockingLoadBalancerInterceptor delegate; + private @Nullable BlockingLoadBalancerInterceptor delegate; public DeferringLoadBalancerInterceptor( ObjectProvider loadBalancerInterceptorProvider) { @@ -45,6 +47,7 @@ public DeferringLoadBalancerInterceptor( } @Override + @SuppressWarnings("NullAway") // nullability checked in tryResolveDelegate() public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { tryResolveDelegate(); diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/EmptyResponse.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/EmptyResponse.java index e76c6c034..34cb1453b 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/EmptyResponse.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/EmptyResponse.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; /** @@ -29,7 +31,7 @@ public boolean hasServer() { } @Override - public ServiceInstance getServer() { + public @Nullable ServiceInstance getServer() { return null; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRecoveryCallback.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRecoveryCallback.java index 647430f72..c71048e50 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRecoveryCallback.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRecoveryCallback.java @@ -18,6 +18,8 @@ import java.net.URI; +import org.jspecify.annotations.Nullable; + import org.springframework.retry.RecoveryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.RetryException; @@ -39,8 +41,9 @@ public abstract class LoadBalancedRecoveryCallback implements RecoveryCall * @param uri The URI the response is from. * @return The response to be returned. */ - protected abstract T createResponse(R response, URI uri); + protected abstract T createResponse(R response, @Nullable URI uri); + @SuppressWarnings("unchecked") @Override public T recover(RetryContext context) throws Exception { Throwable lastThrowable = context.getLastThrowable(); diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryContext.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryContext.java index bb6530043..6a5c68df1 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryContext.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryContext.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; import org.springframework.http.HttpRequest; import org.springframework.retry.RetryContext; @@ -31,9 +33,9 @@ public class LoadBalancedRetryContext extends RetryContextSupport { private HttpRequest request; - private ServiceInstance serviceInstance; + private @Nullable ServiceInstance serviceInstance; - private ServiceInstance previousServiceInstance; + private @Nullable ServiceInstance previousServiceInstance; /** * Creates a new load-balanced context. @@ -65,7 +67,7 @@ public void setRequest(HttpRequest request) { * Gets the service instance used during the retry. * @return The service instance used during the retry. */ - public ServiceInstance getServiceInstance() { + public @Nullable ServiceInstance getServiceInstance() { return this.serviceInstance; } @@ -73,16 +75,16 @@ public ServiceInstance getServiceInstance() { * Sets the service instance to use during the retry. * @param serviceInstance The service instance to use during the retry. */ - public void setServiceInstance(ServiceInstance serviceInstance) { + public void setServiceInstance(@Nullable ServiceInstance serviceInstance) { setPreviousServiceInstance(this.serviceInstance); this.serviceInstance = serviceInstance; } - public ServiceInstance getPreviousServiceInstance() { + public @Nullable ServiceInstance getPreviousServiceInstance() { return previousServiceInstance; } - public void setPreviousServiceInstance(ServiceInstance previousServiceInstance) { + public void setPreviousServiceInstance(@Nullable ServiceInstance previousServiceInstance) { this.previousServiceInstance = previousServiceInstance; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryFactory.java index f1904a4a4..6e0a4ee46 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryFactory.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryFactory.java @@ -54,7 +54,7 @@ default RetryListener[] createRetryListeners(String service) { * @param service The service to create the {@link BackOffPolicy} for. * @return The {@link BackOffPolicy}. */ - default BackOffPolicy createBackOffPolicy(String service) { + default @Nullable BackOffPolicy createBackOffPolicy(String service) { return new NoBackOffPolicy(); } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryPolicy.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryPolicy.java index dbbb67558..d131c65ca 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryPolicy.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryPolicy.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + /** * Retry logic to use for the {@link LoadBalancerClient}. * @@ -71,6 +73,6 @@ public interface LoadBalancedRetryPolicy { * @param exception the {@link Throwable} to evaluate * @return true to retry on the provided exception */ - boolean retryableException(Throwable exception); + boolean retryableException(@Nullable Throwable exception); } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index d0bfbc099..d2f874d3f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.Nullable; import reactor.util.retry.RetryBackoffSpec; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -269,7 +270,7 @@ public static class HealthCheck { * Port at which the health-check request should be made. If none is set, the port * under which the requested service is available at the service instance. */ - private Integer port; + private @Nullable Integer port; /** * Indicates whether the instances should be refetched by the @@ -342,11 +343,11 @@ public void setInterval(Duration interval) { this.interval = interval; } - public Integer getPort() { + public @Nullable Integer getPort() { return port; } - public void setPort(Integer port) { + public void setPort(@Nullable Integer port) { this.port = port; } @@ -598,22 +599,22 @@ public static class ApiVersion { * Sets default version that should be used for each request. */ @Name("default") - private String defaultVersion; + private @Nullable String defaultVersion; /** * Uses the HTTP header with the given name to obtain the version. */ - private String header; + private @Nullable String header; /** * Uses the query parameter with the given name to obtain the version. */ - private String queryParameter; + private @Nullable String queryParameter; /** * Uses the path segment at the given index to obtain the version. */ - private Integer pathSegment; + private @Nullable Integer pathSegment; /** * Uses the media type parameter with the given name to obtain the version. @@ -634,35 +635,35 @@ public void setRequired(boolean required) { this.required = required; } - public String getDefaultVersion() { + public @Nullable String getDefaultVersion() { return defaultVersion; } - public void setDefaultVersion(String defaultVersion) { + public void setDefaultVersion(@Nullable String defaultVersion) { this.defaultVersion = defaultVersion; } - public String getHeader() { + public @Nullable String getHeader() { return header; } - public void setHeader(String header) { + public void setHeader(@Nullable String header) { this.header = header; } - public String getQueryParameter() { + public @Nullable String getQueryParameter() { return queryParameter; } - public void setQueryParameter(String queryParameter) { + public void setQueryParameter(@Nullable String queryParameter) { this.queryParameter = queryParameter; } - public Integer getPathSegment() { + public @Nullable Integer getPathSegment() { return pathSegment; } - public void setPathSegment(Integer pathSegment) { + public void setPathSegment(@Nullable Integer pathSegment) { this.pathSegment = pathSegment; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Request.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Request.java index 9768df2e7..4c6fff108 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Request.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Request.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + /** * Marker interface for a request. * @@ -25,7 +27,7 @@ public interface Request { // Avoid breaking backward compatibility - default C getContext() { + default @Nullable C getContext() { return null; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestDataContext.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestDataContext.java index 6e9a3340d..ad901041f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestDataContext.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestDataContext.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + import org.springframework.http.HttpMethod; /** @@ -38,12 +40,16 @@ public RequestDataContext(RequestData requestData, String hint) { super(requestData, hint); } - public RequestData getClientRequest() { + public @Nullable RequestData getClientRequest() { return (RequestData) super.getClientRequest(); } - public HttpMethod method() { - return ((RequestData) super.getClientRequest()).getHttpMethod(); + public @Nullable HttpMethod method() { + RequestData clientRequest = (RequestData) super.getClientRequest(); + if (clientRequest == null) { + return null; + } + return clientRequest.getHttpMethod(); } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Response.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Response.java index 90f3d9f15..70f4810d5 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Response.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/Response.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + /** * Response created for each request. * @@ -26,6 +28,8 @@ public interface Response { boolean hasServer(); - T getServer(); + default @Nullable T getServer() { + return null; + } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ResponseData.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ResponseData.java index 2cdf1b9f9..8c059cbc2 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ResponseData.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ResponseData.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.core.style.ToStringCreator; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; @@ -40,7 +42,7 @@ */ public class ResponseData { - private final HttpStatusCode httpStatus; + private final @Nullable HttpStatusCode httpStatus; private final HttpHeaders headers; @@ -48,8 +50,8 @@ public class ResponseData { private final RequestData requestData; - public ResponseData(HttpStatusCode httpStatus, HttpHeaders headers, MultiValueMap cookies, - RequestData requestData) { + public ResponseData(@Nullable HttpStatusCode httpStatus, HttpHeaders headers, + MultiValueMap cookies, RequestData requestData) { this.httpStatus = httpStatus; this.headers = headers; this.cookies = cookies; @@ -69,7 +71,7 @@ public ResponseData(ClientHttpResponse clientHttpResponse, RequestData requestDa buildCookiesFromHeaders(clientHttpResponse.getHeaders()), requestData); } - public HttpStatusCode getHttpStatus() { + public @Nullable HttpStatusCode getHttpStatus() { return httpStatus; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java index 58b014ad7..1f56ec015 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; @@ -35,6 +36,7 @@ import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; +import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.StreamUtils; /** @@ -135,14 +137,14 @@ public ClientHttpResponse intercept(final HttpRequest request, final byte[] body // LoadBalancedRecoveryCallback are // the same. In most cases they would be different. @Override - protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) { + protected ClientHttpResponse createResponse(ClientHttpResponse response, @Nullable URI uri) { return response; } }); } private RetryTemplate createRetryTemplate(String serviceName, HttpRequest request, - LoadBalancedRetryPolicy retryPolicy) { + @Nullable LoadBalancedRetryPolicy retryPolicy) { RetryTemplate template = new RetryTemplate(); BackOffPolicy backOffPolicy = lbRetryFactory.createBackOffPolicy(serviceName); template.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); @@ -151,15 +153,16 @@ private RetryTemplate createRetryTemplate(String serviceName, HttpRequest reques if (retryListeners != null && retryListeners.length != 0) { template.setListeners(retryListeners); } - template.setRetryPolicy( - !loadBalancerFactory.getProperties(serviceName).getRetry().isEnabled() || retryPolicy == null - ? new NeverRetryPolicy() - : new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName)); + LoadBalancerProperties properties = loadBalancerFactory.getProperties(serviceName); + boolean retryEnabled = properties == null || properties.getRetry().isEnabled(); + template.setRetryPolicy(!retryEnabled || retryPolicy == null ? new NeverRetryPolicy() + : new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName)); return template; } private String getHint(String serviceId) { - Map hint = loadBalancerFactory.getProperties(serviceId).getHint(); + LoadBalancerProperties properties = loadBalancerFactory.getProperties(serviceId); + Map hint = (properties != null) ? properties.getHint() : new LinkedCaseInsensitiveMap<>(); String defaultHint = hint.getOrDefault("default", "default"); String hintPropertyValue = hint.get(serviceId); return hintPropertyValue != null ? hintPropertyValue : defaultHint; diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableRequestContext.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableRequestContext.java index 8d049517f..8b92675eb 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableRequestContext.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableRequestContext.java @@ -18,6 +18,8 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; import org.springframework.core.style.ToStringCreator; @@ -29,28 +31,28 @@ */ public class RetryableRequestContext extends RequestDataContext { - private ServiceInstance previousServiceInstance; + private @Nullable ServiceInstance previousServiceInstance; - public RetryableRequestContext(ServiceInstance previousServiceInstance) { + public RetryableRequestContext(@Nullable ServiceInstance previousServiceInstance) { this.previousServiceInstance = previousServiceInstance; } - public RetryableRequestContext(ServiceInstance previousServiceInstance, RequestData clientRequestData) { + public RetryableRequestContext(@Nullable ServiceInstance previousServiceInstance, RequestData clientRequestData) { super(clientRequestData); this.previousServiceInstance = previousServiceInstance; } - public RetryableRequestContext(ServiceInstance previousServiceInstance, RequestData clientRequestData, + public RetryableRequestContext(@Nullable ServiceInstance previousServiceInstance, RequestData clientRequestData, String hint) { super(clientRequestData, hint); this.previousServiceInstance = previousServiceInstance; } - public ServiceInstance getPreviousServiceInstance() { + public @Nullable ServiceInstance getPreviousServiceInstance() { return previousServiceInstance; } - public void setPreviousServiceInstance(ServiceInstance previousServiceInstance) { + public void setPreviousServiceInstance(@Nullable ServiceInstance previousServiceInstance) { this.previousServiceInstance = previousServiceInstance; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java index d3aa3d7fa..0c6d5c090 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.net.URI; +import org.jspecify.annotations.Nullable; + /** * Exception to be thrown when the status code is deemed to be retryable. * @@ -30,9 +32,9 @@ public class RetryableStatusCodeException extends IOException { private final Object response; - private final URI uri; + private final @Nullable URI uri; - public RetryableStatusCodeException(String serviceId, int statusCode, Object response, URI uri) { + public RetryableStatusCodeException(String serviceId, int statusCode, Object response, @Nullable URI uri) { super(String.format(MESSAGE, serviceId, statusCode)); this.response = response; this.uri = uri; @@ -42,7 +44,7 @@ public Object getResponse() { return this.response; } - public URI getUri() { + public @Nullable URI getUri() { return this.uri; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/SimpleObjectProvider.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/SimpleObjectProvider.java index deeb344ea..ec282b155 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/SimpleObjectProvider.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/SimpleObjectProvider.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerWebClientBuilderBeanPostProcessor; @@ -38,17 +40,17 @@ public SimpleObjectProvider(T object) { } @Override - public T getObject(Object... args) throws BeansException { + public T getObject(@Nullable Object... args) throws BeansException { return this.object; } @Override - public T getIfAvailable() throws BeansException { + public @Nullable T getIfAvailable() throws BeansException { return this.object; } @Override - public T getIfUnique() throws BeansException { + public @Nullable T getIfUnique() throws BeansException { return this.object; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java index cc395d740..e0e1fc0fe 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java @@ -21,18 +21,17 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import jakarta.annotation.Nullable; import jakarta.annotation.PreDestroy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; import org.springframework.boot.web.server.context.ConfigurableWebServerApplicationContext; import org.springframework.boot.web.server.context.WebServerInitializedEvent; import org.springframework.cloud.client.discovery.ManagementServerPortUtils; import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.core.env.Environment; @@ -47,7 +46,7 @@ * @author Zen Huifer */ public abstract class AbstractAutoServiceRegistration - implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener { + implements AutoServiceRegistration, ApplicationListener { private static final Log logger = LogFactory.getLog(AbstractAutoServiceRegistration.class); @@ -71,24 +70,30 @@ public abstract class AbstractAutoServiceRegistration private List> registrationLifecycles = new ArrayList<>(); - protected AbstractAutoServiceRegistration(ServiceRegistry serviceRegistry, + protected AbstractAutoServiceRegistration(ApplicationContext context, ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties) { + this.context = context; + this.environment = context.getEnvironment(); this.serviceRegistry = serviceRegistry; this.properties = properties; } - protected AbstractAutoServiceRegistration(ServiceRegistry serviceRegistry, + protected AbstractAutoServiceRegistration(ApplicationContext context, ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties, List> registrationManagementLifecycles, List> registrationLifecycles) { + this.context = context; + this.environment = context.getEnvironment(); this.serviceRegistry = serviceRegistry; this.properties = properties; this.registrationManagementLifecycles = registrationManagementLifecycles; this.registrationLifecycles = registrationLifecycles; } - protected AbstractAutoServiceRegistration(ServiceRegistry serviceRegistry, + protected AbstractAutoServiceRegistration(ApplicationContext context, ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties, List> registrationLifecycles) { + this.context = context; + this.environment = context.getEnvironment(); this.serviceRegistry = serviceRegistry; this.properties = properties; this.registrationLifecycles = registrationLifecycles; @@ -119,12 +124,6 @@ public void onApplicationEvent(WebServerInitializedEvent event) { this.start(); } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.context = applicationContext; - this.environment = this.context.getEnvironment(); - } - @Deprecated protected Environment getEnvironment() { return this.environment; @@ -216,7 +215,7 @@ protected String getManagementServiceName() { * @return The management server port. */ @Deprecated - protected Integer getManagementPort() { + protected @Nullable Integer getManagementPort() { return ManagementServerPortUtils.getPort(this.context); } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java index 834ddc506..b405be16a 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.serviceregistry; +import org.jspecify.annotations.Nullable; + /** * Contract to register and deregister instances with a Service Registry. * @@ -30,13 +32,13 @@ public interface ServiceRegistry { * instance, such as its hostname and port. * @param registration registration meta data */ - void register(R registration); + void register(@Nullable R registration); /** * Deregisters the registration. * @param registration registration meta data */ - void deregister(R registration); + void deregister(@Nullable R registration); /** * Closes the ServiceRegistry. This is a lifecycle method. diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java index 8a987d243..e2e16d673 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java @@ -33,11 +33,7 @@ class SpringBootVersionVerifier implements CompatibilityVerifier { private static final Log log = LogFactory.getLog(SpringBootVersionVerifier.class); - final Map ACCEPTED_VERSIONS = new HashMap<>() { - { - this.put("4.0", is4_0()); - } - }; + final Map ACCEPTED_VERSIONS = new HashMap<>(Map.of("4.0", is4_0())); private final List acceptedVersions; diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationMgmtDisabledTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationMgmtDisabledTests.java index 7ad98eed3..c6471ee6b 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationMgmtDisabledTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationMgmtDisabledTests.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -54,8 +55,9 @@ public void portsWork() { public static class Config { @Bean - public TestAutoServiceRegistration testAutoServiceRegistration(AutoServiceRegistrationProperties properties) { - return new TestAutoServiceRegistration(properties); + public TestAutoServiceRegistration testAutoServiceRegistration(ApplicationContext context, + AutoServiceRegistrationProperties properties) { + return new TestAutoServiceRegistration(context, properties); } } @@ -163,12 +165,12 @@ public static class TestAutoServiceRegistration extends AbstractAutoServiceRegis private int port = 0; - public TestAutoServiceRegistration(AutoServiceRegistrationProperties properties) { - super(new TestServiceRegistry(), properties); + public TestAutoServiceRegistration(ApplicationContext context, AutoServiceRegistrationProperties properties) { + super(context, new TestServiceRegistry(), properties); } - protected TestAutoServiceRegistration() { - super(new TestServiceRegistry(), new AutoServiceRegistrationProperties()); + protected TestAutoServiceRegistration(ApplicationContext context) { + super(context, new TestServiceRegistry(), new AutoServiceRegistrationProperties()); } @Override diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationRegistrationLifecycleTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationRegistrationLifecycleTests.java index afccbb33d..7b496da23 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationRegistrationLifecycleTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationRegistrationLifecycleTests.java @@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -94,11 +95,11 @@ public void postProcessAfterStopRegister(TestRegistrationLifecycleRegistration r static class Config { @Bean - public TestAutoServiceRegistrationLifecycle testAutoServiceRegistration( + public TestAutoServiceRegistrationLifecycle testAutoServiceRegistration(ApplicationContext applicationContext, AutoServiceRegistrationProperties properties) { List> registrationLifecycles = new ArrayList<>(); registrationLifecycles.add(new RegistrationLifecycleImpl()); - return new TestAutoServiceRegistrationLifecycle(properties, registrationLifecycles); + return new TestAutoServiceRegistrationLifecycle(applicationContext, properties, registrationLifecycles); } } @@ -110,24 +111,24 @@ static class TestAutoServiceRegistrationLifecycle TestRegistrationLifecycleRegistration testRegistrationLifecycleRegistration = new TestRegistrationLifecycleRegistration(); - protected TestAutoServiceRegistrationLifecycle() { - super(new TestRegistrationLifecycleServiceRegistration(), new AutoServiceRegistrationProperties()); + protected TestAutoServiceRegistrationLifecycle(ApplicationContext context) { + super(context, new TestRegistrationLifecycleServiceRegistration(), new AutoServiceRegistrationProperties()); } - TestAutoServiceRegistrationLifecycle(AutoServiceRegistrationProperties properties) { - super(new TestRegistrationLifecycleServiceRegistration(), properties); + TestAutoServiceRegistrationLifecycle(ApplicationContext context, AutoServiceRegistrationProperties properties) { + super(context, new TestRegistrationLifecycleServiceRegistration(), properties); } - TestAutoServiceRegistrationLifecycle(AutoServiceRegistrationProperties properties, + TestAutoServiceRegistrationLifecycle(ApplicationContext context, AutoServiceRegistrationProperties properties, List> registrationManagementLifecycles, List> registrationLifecycles) { - super(new TestRegistrationLifecycleServiceRegistration(), properties, registrationManagementLifecycles, - registrationLifecycles); + super(context, new TestRegistrationLifecycleServiceRegistration(), properties, + registrationManagementLifecycles, registrationLifecycles); } - TestAutoServiceRegistrationLifecycle(AutoServiceRegistrationProperties properties, + TestAutoServiceRegistrationLifecycle(ApplicationContext context, AutoServiceRegistrationProperties properties, List> registrationLifecycles) { - super(new TestRegistrationLifecycleServiceRegistration(), properties, registrationLifecycles); + super(context, new TestRegistrationLifecycleServiceRegistration(), properties, registrationLifecycles); } @Override diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java index 4f74547e4..b270d6a97 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +30,7 @@ import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -94,8 +96,8 @@ public void eventsFireTest() { public static class Config { @Bean - public TestAutoServiceRegistration testAutoServiceRegistration() { - return new TestAutoServiceRegistration(); + public TestAutoServiceRegistration testAutoServiceRegistration(ApplicationContext context) { + return new TestAutoServiceRegistration(context); } @Bean @@ -213,7 +215,7 @@ public static class TestServiceRegistry implements ServiceRegistry Date: Fri, 21 Nov 2025 16:09:10 -0500 Subject: [PATCH 3/3] Enables jspecify spring-cloud-loadbalancer --- .../cloud/client/DefaultServiceInstance.java | 6 +- .../cloud/client/ServiceInstance.java | 10 +- .../CircuitBreakerConfigurerUtils.java | 3 + .../client/loadbalancer/DefaultResponse.java | 6 +- .../loadbalancer/LoadBalancerProperties.java | 7 +- .../LoadBalancerRequestTransformer.java | 4 +- .../client/loadbalancer/RequestData.java | 12 +- .../context/named/NamedContextFactory.java | 4 +- ...ApiVersionServiceInstanceListSupplier.java | 23 ++-- ...lockingLoadBalancerApiVersionStrategy.java | 13 +- ...veryClientServiceInstanceListSupplier.java | 9 +- ...ealthCheckServiceInstanceListSupplier.java | 9 +- .../HintBasedServiceInstanceListSupplier.java | 16 ++- .../core/LazyWeightedServiceInstanceList.java | 9 +- .../core/LoadBalancerHttpServletRequest.java | 128 ++++++++++++++---- .../core/LoadBalancerServerHttpRequest.java | 4 +- ...ancerServiceInstanceCookieTransformer.java | 21 ++- .../loadbalancer/core/RandomLoadBalancer.java | 1 + ...ApiVersionServiceInstanceListSupplier.java | 28 ++-- ...eactiveLoadBalancerApiVersionStrategy.java | 10 +- ...ckySessionServiceInstanceListSupplier.java | 15 +- .../core/RoundRobinLoadBalancer.java | 1 + ...PreferenceServiceInstanceListSupplier.java | 18 +-- .../ServiceInstanceListSupplierBuilder.java | 2 +- .../SubsetServiceInstanceListSupplier.java | 3 + .../WeightedServiceInstanceListSupplier.java | 10 +- .../core/XForwardedHeadersTransformer.java | 9 +- ...PreferenceServiceInstanceListSupplier.java | 21 ++- .../LoadBalancerClientConfigurationTests.java | 77 +++++++---- ...rviceInstanceListSupplierBuilderTests.java | 50 +++---- 30 files changed, 364 insertions(+), 165 deletions(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java index 2b068a40f..1751cec12 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java @@ -56,13 +56,15 @@ public class DefaultServiceInstance implements ServiceInstance { * @param metadata a map containing metadata. */ public DefaultServiceInstance(@Nullable String instanceId, @Nullable String serviceId, @Nullable String host, - int port, boolean secure, Map metadata) { + int port, boolean secure, @Nullable Map metadata) { this.instanceId = instanceId; this.serviceId = serviceId; this.host = host; this.port = port; this.secure = secure; - this.metadata = metadata; + if (metadata != null) { + this.metadata = metadata; + } } /** diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java index c12f3dc04..85f1bef22 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/ServiceInstance.java @@ -39,16 +39,12 @@ public interface ServiceInstance { /** * @return The service ID as registered. */ - default @Nullable String getServiceId() { - return null; - } + @Nullable String getServiceId(); /** * @return The hostname of the registered service instance. */ - default @Nullable String getHost() { - return null; - } + @Nullable String getHost(); /** * @return The port of the registered service instance. @@ -68,7 +64,7 @@ public interface ServiceInstance { /** * @return The key / value pair metadata associated with the service instance. */ - Map getMetadata(); + @Nullable Map getMetadata(); /** * @return The scheme of the service instance. diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerConfigurerUtils.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerConfigurerUtils.java index 56f6a706a..5d781fb74 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerConfigurerUtils.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerConfigurerUtils.java @@ -48,6 +48,9 @@ */ final class CircuitBreakerConfigurerUtils { + /** + * Default fallback key. + */ public static final String DEFAULT_FALLBACK_KEY = "default"; private CircuitBreakerConfigurerUtils() { diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultResponse.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultResponse.java index 387b0004e..0169612e3 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultResponse.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/DefaultResponse.java @@ -18,6 +18,8 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; import org.springframework.core.style.ToStringCreator; @@ -27,7 +29,7 @@ */ public class DefaultResponse implements Response { - private final ServiceInstance serviceInstance; + private final @Nullable ServiceInstance serviceInstance; public DefaultResponse(ServiceInstance serviceInstance) { this.serviceInstance = serviceInstance; @@ -39,7 +41,7 @@ public boolean hasServer() { } @Override - public ServiceInstance getServer() { + public @Nullable ServiceInstance getServer() { return this.serviceInstance; } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index d2f874d3f..6295e3010 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -196,10 +196,15 @@ public void setApiVersion(ApiVersion apiVersion) { public static class StickySession { + /** + * The default name of the cookie holding the preferred instance id. + */ + public static final String DEFAULT_INSTANCE_ID_COOKIE_NAME = "sc-lb-instance-id"; + /** * The name of the cookie holding the preferred instance id. */ - private String instanceIdCookieName = "sc-lb-instance-id"; + private String instanceIdCookieName = DEFAULT_INSTANCE_ID_COOKIE_NAME; /** * Indicates whether a cookie with the newly selected instance should be added by diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequestTransformer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequestTransformer.java index 3b314b95a..f3effba30 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequestTransformer.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequestTransformer.java @@ -16,6 +16,8 @@ package org.springframework.cloud.client.loadbalancer; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; import org.springframework.core.annotation.Order; import org.springframework.http.HttpRequest; @@ -34,6 +36,6 @@ public interface LoadBalancerRequestTransformer { */ int DEFAULT_ORDER = 0; - HttpRequest transformRequest(HttpRequest request, ServiceInstance instance); + HttpRequest transformRequest(HttpRequest request, @Nullable ServiceInstance instance); } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java index 63c60ad11..b07f15707 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.core.style.ToStringCreator; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; @@ -46,14 +48,14 @@ public class RequestData { private final URI url; - private final HttpHeaders headers; + private final @Nullable HttpHeaders headers; private final MultiValueMap cookies; private final Map attributes; - public RequestData(HttpMethod httpMethod, URI url, HttpHeaders headers, MultiValueMap cookies, - Map attributes) { + public RequestData(HttpMethod httpMethod, URI url, @Nullable HttpHeaders headers, + MultiValueMap cookies, Map attributes) { this.httpMethod = httpMethod; this.url = url; this.headers = headers; @@ -115,11 +117,11 @@ public URI getUrl() { return url; } - public HttpHeaders getHeaders() { + public @Nullable HttpHeaders getHeaders() { return headers; } - public MultiValueMap getCookies() { + public @Nullable MultiValueMap getCookies() { return cookies; } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java index cf1d65af0..ca47e32b8 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java @@ -27,6 +27,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.AotDetector; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; @@ -196,7 +198,7 @@ protected String generateDisplayName(String name) { return this.getClass().getSimpleName() + "-" + name; } - public T getInstance(String name, Class type) { + public @Nullable T getInstance(String name, Class type) { GenericApplicationContext context = getContext(name); try { return context.getBean(type); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingApiVersionServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingApiVersionServiceInstanceListSupplier.java index 14dfe00f7..449d3ba64 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingApiVersionServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingApiVersionServiceInstanceListSupplier.java @@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; @@ -65,9 +66,9 @@ public class BlockingApiVersionServiceInstanceListSupplier extends DelegatingSer private final LoadBalancerClientFactory loadBalancerClientFactory; - private ApiVersionParser apiVersionParser; + private @Nullable ApiVersionParser apiVersionParser; - private ApiVersionStrategy apiVersionStrategy; + private @Nullable ApiVersionStrategy apiVersionStrategy; public BlockingApiVersionServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, LoadBalancerClientFactory loadBalancerClientFactory) { @@ -75,8 +76,14 @@ public BlockingApiVersionServiceInstanceListSupplier(ServiceInstanceListSupplier String serviceId = getServiceId(); this.loadBalancerClientFactory = loadBalancerClientFactory; LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId); - callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); - apiVersionProperties = properties.getApiVersion(); + if (properties != null) { + callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); + apiVersionProperties = properties.getApiVersion(); + } + else { + callGetWithRequestOnDelegates = true; + apiVersionProperties = new LoadBalancerProperties.ApiVersion(); + } } @Override @@ -97,7 +104,7 @@ public Flux> get() { } private List filteredByVersion(List serviceInstances, - Comparable requestedVersion) { + @Nullable Comparable requestedVersion) { if (LOG.isDebugEnabled()) { LOG.debug("Matching instances by API Version: " + requestedVersion); } @@ -132,7 +139,7 @@ void setApiVersionParser(ApiVersionParser apiVersionParser) { this.apiVersionParser = apiVersionParser; } - private Comparable getVersionFromRequest(RequestData requestData) { + private @Nullable Comparable getVersionFromRequest(@Nullable RequestData requestData) { HttpServletRequest servletRequest = new LoadBalancerHttpServletRequest(requestData); Comparable apiVersion = getApiVersionStrategy().resolveParseAndValidateVersion(servletRequest); if (LOG.isDebugEnabled()) { @@ -141,7 +148,7 @@ private Comparable getVersionFromRequest(RequestData requestData) { return apiVersion; } - private Comparable getVersion(ServiceInstance serviceInstance) { + private @Nullable Comparable getVersion(ServiceInstance serviceInstance) { Map metadata = serviceInstance.getMetadata(); if (metadata != null) { String version = metadata.get(API_VERSION); @@ -152,7 +159,7 @@ private Comparable getVersion(ServiceInstance serviceInstance) { return null; } - private ApiVersionParser getApiVersionParser() { + private @Nullable ApiVersionParser getApiVersionParser() { if (apiVersionParser == null) { apiVersionParser = loadBalancerClientFactory.getInstance(getServiceId(), ApiVersionParser.class); } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingLoadBalancerApiVersionStrategy.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingLoadBalancerApiVersionStrategy.java index 2d362b583..730b43646 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingLoadBalancerApiVersionStrategy.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/BlockingLoadBalancerApiVersionStrategy.java @@ -40,12 +40,17 @@ */ public class BlockingLoadBalancerApiVersionStrategy extends DefaultApiVersionStrategy { + private static final ApiVersionParser EMPTY_API_VERSION_PARSER = version -> { + throw new InvalidApiVersionException("No valid ApiVersionParserFound: " + version); + }; + public BlockingLoadBalancerApiVersionStrategy(List versionResolvers, - ApiVersionParser versionParser, @Nullable Boolean versionRequired, @Nullable String defaultVersion, - boolean detectSupportedVersions, @Nullable Predicate> supportedVersionPredicate, + @Nullable ApiVersionParser versionParser, @Nullable Boolean versionRequired, + @Nullable String defaultVersion, boolean detectSupportedVersions, + @Nullable Predicate> supportedVersionPredicate, @Nullable ApiVersionDeprecationHandler deprecationHandler) { - super(versionResolvers, versionParser, versionRequired, defaultVersion, detectSupportedVersions, - supportedVersionPredicate, deprecationHandler); + super(versionResolvers, (versionParser != null) ? versionParser : EMPTY_API_VERSION_PARSER, versionRequired, + defaultVersion, detectSupportedVersions, supportedVersionPredicate, deprecationHandler); } @Override diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java index 752e175eb..e5c6fd36e 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DiscoveryClientServiceInstanceListSupplier.java @@ -31,6 +31,7 @@ import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.core.env.Environment; +import org.springframework.util.Assert; import static org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory.PROPERTY_NAME; @@ -59,7 +60,9 @@ public class DiscoveryClientServiceInstanceListSupplier implements ServiceInstan private final Flux> serviceInstances; public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, Environment environment) { - this.serviceId = environment.getProperty(PROPERTY_NAME); + String property = environment.getProperty(PROPERTY_NAME); + Assert.hasText(property, "'serviceId' must not be empty"); + this.serviceId = property; resolveTimeout(environment); this.serviceInstances = Flux.defer(() -> Mono.fromCallable(() -> delegate.getInstances(serviceId))) .timeout(timeout, Flux.defer(() -> { @@ -73,7 +76,9 @@ public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, Envi } public DiscoveryClientServiceInstanceListSupplier(ReactiveDiscoveryClient delegate, Environment environment) { - this.serviceId = environment.getProperty(PROPERTY_NAME); + String property = environment.getProperty(PROPERTY_NAME); + Assert.hasText(property, "'serviceId' must not be empty"); + this.serviceId = property; resolveTimeout(environment); this.serviceInstances = Flux .defer(() -> delegate.getInstances(serviceId).collectList().flux().timeout(timeout, Flux.defer(() -> { diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java index ec89e7900..5fedce99a 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -56,7 +57,7 @@ public class HealthCheckServiceInstanceListSupplier extends DelegatingServiceIns private final Flux> aliveInstancesReplay; - private Disposable healthCheckDisposable; + private @Nullable Disposable healthCheckDisposable; private final BiFunction> aliveFunction; @@ -64,7 +65,9 @@ public HealthCheckServiceInstanceListSupplier(ServiceInstanceListSupplier delega ReactiveLoadBalancer.Factory loadBalancerClientFactory, BiFunction> aliveFunction) { super(delegate); - this.healthCheck = loadBalancerClientFactory.getProperties(getServiceId()).getHealthCheck(); + LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(getServiceId()); + this.healthCheck = (properties == null) ? new LoadBalancerProperties.HealthCheck() + : properties.getHealthCheck(); defaultHealthCheckPath = healthCheck.getPath().getOrDefault("default", "/actuator/health"); this.aliveFunction = aliveFunction; Repeat aliveInstancesReplayRepeat = Repeat @@ -72,7 +75,7 @@ public HealthCheckServiceInstanceListSupplier(ServiceInstanceListSupplier delega .fixedBackoff(healthCheck.getRefetchInstancesInterval()); Flux> aliveInstancesFlux = Flux.defer(delegate) .repeatWhen(aliveInstancesReplayRepeat) - .switchMap(serviceInstances -> healthCheckFlux(serviceInstances).map(alive -> List.copyOf(alive))); + .switchMap(serviceInstances -> healthCheckFlux(serviceInstances).map(List::copyOf)); aliveInstancesReplay = aliveInstancesFlux.delaySubscription(healthCheck.getInitialDelay()) .replay(1) .refCount(1); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HintBasedServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HintBasedServiceInstanceListSupplier.java index f301c1805..da6f305bc 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HintBasedServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HintBasedServiceInstanceListSupplier.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; @@ -41,10 +42,12 @@ public class HintBasedServiceInstanceListSupplier extends DelegatingServiceInsta private final LoadBalancerProperties properties; + @SuppressWarnings("NullAway") public HintBasedServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, ReactiveLoadBalancer.Factory factory) { super(delegate); - this.properties = factory.getProperties(getServiceId()); + this.properties = (factory.getProperties(getServiceId()) != null) ? factory.getProperties(getServiceId()) + : new LoadBalancerProperties(); } @Override @@ -57,7 +60,7 @@ public Flux> get(Request request) { return delegate.get(request).map(instances -> filteredByHint(instances, getHint(request.getContext()))); } - private String getHint(Object requestContext) { + private @Nullable String getHint(@Nullable Object requestContext) { if (requestContext == null) { return null; } @@ -71,7 +74,7 @@ private String getHint(Object requestContext) { return hint; } - private String getHintFromHeader(RequestDataContext context) { + private @Nullable String getHintFromHeader(RequestDataContext context) { if (context.getClientRequest() != null) { HttpHeaders headers = context.getClientRequest().getHeaders(); if (headers != null) { @@ -81,17 +84,18 @@ private String getHintFromHeader(RequestDataContext context) { return null; } - private List filteredByHint(List instances, String hint) { + private List filteredByHint(List instances, @Nullable String hint) { if (!StringUtils.hasText(hint)) { return instances; } List filteredInstances = new ArrayList<>(); for (ServiceInstance serviceInstance : instances) { - if (serviceInstance.getMetadata().getOrDefault("hint", "").equals(hint)) { + if (serviceInstance.getMetadata() != null + && serviceInstance.getMetadata().getOrDefault("hint", "").equals(hint)) { filteredInstances.add(serviceInstance); } } - if (filteredInstances.size() > 0) { + if (!filteredInstances.isEmpty()) { return filteredInstances; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java index d64de9328..720fa8f24 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Queue; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; /** @@ -36,7 +38,7 @@ class LazyWeightedServiceInstanceList extends AbstractList { private final Object expandingLock = new Object(); - private WeightedServiceInstanceSelector selector; + private @Nullable WeightedServiceInstanceSelector selector; private volatile int position = 0; @@ -58,7 +60,9 @@ public ServiceInstance get(int index) { if (index >= position) { synchronized (expandingLock) { for (; position <= index && position < expanded.length; position++) { - expanded[position] = selector.next(); + if (selector != null) { + expanded[position] = selector.next(); + } } if (position == expanded.length) { selector = null; // for gc @@ -102,6 +106,7 @@ static class WeightedServiceInstanceSelector { } } + @SuppressWarnings("NullAway") // see comment below ServiceInstance next() { if (active.isEmpty()) { Queue temp = active; diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerHttpServletRequest.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerHttpServletRequest.java index 3a68af2a4..c5e8fffa4 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerHttpServletRequest.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerHttpServletRequest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; +import java.nio.charset.Charset; import java.security.Principal; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -48,6 +49,7 @@ import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpUpgradeHandler; import jakarta.servlet.http.Part; +import org.jspecify.annotations.Nullable; import org.springframework.cloud.client.loadbalancer.RequestData; import org.springframework.http.HttpHeaders; @@ -70,14 +72,17 @@ */ public class LoadBalancerHttpServletRequest implements HttpServletRequest { - private final RequestData requestData; + private final @Nullable RequestData requestData; - public LoadBalancerHttpServletRequest(RequestData requestData) { + public LoadBalancerHttpServletRequest(@Nullable RequestData requestData) { this.requestData = requestData; } @Override - public String getAuthType() { + public @Nullable String getAuthType() { + if (requestData == null || requestData.getHeaders() == null) { + return null; + } String authHeader = requestData.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); if (authHeader == null) { return null; @@ -96,6 +101,9 @@ public String getAuthType() { @Override public Cookie[] getCookies() { + if (requestData == null) { + return new Cookie[0]; + } MultiValueMap cookies = requestData.getCookies(); if (cookies == null || cookies.isEmpty()) { return new Cookie[0]; @@ -110,7 +118,8 @@ public Cookie[] getCookies() { @Override public long getDateHeader(String name) { - String headerValue = requestData.getHeaders().getFirst(name); + String headerValue = (requestData == null || requestData.getHeaders() == null) ? null + : requestData.getHeaders().getFirst(name); if (headerValue == null) { return -1L; } @@ -126,18 +135,27 @@ public long getDateHeader(String name) { } @Override - public String getHeader(String name) { + public @Nullable String getHeader(String name) { + if (requestData == null || requestData.getHeaders() == null) { + return null; + } return requestData.getHeaders().getFirst(name); } @Override public Enumeration getHeaders(String name) { + if (requestData == null || requestData.getHeaders() == null) { + return Collections.emptyEnumeration(); + } List headerValues = requestData.getHeaders().get(name); return headerValues != null ? Collections.enumeration(headerValues) : Collections.emptyEnumeration(); } @Override public Enumeration getHeaderNames() { + if (requestData == null || requestData.getHeaders() == null) { + return Collections.emptyEnumeration(); + } HttpHeaders headers = requestData.getHeaders(); Set headerNames = headers.headerNames(); return Collections.enumeration(headerNames); @@ -145,6 +163,9 @@ public Enumeration getHeaderNames() { @Override public int getIntHeader(String name) { + if (requestData == null || requestData.getHeaders() == null) { + return -1; + } String headerValue = requestData.getHeaders().getFirst(name); if (headerValue == null) { return -1; @@ -158,12 +179,18 @@ public int getIntHeader(String name) { } @Override - public String getMethod() { + public @Nullable String getMethod() { + if (requestData == null) { + return null; + } return requestData.getHttpMethod().name(); } @Override - public String getPathInfo() { + public @Nullable String getPathInfo() { + if (requestData == null) { + return null; + } URI uri = requestData.getUrl(); return uri.getPath(); } @@ -179,12 +206,15 @@ public String getContextPath() { } @Override - public String getQueryString() { + public @Nullable String getQueryString() { + if (requestData == null) { + return null; + } return requestData.getUrl().getRawQuery(); } @Override - public String getRemoteUser() { + public @Nullable String getRemoteUser() { Principal principal = getUserPrincipal(); return principal != null ? principal.getName() : null; } @@ -195,7 +225,10 @@ public boolean isUserInRole(String role) { } @Override - public Principal getUserPrincipal() { + public @Nullable Principal getUserPrincipal() { + if (requestData == null) { + return null; + } Object principal = requestData.getAttributes().get(Principal.class.getName()); if (principal instanceof Principal) { return (Principal) principal; @@ -209,12 +242,18 @@ public String getRequestedSessionId() { } @Override - public String getRequestURI() { + public @Nullable String getRequestURI() { + if (requestData == null) { + return null; + } return requestData.getUrl().getRawPath(); } @Override - public StringBuffer getRequestURL() { + public @Nullable StringBuffer getRequestURL() { + if (requestData == null) { + return null; + } URI uri = requestData.getUrl(); StringBuffer url = new StringBuffer(); url.append(uri.getScheme()).append("://").append(uri.getHost()); @@ -284,7 +323,7 @@ public Collection getParts() throws IOException, ServletException { } @Override - public Part getPart(String name) throws IOException, ServletException { + public @Nullable Part getPart(String name) throws IOException, ServletException { return null; } @@ -295,23 +334,36 @@ public T upgrade(Class httpUpgradeHandlerClass } @Override - public Object getAttribute(String name) { + public @Nullable Object getAttribute(String name) { + if (requestData == null) { + return null; + } return requestData.getAttributes().get(name); } @Override public Enumeration getAttributeNames() { + if (requestData == null) { + return Collections.emptyEnumeration(); + } return Collections.enumeration(requestData.getAttributes().keySet()); } @Override - public String getCharacterEncoding() { + public @Nullable String getCharacterEncoding() { + if (requestData == null || requestData.getHeaders() == null) { + return null; + } String contentTypeHeader = requestData.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); if (contentTypeHeader == null) { return null; } try { - return MediaType.parseMediaType(contentTypeHeader).getCharset().name(); + Charset charset = MediaType.parseMediaType(contentTypeHeader).getCharset(); + if (charset == null) { + return null; + } + return charset.name(); } catch (Exception e) { return null; @@ -334,6 +386,9 @@ public int getContentLength() { @Override public long getContentLengthLong() { + if (requestData == null || requestData.getHeaders() == null) { + return -1L; + } String contentLength = requestData.getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH); if (contentLength == null) { return -1L; @@ -347,7 +402,10 @@ public long getContentLengthLong() { } @Override - public String getContentType() { + public @Nullable String getContentType() { + if (requestData == null || requestData.getHeaders() == null) { + return null; + } return requestData.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); } @@ -357,7 +415,7 @@ public ServletInputStream getInputStream() throws IOException { } @Override - public String getParameter(String name) { + public @Nullable String getParameter(String name) { String[] values = getParameterMap().get(name); return values != null && values.length > 0 ? values[0] : null; } @@ -368,12 +426,16 @@ public Enumeration getParameterNames() { } @Override + @SuppressWarnings("NullAway") public String[] getParameterValues(String name) { return getParameterMap().get(name); } @Override public Map getParameterMap() { + if (requestData == null) { + return Collections.emptyMap(); + } return UriComponentsBuilder.fromUri(requestData.getUrl()) .build() .getQueryParams() @@ -388,18 +450,27 @@ public String getProtocol() { } @Override - public String getScheme() { + public @Nullable String getScheme() { + if (requestData == null) { + return null; + } return requestData.getUrl().getScheme(); } @Override - public String getServerName() { + public @Nullable String getServerName() { + if (requestData == null) { + return null; + } return requestData.getUrl().getHost(); } @Override public int getServerPort() { - int port = requestData.getUrl().getPort(); + int port = -1; + if (requestData != null) { + port = requestData.getUrl().getPort(); + } if (port == -1) { return "https".equals(getScheme()) ? 443 : 80; } @@ -422,18 +493,20 @@ public String getRemoteHost() { } @Override - public void setAttribute(String name, Object o) { + public void setAttribute(String name, @Nullable Object o) { if (o == null) { removeAttribute(name); } - else { + else if (requestData != null) { requestData.getAttributes().put(name, o); } } @Override public void removeAttribute(String name) { - requestData.getAttributes().remove(name); + if (requestData != null) { + requestData.getAttributes().remove(name); + } } @Override @@ -448,6 +521,9 @@ public Enumeration getLocales() { @Override public boolean isSecure() { + if (requestData == null) { + return false; + } return "https".equalsIgnoreCase(requestData.getUrl().getScheme()); } @@ -462,7 +538,7 @@ public int getRemotePort() { } @Override - public String getLocalName() { + public @Nullable String getLocalName() { return getServerName(); } @@ -503,7 +579,7 @@ public boolean isAsyncSupported() { } @Override - public AsyncContext getAsyncContext() { + public @Nullable AsyncContext getAsyncContext() { return null; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServerHttpRequest.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServerHttpRequest.java index 58bd0d666..25090d2ce 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServerHttpRequest.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServerHttpRequest.java @@ -22,6 +22,7 @@ import org.springframework.cloud.client.loadbalancer.RequestData; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.AbstractServerHttpRequest; import org.springframework.http.server.reactive.SslInfo; import org.springframework.util.LinkedMultiValueMap; @@ -47,7 +48,8 @@ final class LoadBalancerServerHttpRequest extends AbstractServerHttpRequest { private final RequestData requestData; LoadBalancerServerHttpRequest(RequestData requestData) { - super(requestData.getHttpMethod(), requestData.getUrl(), null, requestData.getHeaders()); + super(requestData.getHttpMethod(), requestData.getUrl(), null, + (requestData.getHeaders() != null) ? requestData.getHeaders() : new HttpHeaders()); this.requestData = requestData; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java index 1bac86abc..742fe6393 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java @@ -19,11 +19,13 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer; -import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.Factory; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; @@ -38,20 +40,25 @@ */ public class LoadBalancerServiceInstanceCookieTransformer implements LoadBalancerRequestTransformer { - private ReactiveLoadBalancer.Factory factory; + private @Nullable Factory factory; - public LoadBalancerServiceInstanceCookieTransformer(ReactiveLoadBalancer.Factory factory) { + public LoadBalancerServiceInstanceCookieTransformer(@Nullable Factory factory) { this.factory = factory; } @Override - public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) { + @SuppressWarnings("NullAway") + public HttpRequest transformRequest(HttpRequest request, @Nullable ServiceInstance instance) { if (instance == null) { return request; } - LoadBalancerProperties.StickySession stickySession = factory != null - ? factory.getProperties(instance.getServiceId()).getStickySession() - : new LoadBalancerProperties.StickySession(); + LoadBalancerProperties.StickySession stickySession; + if (factory != null && factory.getProperties(instance.getServiceId()) != null) { + stickySession = factory.getProperties(instance.getServiceId()).getStickySession(); + } + else { + stickySession = new LoadBalancerProperties.StickySession(); + } if (!stickySession.isAddServiceInstanceCookie()) { return request; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RandomLoadBalancer.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RandomLoadBalancer.java index cd6462fdf..d45534572 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RandomLoadBalancer.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RandomLoadBalancer.java @@ -67,6 +67,7 @@ public Mono> choose(Request request) { .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); } + @SuppressWarnings("NullAway") // guarded by hasServer() private Response processInstanceResponse(ServiceInstanceListSupplier supplier, List serviceInstances) { Response serviceInstanceResponse = getInstanceResponse(serviceInstances); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveApiVersionServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveApiVersionServiceInstanceListSupplier.java index 8844cba56..a8f4a1ca2 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveApiVersionServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveApiVersionServiceInstanceListSupplier.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; @@ -70,9 +71,9 @@ public class ReactiveApiVersionServiceInstanceListSupplier extends DelegatingSer private final LoadBalancerClientFactory loadBalancerClientFactory; - private ApiVersionParser apiVersionParser; + private @Nullable ApiVersionParser apiVersionParser; - private ApiVersionStrategy apiVersionStrategy; + private @Nullable ApiVersionStrategy apiVersionStrategy; public ReactiveApiVersionServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, LoadBalancerClientFactory loadBalancerClientFactory) { @@ -80,8 +81,14 @@ public ReactiveApiVersionServiceInstanceListSupplier(ServiceInstanceListSupplier String serviceId = getServiceId(); this.loadBalancerClientFactory = loadBalancerClientFactory; LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId); - callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); - apiVersionProperties = properties.getApiVersion(); + if (properties != null) { + callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); + apiVersionProperties = properties.getApiVersion(); + } + else { + callGetWithRequestOnDelegates = true; + apiVersionProperties = new LoadBalancerProperties.ApiVersion(); + } } @Override @@ -102,7 +109,7 @@ public Flux> get() { } private List filteredByVersion(List serviceInstances, - Comparable requestedVersion) { + @Nullable Comparable requestedVersion) { if (LOG.isDebugEnabled()) { LOG.debug("Matching instances by API Version: " + requestedVersion); } @@ -137,7 +144,10 @@ void setApiVersionParser(ApiVersionParser apiVersionParser) { this.apiVersionParser = apiVersionParser; } - private Comparable getVersionFromRequest(RequestData requestData) { + private @Nullable Comparable getVersionFromRequest(@Nullable RequestData requestData) { + if (requestData == null) { + return null; + } ServerWebExchange exchange = buildServerWebExchange(requestData); Comparable apiVersion = getApiVersionStrategy().resolveParseAndValidateVersion(exchange); if (LOG.isDebugEnabled()) { @@ -146,7 +156,7 @@ private Comparable getVersionFromRequest(RequestData requestData) { return apiVersion; } - private Comparable getVersion(ServiceInstance serviceInstance) { + private @Nullable Comparable getVersion(ServiceInstance serviceInstance) { Map metadata = serviceInstance.getMetadata(); if (metadata != null) { String version = metadata.get(API_VERSION); @@ -164,7 +174,8 @@ private static ServerWebExchange buildServerWebExchange(RequestData requestData) new DefaultServerCodecConfigurer(), new AcceptHeaderLocaleContextResolver()); } - private ApiVersionParser getApiVersionParser() { + @SuppressWarnings("rawtypes") + private @Nullable ApiVersionParser getApiVersionParser() { if (apiVersionParser == null) { apiVersionParser = loadBalancerClientFactory.getInstance(getServiceId(), ApiVersionParser.class); } @@ -181,6 +192,7 @@ private ApiVersionStrategy getApiVersionStrategy() { return apiVersionStrategy; } + @SuppressWarnings("NullAway") // guarded by hasText() private ApiVersionStrategy buildApiVersionStrategy() { List versionResolvers = new ArrayList<>(); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveLoadBalancerApiVersionStrategy.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveLoadBalancerApiVersionStrategy.java index 5ff87922a..e5b9e9a57 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveLoadBalancerApiVersionStrategy.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ReactiveLoadBalancerApiVersionStrategy.java @@ -40,12 +40,16 @@ */ public class ReactiveLoadBalancerApiVersionStrategy extends DefaultApiVersionStrategy { + private static final ApiVersionParser EMPTY_API_VERSION_PARSER = version -> { + throw new InvalidApiVersionException("No valid ApiVersionParserFound: " + version); + }; + public ReactiveLoadBalancerApiVersionStrategy(List versionResolvers, - ApiVersionParser versionParser, boolean versionRequired, @Nullable String defaultVersion, + @Nullable ApiVersionParser versionParser, boolean versionRequired, @Nullable String defaultVersion, boolean detectSupportedVersions, @Nullable Predicate> supportedVersionPredicate, @Nullable ApiVersionDeprecationHandler deprecationHandler) { - super(versionResolvers, versionParser, versionRequired, defaultVersion, detectSupportedVersions, - supportedVersionPredicate, deprecationHandler); + super(versionResolvers, (versionParser != null) ? versionParser : EMPTY_API_VERSION_PARSER, versionRequired, + defaultVersion, detectSupportedVersions, supportedVersionPredicate, deprecationHandler); } @Override diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplier.java index d3fbe4955..1124ca542 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplier.java @@ -21,11 +21,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.RequestData; import org.springframework.cloud.client.loadbalancer.RequestDataContext; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.util.MultiValueMap; @@ -41,7 +43,7 @@ public class RequestBasedStickySessionServiceInstanceListSupplier extends Delega private static final Log LOG = LogFactory.getLog(RequestBasedStickySessionServiceInstanceListSupplier.class); - private final LoadBalancerProperties properties; + private final @Nullable LoadBalancerProperties properties; public RequestBasedStickySessionServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, ReactiveLoadBalancer.Factory loadBalancerClientFactory) { @@ -57,10 +59,17 @@ public Flux> get() { @SuppressWarnings("rawtypes") @Override public Flux> get(Request request) { - String instanceIdCookieName = properties.getStickySession().getInstanceIdCookieName(); + String instanceIdCookieName = LoadBalancerProperties.StickySession.DEFAULT_INSTANCE_ID_COOKIE_NAME; + if (properties != null) { + instanceIdCookieName = properties.getStickySession().getInstanceIdCookieName(); + } Object context = request.getContext(); if ((context instanceof RequestDataContext)) { - MultiValueMap cookies = ((RequestDataContext) context).getClientRequest().getCookies(); + RequestData clientRequest = ((RequestDataContext) context).getClientRequest(); + MultiValueMap cookies = null; + if (clientRequest != null) { + cookies = clientRequest.getCookies(); + } if (cookies == null) { return delegate.get(request); } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java index bc02b2eb1..74c78570f 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/RoundRobinLoadBalancer.java @@ -86,6 +86,7 @@ public Mono> choose(Request request) { .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); } + @SuppressWarnings("NullAway") // guarded by hasServer() private Response processInstanceResponse(ServiceInstanceListSupplier supplier, List serviceInstances) { Response serviceInstanceResponse = getInstanceResponse(serviceInstances); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java index e79e75eb6..d5fcb1142 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java @@ -21,9 +21,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; @@ -40,7 +42,7 @@ public class SameInstancePreferenceServiceInstanceListSupplier extends Delegatin private static final Log LOG = LogFactory.getLog(SameInstancePreferenceServiceInstanceListSupplier.class); - private ServiceInstance previouslyReturnedInstance; + private @Nullable ServiceInstance previouslyReturnedInstance; private boolean callGetWithRequestOnDelegates; @@ -51,13 +53,13 @@ public SameInstancePreferenceServiceInstanceListSupplier(ServiceInstanceListSupp public SameInstancePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, ReactiveLoadBalancer.Factory loadBalancerClientFactory) { super(delegate); - callGetWithRequestOnDelegates = loadBalancerClientFactory.getProperties(getServiceId()) - .isCallGetWithRequestOnDelegates(); - } - - @Override - public String getServiceId() { - return delegate.getServiceId(); + LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(getServiceId()); + if (properties != null) { + callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); + } + else { + callGetWithRequestOnDelegates = true; + } } @Override diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java index c143dc7dc..465a9ea9c 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java @@ -59,7 +59,7 @@ public final class ServiceInstanceListSupplierBuilder { private static final Log LOG = LogFactory.getLog(ServiceInstanceListSupplierBuilder.class); - private Creator baseCreator; + private @Nullable Creator baseCreator; private final List creators = new ArrayList<>(); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index 12d42b96e..e5d10a643 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -48,6 +48,9 @@ public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, P ReactiveLoadBalancer.Factory factory) { super(delegate); LoadBalancerProperties properties = factory.getProperties(getServiceId()); + if (properties == null) { + properties = new LoadBalancerProperties(); + } this.instanceId = resolveInstanceId(properties, resolver); this.size = properties.getSubset().getSize(); } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/WeightedServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/WeightedServiceInstanceListSupplier.java index 762ccbfea..6f5d16930 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/WeightedServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/WeightedServiceInstanceListSupplier.java @@ -24,6 +24,7 @@ import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; @@ -64,8 +65,13 @@ public WeightedServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, ReactiveLoadBalancer.Factory loadBalancerClientFactory) { super(delegate); this.weightFunction = weightFunction; - callGetWithRequestOnDelegates = loadBalancerClientFactory.getProperties(getServiceId()) - .isCallGetWithRequestOnDelegates(); + LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(getServiceId()); + if (properties != null) { + callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); + } + else { + callGetWithRequestOnDelegates = true; + } } @Override diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformer.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformer.java index 5eb73e869..0b8f6d9a7 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformer.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformer.java @@ -44,9 +44,12 @@ public ClientRequest transformRequest(ClientRequest request, ServiceInstance ins if (instance == null) { return request; } - LoadBalancerProperties.XForwarded xForwarded = clientFactory.getProperties(instance.getServiceId()) - .getXForwarded(); - if (xForwarded.isEnabled()) { + boolean xForwardedEnabled = false; + LoadBalancerProperties properties = clientFactory.getProperties(instance.getServiceId()); + if (properties != null) { + xForwardedEnabled = properties.getXForwarded().isEnabled(); + } + if (xForwardedEnabled) { HttpHeaders headers = request.headers(); String xForwardedHost = request.url().getHost(); String xForwardedProto = request.url().getScheme(); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java index 4f5dd8eca..735bc549d 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java @@ -20,9 +20,11 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig; @@ -39,11 +41,11 @@ */ public class ZonePreferenceServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { - private static final String ZONE = "zone"; + private static final String ZONE_KEY = "zone"; private final LoadBalancerZoneConfig zoneConfig; - private String zone; + private @Nullable String zone; private boolean callGetWithRequestOnDelegates; @@ -58,8 +60,13 @@ public ZonePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier del ReactiveLoadBalancer.Factory loadBalancerClientFactory) { super(delegate); this.zoneConfig = zoneConfig; - callGetWithRequestOnDelegates = loadBalancerClientFactory.getProperties(getServiceId()) - .isCallGetWithRequestOnDelegates(); + LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(getServiceId()); + if (properties != null) { + callGetWithRequestOnDelegates = properties.isCallGetWithRequestOnDelegates(); + } + else { + callGetWithRequestOnDelegates = true; + } } @Override @@ -87,7 +94,7 @@ private List filteredByZone(List serviceInstan filteredInstances.add(serviceInstance); } } - if (filteredInstances.size() > 0) { + if (!filteredInstances.isEmpty()) { return filteredInstances; } } @@ -96,10 +103,10 @@ private List filteredByZone(List serviceInstan return serviceInstances; } - private String getZone(ServiceInstance serviceInstance) { + private @Nullable String getZone(ServiceInstance serviceInstance) { Map metadata = serviceInstance.getMetadata(); if (metadata != null) { - return metadata.get(ZONE); + return metadata.get(ZONE_KEY); } return null; } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfigurationTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfigurationTests.java index 046528d12..36ed83615 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfigurationTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfigurationTests.java @@ -40,6 +40,7 @@ import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.WeightedServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ZonePreferenceServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.support.RetryTemplate; @@ -75,17 +76,20 @@ class LoadBalancerClientConfigurationTests { @Test void shouldInstantiateDefaultServiceInstanceListSupplierWhenConfigurationsPropertyNotSet() { - reactiveDiscoveryClientRunner.run(context -> { - ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); - then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); - then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate()) - .isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); - }); + reactiveDiscoveryClientRunner.withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice") + .run(context -> { + ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); + then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); + then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate()) + .isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); + }); } @Test void shouldInstantiateDefaultServiceInstanceListSupplier() { - reactiveDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=default") + reactiveDiscoveryClientRunner + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=default") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); @@ -96,7 +100,9 @@ void shouldInstantiateDefaultServiceInstanceListSupplier() { @Test void shouldInstantiateZonePreferenceServiceInstanceListSupplier() { - reactiveDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=zone-preference") + reactiveDiscoveryClientRunner + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=zone-preference") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(ZonePreferenceServiceInstanceListSupplier.class); @@ -111,7 +117,8 @@ void shouldInstantiateZonePreferenceServiceInstanceListSupplier() { @Test void shouldInstantiateHealthCheckServiceInstanceListSupplier() { reactiveDiscoveryClientRunner.withUserConfiguration(TestConfig.class) - .withPropertyValues("spring.cloud.loadbalancer.configurations=health-check") + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=health-check") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); @@ -123,7 +130,8 @@ void shouldInstantiateHealthCheckServiceInstanceListSupplier() { @Test void shouldInstantiateWeightedServiceInstanceListSupplier() { reactiveDiscoveryClientRunner.withUserConfiguration(TestConfig.class) - .withPropertyValues("spring.cloud.loadbalancer.configurations=weighted") + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=weighted") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(WeightedServiceInstanceListSupplier.class); @@ -138,7 +146,8 @@ void shouldInstantiateWeightedServiceInstanceListSupplier() { @Test void shouldInstantiateRequestBasedStickySessionServiceInstanceListSupplierTests() { reactiveDiscoveryClientRunner.withUserConfiguration(TestConfig.class) - .withPropertyValues("spring.cloud.loadbalancer.configurations=request-based-sticky-session") + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=request-based-sticky-session") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(RequestBasedStickySessionServiceInstanceListSupplier.class); @@ -152,17 +161,20 @@ void shouldInstantiateRequestBasedStickySessionServiceInstanceListSupplierTests( @Test void shouldInstantiateDefaultBlockingServiceInstanceListSupplierWhenConfigurationsPropertyNotSet() { - blockingDiscoveryClientRunner.run(context -> { - ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); - then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); - then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate()) - .isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); - }); + blockingDiscoveryClientRunner.withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice") + .run(context -> { + ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); + then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); + then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate()) + .isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); + }); } @Test void shouldInstantiateDefaultBlockingServiceInstanceListSupplier() { - blockingDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=default") + blockingDiscoveryClientRunner + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=default") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); @@ -173,19 +185,23 @@ void shouldInstantiateDefaultBlockingServiceInstanceListSupplier() { @Test void shouldWrapWithRetryAwareSupplierWhenRetryTemplateOnClasspath() { - blockingDiscoveryClientRunnerWithRetry.run(context -> { - ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); - then(supplier).isInstanceOf(RetryAwareServiceInstanceListSupplier.class); - then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate()) - .isInstanceOf(CachingServiceInstanceListSupplier.class); - then(((DelegatingServiceInstanceListSupplier) ((DelegatingServiceInstanceListSupplier) supplier) - .getDelegate()).getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); - }); + blockingDiscoveryClientRunnerWithRetry + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice") + .run(context -> { + ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); + then(supplier).isInstanceOf(RetryAwareServiceInstanceListSupplier.class); + then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate()) + .isInstanceOf(CachingServiceInstanceListSupplier.class); + then(((DelegatingServiceInstanceListSupplier) ((DelegatingServiceInstanceListSupplier) supplier) + .getDelegate()).getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); + }); } @Test void shouldNotWrapWithRetryAwareSupplierWhenRetryTemplateOnClasspath() { - blockingDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.retry.avoidPreviousInstance=false") + blockingDiscoveryClientRunner + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.retry.avoidPreviousInstance=false") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); @@ -199,7 +215,8 @@ void shouldNotWrapWithRetryAwareSupplierWhenRetryTemplateOnClasspath() { @MethodSource("blockingConfigurations") void shouldInstantiateBlockingHealthCheckServiceInstanceListSupplier(Class configurationClass) { blockingDiscoveryClientRunner.withUserConfiguration(configurationClass) - .withPropertyValues("spring.cloud.loadbalancer.configurations=health-check") + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=health-check") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); @@ -210,7 +227,9 @@ void shouldInstantiateBlockingHealthCheckServiceInstanceListSupplier(Class co @Test void shouldInstantiateBlockingWeightedServiceInstanceListSupplier() { - blockingDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=weighted") + blockingDiscoveryClientRunner + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice", + "spring.cloud.loadbalancer.configurations=weighted") .run(context -> { ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); then(supplier).isInstanceOf(WeightedServiceInstanceListSupplier.class); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java index 54500b1bc..03c6c1eed 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java @@ -35,18 +35,20 @@ public class ServiceInstanceListSupplierBuilderTests { @Test public void testBuilder() { - new ApplicationContextRunner().withUserConfiguration(CacheTestConfig.class).run(context -> { - ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder() - .withDiscoveryClient() - .withHealthChecks() - .withWeighted() - .build(context); - assertThat(supplier).isInstanceOf(WeightedServiceInstanceListSupplier.class); - DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; - assertThat(delegating.getDelegate()).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); - delegating = (DelegatingServiceInstanceListSupplier) delegating.getDelegate(); - assertThat(delegating.getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); - }); + new ApplicationContextRunner().withUserConfiguration(CacheTestConfig.class) + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice") + .run(context -> { + ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder() + .withDiscoveryClient() + .withHealthChecks() + .withWeighted() + .build(context); + assertThat(supplier).isInstanceOf(WeightedServiceInstanceListSupplier.class); + DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; + assertThat(delegating.getDelegate()).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); + delegating = (DelegatingServiceInstanceListSupplier) delegating.getDelegate(); + assertThat(delegating.getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); + }); } @Test @@ -65,17 +67,19 @@ public void testIllegalArgumentExceptionThrownWhenBaseBuilderNull() { @Test public void testDelegateReturnedIfLoadBalancerCacheManagerNotAvailable() { - new ApplicationContextRunner().withUserConfiguration(BaseTestConfig.class).run(context -> { - ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder() - .withDiscoveryClient() - .withHealthChecks() - .withCaching() - .build(context); - assertThat(supplier).isNotInstanceOf(CachingServiceInstanceListSupplier.class); - assertThat(supplier).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); - DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; - assertThat(delegating.getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); - }); + new ApplicationContextRunner().withUserConfiguration(BaseTestConfig.class) + .withPropertyValues(LoadBalancerClientFactory.PROPERTY_NAME + "=myservice") + .run(context -> { + ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder() + .withDiscoveryClient() + .withHealthChecks() + .withCaching() + .build(context); + assertThat(supplier).isNotInstanceOf(CachingServiceInstanceListSupplier.class); + assertThat(supplier).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); + DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; + assertThat(delegating.getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); + }); } @Import(BaseTestConfig.class)