From 0f567c879d9a710ac2e4068b57c88335daeaf460 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 10 Jan 2020 22:15:29 +0100 Subject: [PATCH] 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 --- .../client/RestTemplateAutoConfiguration.java | 4 +++- .../ClientHttpConnectorAutoConfiguration.java | 4 +++- .../ClientHttpConnectorConfiguration.java | 5 ++++- .../client/WebClientAutoConfiguration.java | 21 +++++++------------ .../client/WebClientCodecCustomizer.java | 8 +++---- .../RestTemplateAutoConfigurationTests.java | 9 +++++++- ...ntHttpConnectorAutoConfigurationTests.java | 15 ++++++++++++- .../WebClientAutoConfigurationTests.java | 4 ++-- 8 files changed, 45 insertions(+), 25 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java index bd8536c3f0..23b128790c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java @@ -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"); * 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.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.web.client.RestTemplate; /** @@ -54,6 +55,7 @@ import org.springframework.web.client.RestTemplate; public class RestTemplateAutoConfiguration { @Bean + @Lazy @ConditionalOnMissingBean public RestTemplateBuilder restTemplateBuilder(ObjectProvider messageConverters, ObjectProvider restTemplateCustomizers, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java index 7e51c30cbf..1d34e8333e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java @@ -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"); * 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.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; import org.springframework.core.annotation.Order; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; @@ -43,6 +44,7 @@ import org.springframework.web.reactive.function.client.WebClient; public class ClientHttpConnectorAutoConfiguration { @Bean + @Lazy @Order(0) @ConditionalOnBean(ClientHttpConnector.class) public WebClientCustomizer clientConnectorCustomizer(ClientHttpConnector clientHttpConnector) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java index e27ff0e935..99c3787ee5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorConfiguration.java @@ -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"); * 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.context.annotation.Bean; 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.JettyClientHttpConnector; import org.springframework.http.client.reactive.JettyResourceFactory; @@ -54,6 +55,7 @@ class ClientHttpConnectorConfiguration { } @Bean + @Lazy public ReactorClientHttpConnector reactorClientHttpConnector(ReactorResourceFactory reactorResourceFactory) { return new ReactorClientHttpConnector(reactorResourceFactory, Function.identity()); } @@ -72,6 +74,7 @@ class ClientHttpConnectorConfiguration { } @Bean + @Lazy public JettyClientHttpConnector jettyClientHttpConnector(JettyResourceFactory jettyResourceFactory) { SslContextFactory sslContextFactory = new SslContextFactory.Client(); HttpClient httpClient = new HttpClient(sslContextFactory); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java index 8625053128..a9b5b0dd14 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java @@ -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"); * 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; -import java.util.List; +import java.util.stream.Collectors; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -49,18 +49,13 @@ import org.springframework.web.reactive.function.client.WebClient; @AutoConfigureAfter({ CodecsAutoConfiguration.class, ClientHttpConnectorAutoConfiguration.class }) public class WebClientAutoConfiguration { - private final WebClient.Builder webClientBuilder; - - public WebClientAutoConfiguration(ObjectProvider customizerProvider) { - this.webClientBuilder = WebClient.builder(); - customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(this.webClientBuilder)); - } - @Bean @Scope("prototype") @ConditionalOnMissingBean - public WebClient.Builder webClientBuilder() { - return this.webClientBuilder.clone(); + public WebClient.Builder webClientBuilder(ObjectProvider customizerProvider) { + WebClient.Builder builder = WebClient.builder(); + customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder; } @Configuration(proxyBeanMethods = false) @@ -70,8 +65,8 @@ public class WebClientAutoConfiguration { @Bean @ConditionalOnMissingBean @Order(0) - public WebClientCodecCustomizer exchangeStrategiesCustomizer(List codecCustomizers) { - return new WebClientCodecCustomizer(codecCustomizers); + public WebClientCodecCustomizer exchangeStrategiesCustomizer(ObjectProvider codecCustomizers) { + return new WebClientCodecCustomizer(codecCustomizers.orderedStream().collect(Collectors.toList())); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientCodecCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientCodecCustomizer.java index 380ac8d35b..8a812baa6b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientCodecCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientCodecCustomizer.java @@ -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"); * 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.reactive.function.client.WebClientCustomizer; -import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; /** @@ -39,9 +38,8 @@ public class WebClientCodecCustomizer implements WebClientCustomizer { @Override public void customize(WebClient.Builder webClientBuilder) { - webClientBuilder.exchangeStrategies(ExchangeStrategies.builder() - .codecs((codecs) -> this.codecCustomizers.forEach((customizer) -> customizer.customize(codecs))) - .build()); + webClientBuilder + .codecs((codecs) -> this.codecCustomizers.forEach((customizer) -> customizer.customize(codecs))); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java index 995f39d56c..aa18715787 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -58,6 +58,13 @@ class RestTemplateAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class)); + @Test + void restTemplateBuilderShouldBeLazilyDefined() { + this.contextRunner.run( + (context) -> assertThat(context.getBeanFactory().getBeanDefinition("restTemplateBuilder").isLazyInit()) + .isTrue()); + } + @Test void restTemplateWhenMessageConvertersDefinedShouldHaveMessageConverters() { this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java index 82304c580a..c5d6f82fe9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfigurationTests.java @@ -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"); * 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.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; @@ -44,6 +45,18 @@ class ClientHttpConnectorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .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 void shouldCreateHttpClientBeans() { this.contextRunner.run((context) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java index 4d71c110d7..ee97b1330d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java @@ -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"); * 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")), any()); 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)); }); }