Skip to content

Commit 91bbde0

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 734e4bb + c9a92bf commit 91bbde0

File tree

8 files changed

+335
-39
lines changed

8 files changed

+335
-39
lines changed

docs/modules/ROOT/pages/spring-cloud-commons/common-abstractions.adoc

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,89 @@ IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestTemplat
218218

219219
TIP: If you see errors such as `java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89`, try injecting `RestOperations` or setting `spring.aop.proxyTargetClass=true`.
220220

221+
[[rest-template-builder-loadbalancer-client]]
222+
== Using `@LoadBalanced RestTemplateBuilder` to create a LoadBalancer Client
223+
224+
You can also configure a `RestTemplate` to use a Load-Balancer client by annotating a
225+
`RestTemplateBuilder` bean with `@LoadBalanced`:
226+
227+
[source,java,indent=0]
228+
----
229+
import org.springframework.boot.web.client.RestTemplateBuilder;@Configuration
230+
public class MyConfiguration {
231+
232+
@Bean
233+
@LoadBalanced
234+
RestTemplateBuilder loadBalancedRestTemplateBuilder() {
235+
return new RestTemplateBuilder();
236+
}
237+
}
238+
239+
public class MyClass {
240+
241+
private final RestTemplate restTemplate;
242+
243+
MyClass(@LoadBalanced RestTemplateBuilder restTemplateBuilder) {
244+
this.restTemplate = restTemplateBuilder.build();
245+
}
246+
247+
public String getStores() {
248+
return restTemplate.getForObject("http://stores/stores", String.class);
249+
}
250+
}
251+
----
252+
253+
The URI needs to use a virtual host name (that is, a service name, not a host name).
254+
The `BlockingLoadBalancerClient` is used to create a full physical address.
255+
256+
IMPORTANT: To use it, add xref:spring-cloud-commons/loadbalancer.adoc#spring-cloud-loadbalancer-starter[Spring Cloud LoadBalancer starter] to your project.
257+
258+
[[multiple-resttemplate-builder-beans]]
259+
=== Multiple `RestTemplateBuilder` beans
260+
261+
If you want a `RestTemplateBuilder` that is not load-balanced, create a `RestTemplateBuilder` bean and inject it.
262+
To access the load-balanced `RestTemplateBuilder`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
263+
264+
[source,java,indent=0]
265+
----
266+
@Configuration
267+
public class MyConfiguration {
268+
269+
@LoadBalanced
270+
@Bean
271+
RestTemplateBuilder loadBalancedRestTemplateBuilder() {
272+
return new RestTemplateBuilder();
273+
}
274+
275+
@Primary
276+
@Bean
277+
RestTemplateBuilder restTemplateBuilder() {
278+
return new RestTemplateBuilder();
279+
}
280+
}
281+
282+
public class MyClass {
283+
284+
@Autowired
285+
private RestTemplateBuilder restTemplateBuilder;
286+
287+
@Autowired
288+
@LoadBalanced
289+
private RestTemplateBuilder loadBalanced;
290+
291+
public String doOtherStuff() {
292+
return loadBalanced.getForObject("http://stores/stores", String.class);
293+
}
294+
295+
public String doStuff() {
296+
return restTemplateBuilder.build().getForObject("http://example.com", String.class);
297+
}
298+
}
299+
----
300+
301+
IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestTemplateBuilder` declaration in the preceding example to disambiguate the unqualified `@Autowired` injection.
302+
303+
221304
[[rest-client-loadbalancer-client]]
222305
== Spring RestClient as a LoadBalancer Client
223306

@@ -256,7 +339,7 @@ IMPORTANT: To use it, add xref:spring-cloud-commons/loadbalancer.adoc#spring-clo
256339
=== Multiple `RestClient.Builder` Objects
257340

258341
If you want a `RestClient.Builder` that is not load-balanced, create a `RestClient.Builder` bean and inject it.
259-
To access the load-balanced `RestClient`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
342+
To access the load-balanced `RestClient.Builder`, use the `@LoadBalanced` qualifier when you create your `@Bean`, as the following example shows:
260343

261344
[source,java,indent=0]
262345
----
@@ -296,7 +379,7 @@ public class MyClass {
296379
}
297380
----
298381

299-
IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestTemplate` declaration in the preceding example to disambiguate the unqualified `@Autowired` injection.
382+
IMPORTANT: Notice the use of the `@Primary` annotation on the plain `RestClient.Builder` declaration in the preceding example to disambiguate the unqualified `@Autowired` injection.
300383

