Skip to content

Commit e15c150

Browse files
committed
Only copy UriBuilderFactory when customized
This commit ensures that, when creating a RestClient.Builder from a RestTemplate, the UriBuilderFactory is only copied if it has been changed from the default values. Before this commit, the UriBuilderFactory was effectively alway copied, resulting in not being able to use a baseUrl. This commit also introduces a small memory optimization in DefaultUriBuilderFactory, so that default environment variables are created lazily. Closes gh-32180
1 parent 7025b7a commit e15c150

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed

Diff for: spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.springframework.util.CollectionUtils;
5555
import org.springframework.web.util.DefaultUriBuilderFactory;
5656
import org.springframework.web.util.UriBuilderFactory;
57+
import org.springframework.web.util.UriTemplateHandler;
5758

5859
/**
5960
* Default implementation of {@link RestClient.Builder}.
@@ -172,9 +173,7 @@ public DefaultRestClientBuilder(DefaultRestClientBuilder other) {
172173
public DefaultRestClientBuilder(RestTemplate restTemplate) {
173174
Assert.notNull(restTemplate, "RestTemplate must not be null");
174175

175-
if (restTemplate.getUriTemplateHandler() instanceof UriBuilderFactory builderFactory) {
176-
this.uriBuilderFactory = builderFactory;
177-
}
176+
this.uriBuilderFactory = getUriBuilderFactory(restTemplate);
178177
this.statusHandlers = new ArrayList<>();
179178
this.statusHandlers.add(StatusHandler.fromErrorHandler(restTemplate.getErrorHandler()));
180179

@@ -191,6 +190,26 @@ public DefaultRestClientBuilder(RestTemplate restTemplate) {
191190
this.observationConvention = restTemplate.getObservationConvention();
192191
}
193192

193+
@Nullable
194+
private static UriBuilderFactory getUriBuilderFactory(RestTemplate restTemplate) {
195+
UriTemplateHandler uriTemplateHandler = restTemplate.getUriTemplateHandler();
196+
if (uriTemplateHandler instanceof DefaultUriBuilderFactory builderFactory) {
197+
// only reuse the DefaultUriBuilderFactory if it has been customized
198+
if (builderFactory.hasRestTemplateDefaults()) {
199+
return null;
200+
}
201+
else {
202+
return builderFactory;
203+
}
204+
}
205+
else if (uriTemplateHandler instanceof UriBuilderFactory builderFactory) {
206+
return builderFactory;
207+
}
208+
else {
209+
return null;
210+
}
211+
}
212+
194213
private static ClientHttpRequestFactory getRequestFactory(RestTemplate restTemplate) {
195214
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
196215
if (requestFactory instanceof InterceptingClientHttpRequestFactory interceptingClientHttpRequestFactory) {

Diff for: spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java

+38-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import java.util.Optional;
2525

2626
import org.springframework.lang.Nullable;
27+
import org.springframework.util.CollectionUtils;
2728
import org.springframework.util.MultiValueMap;
2829
import org.springframework.util.ObjectUtils;
2930
import org.springframework.util.StringUtils;
@@ -46,7 +47,8 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory {
4647

4748
private EncodingMode encodingMode = EncodingMode.TEMPLATE_AND_VALUES;
4849

49-
private final Map<String, Object> defaultUriVariables = new HashMap<>();
50+
@Nullable
51+
private Map<String, Object> defaultUriVariables;
5052

5153
private boolean parsePath = true;
5254

@@ -108,17 +110,31 @@ public EncodingMode getEncodingMode() {
108110
* @param defaultUriVariables default URI variable values
109111
*/
110112
public void setDefaultUriVariables(@Nullable Map<String, ?> defaultUriVariables) {
111-
this.defaultUriVariables.clear();
112113
if (defaultUriVariables != null) {
113-
this.defaultUriVariables.putAll(defaultUriVariables);
114+
if (this.defaultUriVariables == null) {
115+
this.defaultUriVariables = new HashMap<>(defaultUriVariables);
116+
}
117+
else {
118+
this.defaultUriVariables.putAll(defaultUriVariables);
119+
}
120+
}
121+
else {
122+
if (this.defaultUriVariables != null) {
123+
this.defaultUriVariables.clear();
124+
}
114125
}
115126
}
116127

117128
/**
118129
* Return the configured default URI variable values.
119130
*/
120131
public Map<String, ?> getDefaultUriVariables() {
121-
return Collections.unmodifiableMap(this.defaultUriVariables);
132+
if (this.defaultUriVariables != null) {
133+
return Collections.unmodifiableMap(this.defaultUriVariables);
134+
}
135+
else {
136+
return Collections.emptyMap();
137+
}
122138
}
123139

