Auto-configure HTTP client builders as Lazy

Prior to this commit, HTTP client builders auto-configured by Spring
Boot would be eagerly instantiating resources, even if those were not
used by the application.

This commit makes the `RestTemplateBuilder` bean as Lazy.
`WebClient.Builder` was already a prototype bean, but some of its
dependencies could consume resources, like the `HttpClientConnector` and
the related infrastructure. This commit makes those pieces lazy.

Note that since those components are meant to help instantiate actual
HTTP clients for application components, making them lazy won't make any
difference at runtime since they'll be used during context refresh, or
they won't be used at all.

Closes gh-19549
pull/19623/head
Brian Clozel 5 years ago
parent d510a7bd80
commit 0f567c879d

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -38,6 +38,7 @@ import org.springframework.boot.web.client.RestTemplateRequestCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
/** /**
@ -54,6 +55,7 @@ import org.springframework.web.client.RestTemplate;
public class RestTemplateAutoConfiguration { public class RestTemplateAutoConfiguration {
@Bean @Bean
@Lazy
@ConditionalOnMissingBean @ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(ObjectProvider<HttpMessageConverters> messageConverters, public RestTemplateBuilder restTemplateBuilder(ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers, ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import org.springframework.boot.web.reactive.function.client.WebClientCustomizer
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@ -43,6 +44,7 @@ import org.springframework.web.reactive.function.client.WebClient;
public class ClientHttpConnectorAutoConfiguration { public class ClientHttpConnectorAutoConfiguration {
@Bean @Bean
@Lazy
@Order(0) @Order(0)
@ConditionalOnBean(ClientHttpConnector.class) @ConditionalOnBean(ClientHttpConnector.class)
public WebClientCustomizer clientConnectorCustomizer(ClientHttpConnector clientHttpConnector) { public WebClientCustomizer clientConnectorCustomizer(ClientHttpConnector clientHttpConnector) {

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.http.client.reactive.JettyResourceFactory; import org.springframework.http.client.reactive.JettyResourceFactory;
@ -54,6 +55,7 @@ class ClientHttpConnectorConfiguration {
} }
@Bean @Bean
@Lazy
public ReactorClientHttpConnector reactorClientHttpConnector(ReactorResourceFactory reactorResourceFactory) { public ReactorClientHttpConnector reactorClientHttpConnector(ReactorResourceFactory reactorResourceFactory) {
return new ReactorClientHttpConnector(reactorResourceFactory, Function.identity()); return new ReactorClientHttpConnector(reactorResourceFactory, Function.identity());
} }
@ -72,6 +74,7 @@ class ClientHttpConnectorConfiguration {
} }
@Bean @Bean
@Lazy
public JettyClientHttpConnector jettyClientHttpConnector(JettyResourceFactory jettyResourceFactory) { public JettyClientHttpConnector jettyClientHttpConnector(JettyResourceFactory jettyResourceFactory) {
SslContextFactory sslContextFactory = new SslContextFactory.Client(); SslContextFactory sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory); HttpClient httpClient = new HttpClient(sslContextFactory);

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +16,7 @@
package org.springframework.boot.autoconfigure.web.reactive.function.client; package org.springframework.boot.autoconfigure.web.reactive.function.client;
import java.util.List; import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -49,18 +49,13 @@ import org.springframework.web.reactive.function.client.WebClient;
@AutoConfigureAfter({ CodecsAutoConfiguration.class, ClientHttpConnectorAutoConfiguration.class }) @AutoConfigureAfter({ CodecsAutoConfiguration.class, ClientHttpConnectorAutoConfiguration.class })
public class WebClientAutoConfiguration { public class WebClientAutoConfiguration {
private final WebClient.Builder webClientBuilder;
public WebClientAutoConfiguration(ObjectProvider<WebClientCustomizer> customizerProvider) {
this.webClientBuilder = WebClient.builder();
customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(this.webClientBuilder));
}
@Bean @Bean
@Scope("prototype") @Scope("prototype")
@ConditionalOnMissingBean @ConditionalOnMissingBean
public WebClient.Builder webClientBuilder() { public WebClient.Builder webClientBuilder(ObjectProvider<WebClientCustomizer> customizerProvider) {
return this.webClientBuilder.clone(); WebClient.Builder builder = WebClient.builder();
customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ -70,8 +65,8 @@ public class WebClientAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@Order(0) @Order(0)
public WebClientCodecCustomizer exchangeStrategiesCustomizer(List<CodecCustomizer> codecCustomizers) { public WebClientCodecCustomizer exchangeStrategiesCustomizer(ObjectProvider<CodecCustomizer> codecCustomizers) {
return new WebClientCodecCustomizer(codecCustomizers); return new WebClientCodecCustomizer(codecCustomizers.orderedStream().collect(Collectors.toList()));
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,7 +20,6 @@ import java.util.List;
import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
/** /**
@ -39,9 +38,8 @@ public class WebClientCodecCustomizer implements WebClientCustomizer {
@Override @Override
public void customize(WebClient.Builder webClientBuilder) { public void customize(WebClient.Builder webClientBuilder) {
webClientBuilder.exchangeStrategies(ExchangeStrategies.builder() webClientBuilder
.codecs((codecs) -> this.codecCustomizers.forEach((customizer) -> customizer.customize(codecs))) .codecs((codecs) -> this.codecCustomizers.forEach((customizer) -> customizer.customize(codecs)));
.build());
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -58,6 +58,13 @@ class RestTemplateAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class));
@Test
void restTemplateBuilderShouldBeLazilyDefined() {
this.contextRunner.run(
(context) -> assertThat(context.getBeanFactory().getBeanDefinition("restTemplateBuilder").isLazyInit())
.isTrue());
}
@Test @Test
void restTemplateWhenMessageConvertersDefinedShouldHaveMessageConverters() { void restTemplateWhenMessageConvertersDefinedShouldHaveMessageConverters() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.web.reactive.function.client;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
@ -44,6 +45,18 @@ class ClientHttpConnectorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class));
@Test
void shouldCreateResourcesLazily() {
this.contextRunner.run((context) -> {
BeanDefinition customizerDefinition = context.getBeanFactory()
.getBeanDefinition("clientConnectorCustomizer");
assertThat(customizerDefinition.isLazyInit()).isTrue();
BeanDefinition connectorDefinition = context.getBeanFactory()
.getBeanDefinition("reactorClientHttpConnector");
assertThat(connectorDefinition.isLazyInit()).isTrue();
});
}
@Test @Test
void shouldCreateHttpClientBeans() { void shouldCreateHttpClientBeans() {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -102,7 +102,7 @@ class WebClientAutoConfigurationTests {
verify(secondConnector).connect(eq(HttpMethod.GET), eq(URI.create("https://second.example.org/foo")), verify(secondConnector).connect(eq(HttpMethod.GET), eq(URI.create("https://second.example.org/foo")),
any()); any());
WebClientCustomizer customizer = context.getBean("webClientCustomizer", WebClientCustomizer.class); WebClientCustomizer customizer = context.getBean("webClientCustomizer", WebClientCustomizer.class);
verify(customizer, times(1)).customize(any(WebClient.Builder.class)); verify(customizer, times(2)).customize(any(WebClient.Builder.class));
}); });
} }

Loading…
Cancel
Save