diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java
index c8ae65a0ace5..4637a66d873c 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-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.
@@ -24,6 +24,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -42,6 +44,7 @@
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Rossen Stoyanchev
+ * @author Yanming Zhou
* @since 3.1
* @see UriComponentsBuilder
*/
@@ -144,7 +147,9 @@ public final UriComponents encode() {
/**
* Replace all URI template variables with the values from a given map.
*
The given map keys represent variable names; the corresponding values
- * represent variable values. The order of variables is not significant.
+ * represent variable values, if the variable value is {@link Supplier} or
+ * {@link Function}, then the value to use is applied base on the variable name.
+ * The order of variables is not significant.
* @param uriVariables the map of URI variables
* @return the expanded URI components
*/
@@ -233,6 +238,7 @@ public final String toString() {
return expandUriComponent(source, uriVariables, null);
}
+ @SuppressWarnings({"unchecked", "rawtypes"})
static @Nullable String expandUriComponent(@Nullable String source, UriTemplateVariables uriVariables,
@Nullable UnaryOperator encoder) {
@@ -254,6 +260,12 @@ public final String toString() {
if (UriTemplateVariables.SKIP_VALUE.equals(varValue)) {
continue;
}
+ if (varValue instanceof Supplier supplier) {
+ varValue = supplier.get();
+ }
+ else if (varValue instanceof Function function) {
+ varValue = function.apply(varName);
+ }
String formatted = getVariableValueAsString(varValue);
formatted = encoder != null ? encoder.apply(formatted) : Matcher.quoteReplacement(formatted);
matcher.appendReplacement(sb, formatted);
diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java
index c06a22736fcd..0ba37ddda3a1 100644
--- a/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java
+++ b/spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-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.
@@ -20,8 +20,10 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.jspecify.annotations.Nullable;
@@ -44,6 +46,7 @@
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @author Nicklas Wiegandt
+ * @author Yanming Zhou
*/
public class RestClientBuilderTests {
@@ -110,6 +113,23 @@ void defaultUri() {
assertThat(fieldValue("baseUrl", defaultBuilder)).isEqualTo(baseUrl.toString());
}
+ @Test
+ void baseUriWithDynamicDefaultUriVariables() {
+ String key = "partition";
+ String baseUrl = "http://{" + key + "}.example.com";
+ Map holder = new HashMap<>();
+ RestClient restClient = RestClient.builder().baseUrl(baseUrl)
+ .defaultUriVariables(Map.of(key, (Function) holder::get)).build();
+
+ holder.put(key, "p0");
+ assertThat(fieldValue("uri", restClient.get().uri("/{foo}.html", Map.of("foo", "index")))
+ .toString()).isEqualTo("http://p0.example.com/index.html");
+
+ holder.put(key, "p1");
+ assertThat(fieldValue("uri", restClient.get().uri("/{bar}.html", Map.of("bar", "index")))
+ .toString()).isEqualTo("http://p1.example.com/index.html");
+ }
+
@Test
void messageConvertersList() {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
@@ -281,4 +301,17 @@ void buildCopiesDefaultCookiesImmutable() {
return null;
}
}
+
+ private static @Nullable Object fieldValue(String name, Object instance) {
+ try {
+ Field field = instance.getClass().getDeclaredField(name);
+ field.setAccessible(true);
+
+ return field.get(instance);
+ }
+ catch (NoSuchFieldException | IllegalAccessException ex) {
+ fail(ex.getMessage(), ex);
+ return null;
+ }
+ }
}
diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java
index 33fcbbe27a4e..47c14ac539cf 100644
--- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-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.
@@ -23,6 +23,9 @@
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -42,6 +45,7 @@
* @author Arjen Poutsma
* @author Phillip Webb
* @author Rossen Stoyanchev
+ * @author Yanming Zhou
*/
class UriComponentsTests {
@@ -266,4 +270,24 @@ void equalsOpaqueUriComponents(ParserType parserType) {
assertThat(uric1).isNotEqualTo(uric3);
}
+ @Test
+ void expandSupplier() {
+ String key = "partition";
+ Map holder = Map.of(key, "p0");
+ UriComponents uri = UriComponentsBuilder.fromUriString("http://{" + key + "}.example.com")
+ .uriVariables(Map.of(key, (Supplier) () -> holder.get(key))).build();
+
+ assertThat(uri.toString()).isEqualTo("http://p0.example.com");
+ }
+
+ @Test
+ void expandFunction() {
+ String key = "partition";
+ Map holder = Map.of(key, "p0");
+ UriComponents uri = UriComponentsBuilder.fromUriString("http://{" + key + "}.example.com")
+ .uriVariables(Map.of(key, (Function) holder::get)).build();
+
+ assertThat(uri.toString()).isEqualTo("http://p0.example.com");
+ }
+
}