Add RestClient HttpMessageConverters support

Update `RestClientAutoConfiguration` to apply `HttpMessageConverters`
configuration.

See gh-36213
This commit is contained in:
Phillip Webb 2023-07-05 14:11:28 +01:00
parent 2d2f050262
commit 5e01c66552
4 changed files with 212 additions and 0 deletions

View File

@ -0,0 +1,60 @@
/*
* Copyright 2012-2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.client;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClient;
/**
* {@link RestClientCustomizer} to apply {@link HttpMessageConverter
* HttpMessageConverters}.
*
* @author Phillip Webb
* @since 3.2.0
*/
public class HttpMessageConvertersRestClientCustomizer implements RestClientCustomizer {
private final Iterable<? extends HttpMessageConverter<?>> messageConverters;
public HttpMessageConvertersRestClientCustomizer(HttpMessageConverter<?>... messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null");
this.messageConverters = Arrays.asList(messageConverters);
}
HttpMessageConvertersRestClientCustomizer(HttpMessageConverters messageConverters) {
this.messageConverters = messageConverters;
}
@Override
public void customize(RestClient.Builder restClientBuilder) {
restClientBuilder.messageConverters(this::configureMessageConverters);
}
private void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
if (this.messageConverters != null) {
messageConverters.clear();
this.messageConverters.forEach(messageConverters::add);
}
}
}

View File

@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
@ -28,6 +29,8 @@ import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Scope;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.client.RestClient;
/**
@ -45,6 +48,14 @@ import org.springframework.web.client.RestClient;
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Order(Ordered.LOWEST_PRECEDENCE)
public HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomizer(
ObjectProvider<HttpMessageConverters> messageConverters) {
return new HttpMessageConvertersRestClientCustomizer(messageConverters.getIfUnique());
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean

View File

@ -0,0 +1,77 @@
/*
* Copyright 2012-2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.client;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HttpMessageConvertersRestClientCustomizer}
*
* @author Phillip Webb
*/
class HttpMessageConvertersRestClientCustomizerTests {
@Test
void createWhenNullMessageConvertersArrayThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new HttpMessageConvertersRestClientCustomizer((HttpMessageConverter<?>[]) null))
.withMessage("MessageConverters must not be null");
}
@Test
void createWhenNullMessageConvertersDoesNotCustomize() {
HttpMessageConverter<?> c0 = mock();
assertThat(apply(new HttpMessageConvertersRestClientCustomizer((HttpMessageConverters) null), c0))
.containsExactly(c0);
}
@Test
void customizeConfiguresMessageConverters() {
HttpMessageConverter<?> c0 = mock();
HttpMessageConverter<?> c1 = mock();
HttpMessageConverter<?> c2 = mock();
assertThat(apply(new HttpMessageConvertersRestClientCustomizer(c1, c2), c0)).containsExactly(c1, c2);
}
@SuppressWarnings("unchecked")
private List<HttpMessageConverter<?>> apply(HttpMessageConvertersRestClientCustomizer customizer,
HttpMessageConverter<?>... converters) {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(Arrays.asList(converters));
RestClient.Builder restClientBuilder = mock();
ArgumentCaptor<Consumer<List<HttpMessageConverter<?>>>> captor = ArgumentCaptor.forClass(Consumer.class);
given(restClientBuilder.messageConverters(captor.capture())).willReturn(restClientBuilder);
customizer.customize(restClientBuilder);
captor.getValue().accept(messageConverters);
return messageConverters;
}
}

View File

@ -16,14 +16,21 @@
package org.springframework.boot.autoconfigure.web.client;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@ -77,6 +84,49 @@ class RestClientAutoConfigurationTests {
});
}
@Test
@SuppressWarnings("unchecked")
void restClientWhenMessageConvertersDefinedShouldHaveMessageConverters() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.withUserConfiguration(RestClientConfig.class)
.run((context) -> {
RestClient restClient = context.getBean(RestClient.class);
List<HttpMessageConverter<?>> expectedConverters = context.getBean(HttpMessageConverters.class)
.getConverters();
List<HttpMessageConverter<?>> actualConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
.getField(restClient, "messageConverters");
assertThat(actualConverters).containsExactlyElementsOf(expectedConverters);
});
}
@Test
@SuppressWarnings("unchecked")
void restClientWhenNoMessageConvertersDefinedShouldHaveDefaultMessageConverters() {
this.contextRunner.withUserConfiguration(RestClientConfig.class).run((context) -> {
RestClient restClient = context.getBean(RestClient.class);
RestClient defaultRestClient = RestClient.builder().build();
List<HttpMessageConverter<?>> actualConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
.getField(restClient, "messageConverters");
List<HttpMessageConverter<?>> expectedConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
.getField(defaultRestClient, "messageConverters");
assertThat(actualConverters).hasSameSizeAs(expectedConverters);
});
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
void restClientWhenHasCustomMessageConvertersShouldHaveMessageConverters() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.withUserConfiguration(CustomHttpMessageConverter.class, RestClientConfig.class)
.run((context) -> {
RestClient restClient = context.getBean(RestClient.class);
List<HttpMessageConverter<?>> actualConverters = (List<HttpMessageConverter<?>>) ReflectionTestUtils
.getField(restClient, "messageConverters");
assertThat(actualConverters).extracting(HttpMessageConverter::getClass)
.contains((Class) CustomHttpMessageConverter.class);
});
}
@Configuration(proxyBeanMethods = false)
static class CodecConfiguration {
@ -111,4 +161,18 @@ class RestClientAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class RestClientConfig {
@Bean
RestClient restClient(RestClient.Builder restClientBuilder) {
return restClientBuilder.build();
}
}
static class CustomHttpMessageConverter extends StringHttpMessageConverter {
}
}