301384
[[webclinet-loadbalancer-client]]
302385
== Spring WebClient as a LoadBalancer Client

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"dependencies": {
33
"antora": "3.2.0-alpha.6",
44
"@antora/atlas-extension": "1.0.0-alpha.2",
5-
"@antora/collector-extension": "1.0.0-beta.2",
5+
"@antora/collector-extension": "1.0.0-beta.3",
66
"@asciidoctor/tabs": "1.0.0-beta.6",
77
"@springio/antora-extensions": "1.14.2",
88
"@springio/asciidoctor-extensions": "1.0.0-alpha.14"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.client.loadbalancer;
18+
19+
import org.springframework.beans.BeansException;
20+
import org.springframework.beans.factory.ObjectProvider;
21+
import org.springframework.beans.factory.config.BeanPostProcessor;
22+
import org.springframework.context.ApplicationContext;
23+
import org.springframework.http.client.ClientHttpRequestInterceptor;
24+
25+
/**
26+
* A {@link BeanPostProcessor} that adds the provided {@link ClientHttpRequestInterceptor}
27+
* to bean instances annotated with {@link LoadBalanced}.
28+
*
29+
* @author Olga Maciaszek-Sharma
30+
* @since 4.2.0
31+
*/
32+
public abstract class AbstractLoadBalancerBlockingBuilderBeanPostProcessor<T extends ClientHttpRequestInterceptor>
33+
implements BeanPostProcessor {
34+
35+
protected final ObjectProvider<T> loadBalancerInterceptorProvider;
36+
37+
protected final ApplicationContext context;
38+
39+
AbstractLoadBalancerBlockingBuilderBeanPostProcessor(ObjectProvider<T> loadBalancerInterceptorProvider,
40+
ApplicationContext context) {
41+
this.loadBalancerInterceptorProvider = loadBalancerInterceptorProvider;
42+
this.context = context;
43+
}
44+
45+
@Override
46+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
47+
// a separate call to verify supported type before searching for annotation for
48+
// performance reasons
49+
if (isSupported(bean)) {
50+
if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
51+
return bean;
52+
}
53+
ClientHttpRequestInterceptor interceptor = loadBalancerInterceptorProvider.getIfAvailable();
54+
if (interceptor == null) {
55+
throw new IllegalStateException(ClientHttpRequestInterceptor.class.getSimpleName() + " not available.");
56+
}
57+
bean = apply(bean, interceptor);
58+
}
59+
return bean;
60+
}
61+
62+
protected abstract Object apply(Object bean, ClientHttpRequestInterceptor interceptor);
63+
64+
protected abstract boolean isSupported(Object bean);
65+
66+
}

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ static LoadBalancerRestClientBuilderBeanPostProcessor<DeferringLoadBalancerInter
101101
return new LoadBalancerRestClientBuilderBeanPostProcessor<>(loadBalancerInterceptorProvider, context);
102102
}
103103

104+
@Bean
105+
@ConditionalOnBean(DeferringLoadBalancerInterceptor.class)
106+
@ConditionalOnMissingBean(LoadBalancerRestTemplateBuilderBeanPostProcessor.class)
107+
static LoadBalancerRestTemplateBuilderBeanPostProcessor<DeferringLoadBalancerInterceptor> lbRestTemplateBuilderPostProcessor(
108+
ObjectProvider<DeferringLoadBalancerInterceptor> loadBalancerInterceptorProvider,
109+
ApplicationContext context) {
110+
return new LoadBalancerRestTemplateBuilderBeanPostProcessor<>(loadBalancerInterceptorProvider, context);
111+
}
112+
104113
}
105114

