Skip to content

Commit 05d2f3c

Browse files
committed
Merge pull request #32528 from marcingrzejszczak
* gh-32528: Polish "Break cycles between Zipkin senders and HTTP client observation" Break cycles between Zipkin senders and HTTP client observation Closes gh-32528
2 parents 13c638b + 574242b commit 05d2f3c

File tree

5 files changed

+172
-20
lines changed

5 files changed

+172
-20
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java

+11-9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import zipkin2.reporter.brave.ZipkinSpanHandler;
2626
import zipkin2.reporter.urlconnection.URLConnectionSender;
2727

28+
import org.springframework.beans.factory.ObjectProvider;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2930
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -73,12 +74,12 @@ static class RestTemplateSenderConfiguration {
7374

7475
@Bean
7576
@ConditionalOnMissingBean(Sender.class)
76-
@ConditionalOnBean(RestTemplateBuilder.class)
7777
ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties,
78-
RestTemplateBuilder restTemplateBuilder) {
79-
RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(properties.getConnectTimeout())
80-
.setReadTimeout(properties.getReadTimeout()).build();
81-
return new ZipkinRestTemplateSender(properties.getEndpoint(), restTemplate);
78+
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers) {
79+
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder()
80+
.setConnectTimeout(properties.getConnectTimeout()).setReadTimeout(properties.getReadTimeout());
81+
customizers.orderedStream().forEach((c) -> c.customize(restTemplateBuilder));
82+
return new ZipkinRestTemplateSender(properties.getEndpoint(), restTemplateBuilder.build());
8283
}
8384

