Merge remote-tracking branch 'origin/main'

This commit is contained in:
Olga Maciaszek-Sharma
2025-03-05 14:22:10 +01:00
20 changed files with 147 additions and 67 deletions

View File

@@ -2,17 +2,12 @@ version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "3.1.x" # oldest OSS supported branch
target-branch: "4.1.x" # oldest OSS supported branch
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "4.0.x" # oldest OSS supported branch
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "4.1.x"
target-branch: "4.2.x"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
@@ -20,28 +15,6 @@ updates:
target-branch: "main"
schedule:
interval: "weekly"
- package-ecosystem: maven
directory: /
schedule:
interval: daily
target-branch: 3.1.x
ignore:
# only upgrade patch versions for maintenance branch
- dependency-name: "*"
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: maven
directory: /
schedule:
interval: daily
target-branch: 4.0.x
ignore:
# only upgrade patch versions for maintenance branch
- dependency-name: "*"
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: maven
directory: /
schedule:
@@ -53,6 +26,17 @@ updates:
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: maven
directory: /
schedule:
interval: daily
target-branch: 4.2.x
ignore:
# only upgrade patch versions for maintenance branch
- dependency-name: "*"
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: maven
directory: /
schedule:
@@ -78,3 +62,8 @@ updates:
directory: /docs
schedule:
interval: weekly
- package-ecosystem: npm
target-branch: 4.2.x
directory: /docs
schedule:
interval: weekly

View File

@@ -5,9 +5,9 @@ name: Build
on:
push:
branches: [ main, 4.1.x ]
branches: [ main, 4.2.x, 4.1.x ]
pull_request:
branches: [ main, 4.1.x ]
branches: [ main, 4.2.x, 4.1.x ]
jobs:
build:

View File

@@ -85,9 +85,9 @@ The `Customizer` interface has a single method (called `customize`) that takes t
For detailed information on how to customize a given implementation see
the following documentation:
* link:../../../../spring-cloud-circuitbreaker/current/reference/html/spring-cloud-circuitbreaker.html#configuring-resilience4j-circuit-breakers[Resilience4J]
* link:../../../../spring-cloud-circuitbreaker/reference/spring-cloud-circuitbreaker-resilience4j.html[Resilience4J]
* link:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc/circuitbreaker-sentinel.adoc#circuit-breaker-spring-cloud-circuit-breaker-with-sentinel--configuring-sentinel-circuit-breakers[Sentinel]
* link:../../../../../spring-cloud-circuitbreaker/docs/current/reference/html/spring-cloud-circuitbreaker.html#configuring-spring-retry-circuit-breakers[Spring Retry]
* link:../../../../../spring-cloud-circuitbreaker/reference/spring-cloud-circuitbreaker-spring-retry.html[Spring Retry]
Some `CircuitBreaker` implementations such as `Resilience4JCircuitBreaker` call `customize` method every time `CircuitBreaker#run` is called.
It can be inefficient. In that case, you can use `CircuitBreaker#once` method. It is useful where calling `customize` many times doesn't make sense,

View File

@@ -76,8 +76,8 @@
|spring.cloud.loadbalancer.zone | | Spring Cloud LoadBalancer zone.
|spring.cloud.refresh.additional-property-sources-to-retain | | Additional property sources to retain during a refresh. Typically only system property sources are retained. This property allows property sources, such as property sources created by EnvironmentPostProcessors to be retained as well.
|spring.cloud.refresh.enabled | `+++true+++` | Enables autoconfiguration for the refresh scope and associated features.
|spring.cloud.refresh.extra-refreshable | `+++true+++` | Additional class names for beans to post process into refresh scope.
|spring.cloud.refresh.never-refreshable | `+++true+++` | Comma separated list of class names for beans to never be refreshed or rebound.
|spring.cloud.refresh.extra-refreshable | `+++true+++` | Additional bean names or class names for beans to post process into refresh scope.
|spring.cloud.refresh.never-refreshable | `+++true+++` | Comma separated list of bean names or class names for beans to never be refreshed or rebound.
|spring.cloud.refresh.on-restart.enabled | `+++true+++` | Enable refreshing context on start.
|spring.cloud.service-registry.auto-registration.enabled | `+++true+++` | Whether service auto-registration is enabled. Defaults to true.
|spring.cloud.service-registry.auto-registration.fail-fast | `+++false+++` | Whether startup fails if there is no AutoServiceRegistration. Defaults to false.