106115
@AutoConfiguration

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientBuilderBeanPostProcessor.java

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,22 @@
1616

1717
package org.springframework.cloud.client.loadbalancer;
1818

19-
import org.springframework.beans.BeansException;
2019
import org.springframework.beans.factory.ObjectProvider;
21-
import org.springframework.beans.factory.config.BeanPostProcessor;
2220
import org.springframework.context.ApplicationContext;
2321
import org.springframework.http.client.ClientHttpRequestInterceptor;
2422
import org.springframework.web.client.RestClient;
2523

2624
/**
27-
* A {@link BeanPostProcessor} that adds the provided {@link ClientHttpRequestInterceptor}
28-
* to all {@link RestClient.Builder} instances annotated with {@link LoadBalanced}.
25+
* {@link RestClient.Builder}-specific
26+
* {@link AbstractLoadBalancerBlockingBuilderBeanPostProcessor} implementation. Adds the
27+
* provided {@link ClientHttpRequestInterceptor} to all {@link RestClient.Builder}
28+
* instances annotated with {@link LoadBalanced}.
2929
*
3030
* @author Olga Maciaszek-Sharma
3131
* @since 4.1.0
3232
*/
3333
public class LoadBalancerRestClientBuilderBeanPostProcessor<T extends ClientHttpRequestInterceptor>
34-
implements BeanPostProcessor {
35-
36-
private final ObjectProvider<T> loadBalancerInterceptorProvider;
37-
38-
private final ApplicationContext context;
34+
extends AbstractLoadBalancerBlockingBuilderBeanPostProcessor<T> {
3935

4036
/**
4137
* Creates a {@link LoadBalancerRestClientBuilderBeanPostProcessor} instance using a
@@ -48,8 +44,7 @@ public class LoadBalancerRestClientBuilderBeanPostProcessor<T extends ClientHttp
4844
*/
4945
@Deprecated(forRemoval = true)
5046
public LoadBalancerRestClientBuilderBeanPostProcessor(T loadBalancerInterceptor, ApplicationContext context) {
51-
this.loadBalancerInterceptorProvider = new SimpleObjectProvider<>(loadBalancerInterceptor);
52-
this.context = context;
47+
this(new SimpleObjectProvider<>(loadBalancerInterceptor), context);
5348
}
5449

5550
/**
@@ -61,23 +56,17 @@ public LoadBalancerRestClientBuilderBeanPostProcessor(T loadBalancerInterceptor,
6156
*/
6257
public LoadBalancerRestClientBuilderBeanPostProcessor(ObjectProvider<T> loadBalancerInterceptorProvider,
6358
ApplicationContext context) {
64-
this.loadBalancerInterceptorProvider = loadBalancerInterceptorProvider;
65-
this.context = context;
59+
super(loadBalancerInterceptorProvider, context);
60+
}
61+
62+
@Override
63+
protected Object apply(Object bean, ClientHttpRequestInterceptor interceptor) {
64+
return ((RestClient.Builder) bean).requestInterceptor(interceptor);
6665
}
6766

6867
@Override
69-
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
70-
if (bean instanceof RestClient.Builder) {
71-
if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
72-
return bean;
73-
}
74-
ClientHttpRequestInterceptor interceptor = loadBalancerInterceptorProvider.getIfAvailable();
75-
if (interceptor == null) {
76-
throw new IllegalStateException(ClientHttpRequestInterceptor.class.getSimpleName() + " not available.");
77-
}
78-
((RestClient.Builder) bean).requestInterceptor(interceptor);
79-
}
80-
return bean;
68+
protected boolean isSupported(Object bean) {
69+
return bean instanceof RestClient.Builder;
8170
}
8271

8372
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.client.loadbalancer;
18+
19+
import org.springframework.beans.factory.ObjectProvider;
20+
import org.springframework.boot.web.client.RestTemplateBuilder;
21+
import org.springframework.context.ApplicationContext;
22+
import org.springframework.http.client.ClientHttpRequestInterceptor;
23+
24+
/**
25+
* {@link RestTemplateBuilder}-specific
26+
* {@link AbstractLoadBalancerBlockingBuilderBeanPostProcessor} implementation. Adds the
27+
* provided {@link ClientHttpRequestInterceptor} to all {@link RestTemplateBuilder}
28+
* instances annotated with {@link LoadBalanced}.
29+
*
30+
* @author Olga Maciaszek-Sharma
31+
* @since 4.2.0
32+
*/
33+
public class LoadBalancerRestTemplateBuilderBeanPostProcessor<T extends ClientHttpRequestInterceptor>
34+
extends AbstractLoadBalancerBlockingBuilderBeanPostProcessor<T> {
35+
36+
public LoadBalancerRestTemplateBuilderBeanPostProcessor(ObjectProvider<T> loadBalancerInterceptorProvider,
37+
ApplicationContext context) {
38+
super(loadBalancerInterceptorProvider, context);
39+
}
40+
41+
@Override
42+
protected boolean isSupported(Object bean) {
43+
return bean instanceof RestTemplateBuilder;
44+
}
45+
46+
@Override
47+
protected Object apply(Object bean, ClientHttpRequestInterceptor interceptor) {
48+
return ((RestTemplateBuilder) bean).interceptors(interceptor);
49+
}
50+
51+
}

spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRestClientIntegrationTests.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.cloud.client.loadbalancer;
1818

19-
import java.io.IOException;
2019
import java.net.URI;
2120

2221
import org.junit.jupiter.api.Test;
@@ -25,7 +24,6 @@
2524
import org.springframework.boot.SpringBootConfiguration;
2625
import org.springframework.boot.test.context.SpringBootTest;
2726
import org.springframework.cloud.client.ServiceInstance;
28-
import org.springframework.context.ApplicationContext;
2927
import org.springframework.context.annotation.Bean;
3028
import org.springframework.web.client.RestClient;
3129

@@ -41,17 +39,16 @@
4139
properties = "spring.cloud.loadbalancer.retry.enabled=false")
4240
public class LoadBalancedRestClientIntegrationTests {
4341

44-
private final RestClient client;
42+
private final RestClient.Builder restClientBuilder;
4543

46-
@Autowired
47-
ApplicationContext context;
48-
49-
public LoadBalancedRestClientIntegrationTests(@Autowired RestClient.Builder clientBuilder) {
50-
this.client = clientBuilder.build();
44+
public LoadBalancedRestClientIntegrationTests(@Autowired RestClient.Builder restClientBuilder) {
45+
this.restClientBuilder = restClientBuilder;
5146
}
5247

5348
@Test
5449
void shouldBuildLoadBalancedRestClientInConstructor() {
50+
RestClient client = restClientBuilder.build();
51+
5552
// Interceptors are not visible in RestClient
5653
assertThatThrownBy(() -> client.get().uri("http://test-service").retrieve())
5754
.hasMessage("LoadBalancerInterceptor invoked.");
@@ -70,13 +67,13 @@ RestClient.Builder restClientBuilder() {
7067
LoadBalancerClient testLoadBalancerClient() {
7168
return new LoadBalancerClient() {
7269
@Override
73-
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
70+
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
7471
throw new UnsupportedOperationException("LoadBalancerInterceptor invoked.");
7572
}
7673

7774
@Override
78-
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)
79-
throws IOException {
75+
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
76+
LoadBalancerRequest<T> request) {
8077
throw new UnsupportedOperationException("LoadBalancerInterceptor invoked.");
8178
}
8279

0 commit comments

Comments
 (0)