8485
}
@@ -90,10 +91,11 @@ static class WebClientSenderConfiguration {
9091

9192
@Bean
9293
@ConditionalOnMissingBean(Sender.class)
93-
@ConditionalOnBean(WebClient.Builder.class)
94-
ZipkinWebClientSender webClientSender(ZipkinProperties properties, WebClient.Builder webClientBuilder) {
95-
WebClient webClient = webClientBuilder.build();
96-
return new ZipkinWebClientSender(properties.getEndpoint(), webClient);
94+
ZipkinWebClientSender webClientSender(ZipkinProperties properties,
95+
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers) {
96+
WebClient.Builder builder = WebClient.builder();
97+
customizers.orderedStream().forEach((c) -> c.customize(builder));
98+
return new ZipkinWebClientSender(properties.getEndpoint(), builder.build());
9799
}
98100

99101
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-2022 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.boot.actuate.autoconfigure.tracing.zipkin;
18+
19+
import org.springframework.boot.web.client.RestTemplateBuilder;
20+
21+
/**
22+
* Callback interface that can be implemented by beans wishing to customize the
23+
* {@link RestTemplateBuilder} used to send spans to Zipkin.
24+
*
25+
* @author Marcin Grzejszczak
26+
* @since 3.0.0
27+
*/
28+
@FunctionalInterface
29+
public interface ZipkinRestTemplateBuilderCustomizer {
30+
31+
/**
32+
* Customize the rest template builder.
33+
* @param restTemplateBuilder the {@code RestTemplateBuilder} to customize
34+
*/
35+
void customize(RestTemplateBuilder restTemplateBuilder);
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-2022 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.boot.actuate.autoconfigure.tracing.zipkin;
18+
19+
import org.springframework.web.reactive.function.client.WebClient;
20+
import org.springframework.web.reactive.function.client.WebClient.Builder;
21+
22+
/**
23+
* Callback interface that can be implemented by beans wishing to customize the
24+
* {@link Builder} used to send spans to Zipkin.
25+
*
26+
* @author Marcin Grzejszczak
27+
* @since 3.0.0
28+
*/
29+
@FunctionalInterface
30+
public interface ZipkinWebClientBuilderCustomizer {
31+
32+
/**
33+
* Customize the web client builder.
34+
* @param webClientBuilder the {@code WebClient.Builder} to customize
35+
*/
36+
void customize(WebClient.Builder webClientBuilder);
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2012-2022 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.boot.actuate.autoconfigure.tracing.zipkin;
18+
19+
import org.junit.jupiter.api.Test;
20+
import zipkin2.reporter.urlconnection.URLConnectionSender;
21+
22+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
23+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration;
25+
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
26+
import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration;
27+
import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.AutoConfigurations;
29+
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
31+
import org.springframework.boot.test.context.FilteredClassLoader;
32+
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
33+
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
34+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
35+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
36+
import org.springframework.context.ConfigurableApplicationContext;
37+
38+
import static org.assertj.core.api.Assertions.assertThat;
39+
40+
/**
41+
* Integration tests for {@link ZipkinAutoConfiguration} and other related
42+
* auto-configurations.
43+
*
44+
* @author Andy Wilkinson
45+
*/
46+
class ZipkinAutoConfigurationIntegrationTests {
47+
48+
@Test
49+
void zipkinsUseOfRestTemplateDoesNotCauseACycle() {
50+
configure(new WebApplicationContextRunner())
51+
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class))
52+
.run((context) -> assertThat(context).hasNotFailed());
53+
}
54+
55+
@Test
56+
void zipkinsUseOfWebClientDoesNotCauseACycle() {
57+
configure(new ReactiveWebApplicationContextRunner())
58+
.withConfiguration(AutoConfigurations.of(WebClientAutoConfiguration.class))
59+
.run((context) -> assertThat(context).hasNotFailed());
60+
}
61+
62+
<SELF extends AbstractApplicationContextRunner<SELF, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> AbstractApplicationContextRunner<SELF, C, A> configure(
63+
AbstractApplicationContextRunner<SELF, ?, ?> runner) {
64+
return runner
65+
.withConfiguration(AutoConfigurations.of(MicrometerTracingAutoConfiguration.class,
66+
ObservationAutoConfiguration.class, BraveAutoConfiguration.class, ZipkinAutoConfiguration.class,
67+
HttpClientMetricsAutoConfiguration.class, MetricsAutoConfiguration.class,
68+
SimpleMetricsExportAutoConfiguration.class))
69+
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class));
70+
}
71+
72+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java

+14-11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
1818

1919
import org.junit.jupiter.api.Test;
20+
import org.mockito.ArgumentMatchers;
2021
import zipkin2.reporter.Sender;
2122
import zipkin2.reporter.urlconnection.URLConnectionSender;
2223

@@ -26,12 +27,12 @@
2627
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2728
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
2829
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
29-
import org.springframework.boot.web.client.RestTemplateBuilder;
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Configuration;
3232
import org.springframework.web.reactive.function.client.WebClient;
3333

3434
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.mockito.BDDMockito.then;
3536
import static org.mockito.Mockito.mock;
3637

3738
/**
@@ -66,6 +67,8 @@ void shouldPreferWebClientSenderIfWebApplicationIsReactiveAndUrlSenderIsNotAvail
6667
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
6768
assertThat(context).hasSingleBean(Sender.class);
6869
assertThat(context).hasSingleBean(ZipkinWebClientSender.class);
70+
then(context.getBean(ZipkinWebClientBuilderCustomizer.class)).should()
71+
.customize(ArgumentMatchers.any());
6972
});
7073
}
7174

@@ -90,29 +93,29 @@ void shouldPreferWebClientInNonWebApplicationAndUrlConnectionSenderIsNotAvailabl
9093
}
9194

9295
@Test
93-
void willUseRestTemplateInNonWebApplicationIfUrlConnectionSenderIsNotAvailable() {
96+
void willUseRestTemplateInNonWebApplicationIfUrlConnectionSenderAndWebclientAreNotAvailable() {
9497
this.contextRunner.withUserConfiguration(RestTemplateConfiguration.class)
95-
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")).run((context) -> {
98+
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)).run((context) -> {
9699
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
97100
assertThat(context).hasSingleBean(Sender.class);
98101
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
99102
});
100103
}
101104

102105
@Test
103-
void willUseRestTemplateInServletWebApplicationIfUrlConnectionSenderIsNotAvailable() {
106+
void willUseRestTemplateInServletWebApplicationIfUrlConnectionSenderAndWebClientNotAvailable() {
104107
this.servletContextRunner.withUserConfiguration(RestTemplateConfiguration.class)
105-
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")).run((context) -> {
108+
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)).run((context) -> {
106109
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
107110
assertThat(context).hasSingleBean(Sender.class);
108111
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
109112
});
110113
}
111114

112115
@Test
113-
void willUseRestTemplateInReactiveWebApplicationIfUrlConnectionSenderIsNotAvailable() {
116+
void willUseRestTemplateInReactiveWebApplicationIfUrlConnectionSenderAndWebClientAreNotAvailable() {
114117
this.reactiveContextRunner.withUserConfiguration(RestTemplateConfiguration.class)
115-
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")).run((context) -> {
118+
.withClassLoader(new FilteredClassLoader(URLConnectionSender.class, WebClient.class)).run((context) -> {
116119
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
117120
assertThat(context).hasSingleBean(Sender.class);
118121
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
@@ -140,8 +143,8 @@ void shouldBackOffOnCustomBeans() {
140143
private static class RestTemplateConfiguration {
141144

142145
@Bean
143-
RestTemplateBuilder restTemplateBuilder() {
144-
return new RestTemplateBuilder();
146+
ZipkinRestTemplateBuilderCustomizer restTemplateBuilder() {
147+
return mock(ZipkinRestTemplateBuilderCustomizer.class);
145148
}
146149

147150
}
@@ -150,8 +153,8 @@ RestTemplateBuilder restTemplateBuilder() {
150153
private static class WebClientConfiguration {
151154

152155
@Bean
153-
WebClient.Builder webClientBuilder() {
154-
return WebClient.builder();
156+
ZipkinWebClientBuilderCustomizer webClientBuilder() {
157+
return mock(ZipkinWebClientBuilderCustomizer.class);
155158
}
156159

157160
}

0 commit comments

Comments
 (0)