View File

@@ -4,7 +4,7 @@
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.1",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.14"
"@springio/antora-extensions": "1.14.4",
"@springio/asciidoctor-extensions": "1.0.0-alpha.16"
}
}

View File

@@ -28,7 +28,7 @@
<properties>
<bintray.package>commons</bintray.package>
<evictor.version>1.0.0</evictor.version>
<bouncycastle-bcprov-jdk18on.version>1.78.1</bouncycastle-bcprov-jdk18on.version>
<bouncycastle-bcprov-jdk18on.version>1.80</bouncycastle-bcprov-jdk18on.version>
</properties>
<build>
<plugins>

View File

@@ -15,6 +15,7 @@
<name>spring-cloud-commons-dependencies</name>
<description>Spring Cloud Commons Dependencies</description>
<properties>
<okhttp.version>4.12.0</okhttp.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -53,6 +54,20 @@
<artifactId>spring-cloud-test-support</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot removed its dependency management of okhttp in Spring Boot 3.4.0 -->
<!-- This resulted in a different version fo okhttp in Spring Cloud 2024.0.0 -->
<!-- We will now manage the version in Spring Cloud Commons to maintain backward compatibility -->
<!-- This can be removed in the next major -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>

View File

@@ -176,6 +176,16 @@
<artifactId>httpclient5</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,6 +63,7 @@ import org.springframework.util.StringUtils;
* @author Dave Syer
* @author Venil Noronha
* @author Olga Maciaszek-Sharma
* @author Yanming Zhou
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@@ -209,6 +210,9 @@ public class RefreshAutoConfiguration {
// Already refresh scoped
return false;
}
if (this.refreshables.contains(name)) {
return true;
}
String type = definition.getBeanClassName();
if (!StringUtils.hasText(type) && registry instanceof BeanFactory) {
Class<?> cls = ((BeanFactory) registry).getType(name);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ import org.springframework.util.StringUtils;
* the <code>@ConfigurationProperties</code> bean.
*
* @author Dave Syer
* @author Yanming Zhou
* @see RefreshScope for a deeper and optionally more focused refresh of bean components.
*
*/
@@ -131,7 +132,7 @@ public class ConfigurationPropertiesRebinder
// TODO: determine a more general approach to fix this.
// see
// https://github.com/spring-cloud/spring-cloud-commons/issues/571
if (getNeverRefreshable().contains(bean.getClass().getName())) {
if (getNeverRefreshable().contains(bean.getClass().getName()) || getNeverRefreshable().contains(name)) {
return false; // ignore
}
appContext.getAutowireCapableBeanFactory().destroyBean(bean);

View File

@@ -92,9 +92,6 @@ public class ConfigDataContextRefresher extends ContextRefresher
postProcessor.postProcessEnvironment(environment, application);
}
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
MutablePropertySources target = getContext().getEnvironment().getPropertySources();
String targetName = null;
for (PropertySource<?> source : environment.getPropertySources()) {

View File

@@ -51,8 +51,6 @@ public abstract class ContextRefresher {
protected final Log logger = LogFactory.getLog(getClass());
protected static final String REFRESH_ARGS_PROPERTY_SOURCE = "refreshArgs";
protected static final String[] DEFAULT_PROPERTY_SOURCES = new String[] {
// order matters, if cli args aren't first, things get messy
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME, "defaultProperties" };

View File

@@ -41,6 +41,8 @@ import static org.springframework.cloud.util.PropertyUtils.BOOTSTRAP_ENABLED_PRO
*/
public class LegacyContextRefresher extends ContextRefresher {
private static final String REFRESH_ARGS_PROPERTY_SOURCE = "refreshArgs";
@Deprecated
public LegacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope) {
super(context, scope);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.core.env.Environment;
/**
* @author Spencer Gibb
*/
@Deprecated(since = "4.3.0", forRemoval = true)
public final class EnvironmentUtils {
private EnvironmentUtils() {

View File

@@ -39,13 +39,13 @@
{
"name": "spring.cloud.refresh.extra-refreshable",
"type": "java.util.Set<java.lang.String>",
"description": "Additional class names for beans to post process into refresh scope.",
"description": "Additional bean names or class names for beans to post process into refresh scope.",
"defaultValue": true
},
{
"name": "spring.cloud.refresh.never-refreshable",
"type": "java.lang.String",
"description": "Comma separated list of class names for beans to never be refreshed or rebound.",
"description": "Comma separated list of bean names or class names for beans to never be refreshed or rebound.",
"defaultValue": true
},
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@ import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Dave Syer
* @author Olga Maciaszek-Sharma
* @author Yanming Zhou
*/
@ExtendWith(OutputCaptureExtension.class)
class RefreshAutoConfigurationTests {
@@ -79,7 +80,7 @@ class RefreshAutoConfigurationTests {
}
@Test
public void extraRefreshables() {
public void extraRefreshableWithClassName() {
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class,
"sealedconfig.foo=bar",
"spring.cloud.refresh.extra-refreshable:" + SealedConfigProps.class.getName())) {
@@ -89,7 +90,17 @@ class RefreshAutoConfigurationTests {
}
@Test
void neverRefreshable() {
public void extraRefreshableWithBeanName() {
String beanName = "sealedconfig-" + SealedConfigProps.class.getName();
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class,
"sealedconfig.foo=bar", "spring.cloud.refresh.extra-refreshable:" + beanName)) {
context.getBean(SealedConfigProps.class);
context.getBean(ContextRefresher.class).refresh();
}
}
@Test
void neverRefreshableWithClassName() {
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class,
"countingconfig.foo=bar",
"spring.cloud.refresh.never-refreshable:" + CountingConfigProps.class.getName())) {
@@ -99,6 +110,17 @@ class RefreshAutoConfigurationTests {
}
}
@Test
void neverRefreshableWithBeanName() {
String beanName = "countingconfig-" + CountingConfigProps.class.getName();
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class,
"countingconfig.foo=bar", "spring.cloud.refresh.never-refreshable:" + beanName)) {
CountingConfigProps configProps = context.getBean(CountingConfigProps.class);
context.getBean(ContextRefresher.class).refresh();
assertThat(configProps.count).as("config props was rebound when it should not have been").hasValue(1);
}
}
@Test
void refreshScopeLifecylePresentByDefault() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,10 @@ import io.micrometer.core.instrument.Tags;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
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.Response;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.util.StringUtils;
@@ -55,7 +57,11 @@ class LoadBalancerTags {
}
Iterable<Tag> buildSuccessRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
Response<ServiceInstance> lbResponse = completionContext.getLoadBalancerResponse();
if (lbResponse == null) {
return Tags.empty();
}
ServiceInstance serviceInstance = lbResponse.getServer();
Tags tags = Tags.of(buildServiceInstanceTags(serviceInstance));
Object clientResponse = completionContext.getClientResponse();
if (clientResponse instanceof ResponseData responseData) {
@@ -100,9 +106,9 @@ class LoadBalancerTags {
}
Iterable<Tag> buildDiscardedRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
if (completionContext.getLoadBalancerRequest().getContext() instanceof RequestDataContext) {
RequestData requestData = ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext())
.getClientRequest();
Request<Object> lbRequest = completionContext.getLoadBalancerRequest();
if (lbRequest != null && lbRequest.getContext() instanceof RequestDataContext requestDataContext) {
RequestData requestData = requestDataContext.getClientRequest();
if (requestData != null) {
return Tags.of(valueOrUnknown("method", requestData.getHttpMethod()),
valueOrUnknown("uri", getPath(requestData)), valueOrUnknown("serviceId", getHost(requestData)));
@@ -118,11 +124,15 @@ class LoadBalancerTags {
}
Iterable<Tag> buildFailedRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
Response<ServiceInstance> lbResponse = completionContext.getLoadBalancerResponse();
if (lbResponse == null) {
return Tags.empty();
}
ServiceInstance serviceInstance = lbResponse.getServer();
Tags tags = Tags.of(buildServiceInstanceTags(serviceInstance)).and(exception(completionContext.getThrowable()));
if (completionContext.getLoadBalancerRequest().getContext() instanceof RequestDataContext) {
RequestData requestData = ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext())
.getClientRequest();
Request<Object> lbRequest = completionContext.getLoadBalancerRequest();
if (lbRequest != null && lbRequest.getContext() instanceof RequestDataContext requestDataContext) {
RequestData requestData = requestDataContext.getClientRequest();
if (requestData != null) {
return tags.and(Tags.of(valueOrUnknown("method", requestData.getHttpMethod()),
valueOrUnknown("uri", getPath(requestData))));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,10 +85,10 @@ public class MicrometerStatsLoadBalancerLifecycle implements LoadBalancerLifecyc
@Override
public void onStartRequest(Request<Object> request, Response<ServiceInstance> lbResponse) {
if (request.getContext() instanceof TimedRequestContext) {
if (request != null && request.getContext() instanceof TimedRequestContext) {
((TimedRequestContext) request.getContext()).setRequestStartTime(System.nanoTime());
}
if (!lbResponse.hasServer()) {
if (lbResponse == null || !lbResponse.hasServer()) {
return;
}
ServiceInstance serviceInstance = lbResponse.getServer();
@@ -104,7 +104,11 @@ public class MicrometerStatsLoadBalancerLifecycle implements LoadBalancerLifecyc
@Override
public void onComplete(CompletionContext<Object, ServiceInstance, Object> completionContext) {
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
ServiceInstance serviceInstance = null;
Response<ServiceInstance> loadBalancerResponse = completionContext.getLoadBalancerResponse();
if (loadBalancerResponse != null) {
serviceInstance = loadBalancerResponse.getServer();
}
LoadBalancerProperties properties = serviceInstance != null
? loadBalancerFactory.getProperties(serviceInstance.getServiceId())
: loadBalancerFactory.getProperties(null);
@@ -121,7 +125,11 @@ public class MicrometerStatsLoadBalancerLifecycle implements LoadBalancerLifecyc
if (activeRequestsCounter != null) {
activeRequestsCounter.decrementAndGet();
}
Object loadBalancerRequestContext = completionContext.getLoadBalancerRequest().getContext();
Request<Object> lbRequest = completionContext.getLoadBalancerRequest();
if (lbRequest == null) {
return;
}
Object loadBalancerRequestContext = lbRequest.getContext();
if (requestHasBeenTimed(loadBalancerRequestContext)) {
if (CompletionContext.Status.FAILED.equals(completionContext.status())) {
Timer.builder("loadbalancer.requests.failed")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMapAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.UNKNOWN;
@@ -243,6 +244,26 @@ class MicrometerStatsLoadBalancerLifecycleTests {
Tag.of("serviceInstance.port", "0"), Tag.of("status", "200"), Tag.of("uri", UNKNOWN));
}
@Test
void shouldHandleNullLoadBalancerResponse() {
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
new HttpHeaders(), new HashMap<>());
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
assertThatCode(() -> {
statsLifecycle.onStartRequest(lbRequest, null);
statsLifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, null));
}).doesNotThrowAnyException();
}
@Test
void shouldHandleNullLoadBalancerRequest() {
Response<ServiceInstance> lbResponse = new EmptyResponse();
assertThatCode(() -> {
statsLifecycle.onStartRequest(null, lbResponse);
statsLifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, null, lbResponse));
}).doesNotThrowAnyException();
}
private static class StatsTestContext {
}

View File

@@ -21,6 +21,8 @@
<suppress files=".*Tests.*" checks="MethodParamPad"/>
<suppress files=".*AutoConfiguration.*" checks="HideUtilityClassConstructor"/>
<suppress files=".*AutoConfiguration.*" checks="FinalClass"/>
<suppress files=".*ApplicationListener.*" checks="FinalClass"/>
<suppress files=".*Tests.*" checks="FinalClass"/>
<suppress files=".*ReactiveDiscoveryClient.*" checks="JavadocVariable"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/](java|java21)[\\/]" checks="RegexpSinglelineJava" id="toLowerCaseWithoutLocale"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/](java|java21)[\\/]" checks="RegexpSinglelineJava" id="toUpperCaseWithoutLocale"/>