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"); + } + }