124140
/**
@@ -141,6 +157,20 @@ public boolean shouldParsePath() {
141157
return this.parsePath;
142158
}
143159

160+
/**
161+
* Indicates whether this {@code DefaultUriBuilderFactory} uses the default
162+
* {@link org.springframework.web.client.RestTemplate RestTemplate}
163+
* settings.
164+
* @since 6.1.4
165+
*/
166+
public boolean hasRestTemplateDefaults() {
167+
// see RestTemplate::initUriTemplateHandler
168+
return this.baseUri == null &&
169+
this.encodingMode == EncodingMode.URI_COMPONENT &&
170+
CollectionUtils.isEmpty(this.defaultUriVariables) &&
171+
this.parsePath;
172+
}
173+
144174

145175
// UriTemplateHandler
146176

@@ -379,8 +409,8 @@ public DefaultUriBuilder fragment(@Nullable String fragment) {
379409

380410
@Override
381411
public URI build(Map<String, ?> uriVars) {
382-
if (!defaultUriVariables.isEmpty()) {
383-
Map<String, Object> map = new HashMap<>();
412+
if (!CollectionUtils.isEmpty(defaultUriVariables)) {
413+
Map<String, Object> map = new HashMap<>(defaultUriVariables.size() + uriVars.size());
384414
map.putAll(defaultUriVariables);
385415
map.putAll(uriVars);
386416
uriVars = map;
@@ -394,7 +424,7 @@ public URI build(Map<String, ?> uriVars) {
394424

395425
@Override
396426
public URI build(Object... uriVars) {
397-
if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) {
427+
if (ObjectUtils.isEmpty(uriVars) && !CollectionUtils.isEmpty(defaultUriVariables)) {
398428
return build(Collections.emptyMap());
399429
}
400430
if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {

Diff for: spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java

+25-7
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
2828
import org.springframework.http.converter.HttpMessageConverter;
2929
import org.springframework.http.converter.StringHttpMessageConverter;
30+
import org.springframework.lang.Nullable;
3031
import org.springframework.web.util.DefaultUriBuilderFactory;
3132

3233
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.assertj.core.api.Assertions.fail;
3335

3436

3537
/**
@@ -39,9 +41,9 @@ public class RestClientBuilderTests {
3941

4042
@SuppressWarnings("unchecked")
4143
@Test
42-
void createFromRestTemplate() throws NoSuchFieldException, IllegalAccessException {
44+
void createFromRestTemplate() {
4345
JettyClientHttpRequestFactory requestFactory = new JettyClientHttpRequestFactory();
44-
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
46+
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("baseUri");
4547
ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
4648
List<HttpMessageConverter<?>> restTemplateMessageConverters = List.of(new StringHttpMessageConverter());
4749
ClientHttpRequestInterceptor interceptor = new BasicAuthenticationInterceptor("foo", "bar");
@@ -77,12 +79,28 @@ void createFromRestTemplate() throws NoSuchFieldException, IllegalAccessExceptio
7779
assertThat(initializers).containsExactly(initializer);
7880
}
7981

80-
private static Object fieldValue(String name, DefaultRestClientBuilder instance)
81-
throws NoSuchFieldException, IllegalAccessException {
82+
@Test
83+
void defaultUriBuilderFactory() {
84+
RestTemplate restTemplate = new RestTemplate();
85+
86+
RestClient.Builder builder = RestClient.builder(restTemplate);
87+
assertThat(builder).isInstanceOf(DefaultRestClientBuilder.class);
88+
DefaultRestClientBuilder defaultBuilder = (DefaultRestClientBuilder) builder;
8289

83-
Field field = DefaultRestClientBuilder.class.getDeclaredField(name);
84-
field.setAccessible(true);
90+
assertThat(fieldValue("uriBuilderFactory", defaultBuilder)).isNull();
91+
}
8592

86-
return field.get(instance);
93+
@Nullable
94+
private static Object fieldValue(String name, DefaultRestClientBuilder instance) {
95+
try {
96+
Field field = DefaultRestClientBuilder.class.getDeclaredField(name);
97+
field.setAccessible(true);
98+
99+
return field.get(instance);
100+
}
101+
catch (NoSuchFieldException | IllegalAccessException ex) {
102+
fail(ex.getMessage(), ex);
103+
return null;
104+
}
87105
}
88106
}

0 commit comments

Comments
 (0)